diff --git a/tools/exp/codegen/method.go b/tools/exp/codegen/method.go index ad379d0..4f6df1b 100644 --- a/tools/exp/codegen/method.go +++ b/tools/exp/codegen/method.go @@ -206,3 +206,12 @@ func (m Method) Call(on string, params ...jen.Code) jen.Code { func (m Method) Name() string { return m.function.name } + +// ToFunctionSignature obtains this method's FunctionSignature. +func (m Method) ToFunctionSignature() FunctionSignature { + return FunctionSignature{ + Name: m.Name(), + Params: m.function.params, + Ret: m.function.ret, + } +} diff --git a/tools/exp/codegen/struct.go b/tools/exp/codegen/struct.go index 6efe51c..9920003 100644 --- a/tools/exp/codegen/struct.go +++ b/tools/exp/codegen/struct.go @@ -78,3 +78,12 @@ func (s *Struct) Method(name string) *Method { func (s *Struct) Constructors(name string) *Function { return s.constructors[name] } + +// ToInterface creates an interface version of this struct. +func (s *Struct) ToInterface(pkg, name, comment string) *Interface { + fns := make([]FunctionSignature, 0, len(s.methods)) + for _, m := range s.methods { + fns = append(fns, m.ToFunctionSignature()) + } + return NewInterface(pkg, name, fns, comment) +} diff --git a/tools/exp/codegen/typedef.go b/tools/exp/codegen/typedef.go index dade2b3..ef03334 100644 --- a/tools/exp/codegen/typedef.go +++ b/tools/exp/codegen/typedef.go @@ -66,3 +66,12 @@ func (t *Typedef) Method(name string) *Method { func (t *Typedef) Constructors(name string) *Function { return t.constructors[name] } + +// ToInterface creates an interface version of this typedef. +func (t *Typedef) ToInterface(pkg, name, comment string) *Interface { + fns := make([]FunctionSignature, 0, len(t.methods)) + for _, m := range t.methods { + fns = append(fns, m.ToFunctionSignature()) + } + return NewInterface(pkg, name, fns, comment) +} diff --git a/tools/exp/convert/convert.go b/tools/exp/convert/convert.go index 8e868e9..f556715 100644 --- a/tools/exp/convert/convert.go +++ b/tools/exp/convert/convert.go @@ -19,6 +19,7 @@ type vocabulary struct { FProps map[string]*props.FunctionalPropertyGenerator NFProps map[string]*props.NonFunctionalPropertyGenerator Types map[string]*props.TypeGenerator + Manager *props.ManagerGenerator } func newVocabulary() vocabulary { @@ -30,6 +31,30 @@ func newVocabulary() vocabulary { } } +func (v vocabulary) typeArray() []*props.TypeGenerator { + tg := make([]*props.TypeGenerator, 0, len(v.Types)) + for _, t := range v.Types { + tg = append(tg, t) + } + return tg +} + +func (v vocabulary) funcPropArray() []*props.FunctionalPropertyGenerator { + fp := make([]*props.FunctionalPropertyGenerator, 0, len(v.FProps)) + for _, f := range v.FProps { + fp = append(fp, f) + } + return fp +} + +func (v vocabulary) nonFuncPropArray() []*props.NonFunctionalPropertyGenerator { + nfp := make([]*props.NonFunctionalPropertyGenerator, 0, len(v.NFProps)) + for _, nf := range v.NFProps { + nfp = append(nfp, nf) + } + return nfp +} + type PropertyPackagePolicy int const ( @@ -48,11 +73,11 @@ const ( type Converter struct { Registry *rdf.RDFRegistry - VocabularyRoot string + VocabularyRoot *props.PackageManager PropertyPackagePolicy PropertyPackagePolicy - PropertyPackageRoot string + PropertyPackageRoot *props.PackageManager TypePackagePolicy TypePackagePolicy - TypePackageRoot string + TypePackageRoot *props.PackageManager } func (c Converter) Convert(p *rdf.ParsedVocabulary) (f []*File, e error) { @@ -70,17 +95,13 @@ func (c Converter) convertToFiles(v vocabulary) (f []*File, e error) { // TODO: Implement } for _, i := range v.FProps { - var pkg string - pkg, e = c.propertyPackageFile(i) + var pm *props.PackageManager + pm, e = c.propertyPackageManager(i) if e != nil { return } - var dir string - dir, e = c.propertyPackageDirectory(i) - if e != nil { - return - } - file := jen.NewFilePath(pkg) + dir := pm.PrivatePackage().Path() + file := jen.NewFilePath(dir) file.Add(i.Definition().Definition()) f = append(f, &File{ F: file, @@ -89,17 +110,13 @@ func (c Converter) convertToFiles(v vocabulary) (f []*File, e error) { }) } for _, i := range v.NFProps { - var pkg string - pkg, e = c.propertyPackageFile(i) + var pm *props.PackageManager + pm, e = c.propertyPackageManager(i) if e != nil { return } - var dir string - dir, e = c.propertyPackageDirectory(i) - if e != nil { - return - } - file := jen.NewFilePath(pkg) + dir := pm.PrivatePackage().Path() + file := jen.NewFilePath(dir) s, t := i.Definitions() file.Add(s.Definition()).Line().Add(t.Definition()) f = append(f, &File{ @@ -109,17 +126,13 @@ func (c Converter) convertToFiles(v vocabulary) (f []*File, e error) { }) } for _, i := range v.Types { - var pkg string - pkg, e = c.typePackageFile(i) + var pm *props.PackageManager + pm, e = c.typePackageManager(i) if e != nil { return } - var dir string - dir, e = c.typePackageDirectory(i) - if e != nil { - return - } - file := jen.NewFilePath(pkg) + dir := pm.PrivatePackage().Path() + file := jen.NewFilePath(dir) file.Add(i.Definition().Definition()) f = append(f, &File{ F: file, @@ -127,6 +140,15 @@ func (c Converter) convertToFiles(v vocabulary) (f []*File, e error) { Directory: dir, }) } + // TODO: For Manager + dir := c.VocabularyRoot.PrivatePackage().Path() + file := jen.NewFilePath(dir) + file.Add(v.Manager.PrivateManager().Definition()) + f = append(f, &File{ + F: file, + FileName: "gen_manager.go", + Directory: dir, + }) return } @@ -186,6 +208,11 @@ func (c Converter) convertVocabulary(p *rdf.ParsedVocabulary) (v vocabulary, e e return } } + v.Manager, e = props.NewManagerGenerator( + *c.VocabularyRoot, + v.typeArray(), + v.funcPropArray(), + v.nonFuncPropArray()) return } @@ -195,11 +222,12 @@ func (c Converter) convertType(t rdf.VocabularyType, existingNFProps map[string]*props.NonFunctionalPropertyGenerator, existingTypes map[string]*props.TypeGenerator) (tg *props.TypeGenerator, e error) { // Determine the props package name - var pkg string - pkg, e = c.typePackageName(t) + var pm *props.PackageManager + pm, e = c.typePackageManager(t) if e != nil { return } + pkg := pm.PrivatePackage() // Determine the properties for this type var p []props.Property for _, prop := range t.Properties { @@ -251,60 +279,48 @@ func (c Converter) convertType(t rdf.VocabularyType, ext = append(ext, existingTypes[ex.Name]) } } - tg, e = props.NewTypeGenerator( - pkg, - c.convertTypeToName(t), - t.Notes, - p, - wop, - ext, - nil) - if e != nil { - return - } // Apply disjoint if both sides are available because the TypeGenerator // does not know the entire vocabulary, so cannot do this lookup and // create this connection for us. - // - // TODO: Pass in the disjoint and have the TypeGenerator complete the - // doubly-linked connection for us. + var disjoint []*props.TypeGenerator for _, disj := range t.DisjointWith { if len(disj.Vocab) != 0 { // TODO: This should be fixed to handle references e = fmt.Errorf("unhandled use case: type is disjoint with another type outside its vocabulary") return } else if disjointType, ok := existingTypes[disj.Name]; ok { - disjointType.AddDisjoint(tg) - tg.AddDisjoint(disjointType) + disjoint = append(disjoint, disjointType) } } - // Apply the type's KindSerializationFuncs to the property because there - // is no way for the TypeGenerator to know all properties who have a - // range of this type. + // Pass in properties whose range is this type so it can build + // references properly. // - // TODO: Pass in these properties to the TypeGenerator constructor so it - // can build these double-links properly. Note this would also need to - // apply to referenced properties, possibly. + // TODO: Enable this for referenced properties. + name := c.convertTypeToName(t) + var rangeProps []props.Property for _, prop := range existingFProps { for _, kind := range prop.Kinds { - if kind.Name.LowerName == tg.TypeName() { - ser, deser, less := tg.KindSerializationFuncs() - if e = prop.SetKindFns(tg.TypeName(), ser, deser, less); e != nil { - return - } + if kind.Name.LowerName == name { + rangeProps = append(rangeProps, prop) } } } for _, prop := range existingNFProps { for _, kind := range prop.Kinds { - if kind.Name.LowerName == tg.TypeName() { - ser, deser, less := tg.KindSerializationFuncs() - if e = prop.SetKindFns(tg.TypeName(), ser, deser, less); e != nil { - return - } + if kind.Name.LowerName == name { + rangeProps = append(rangeProps, prop) } } } + tg, e = props.NewTypeGenerator( + pkg, + name, + t.Notes, + p, + wop, + rangeProps, + ext, + disjoint) return } @@ -317,11 +333,12 @@ func (c Converter) convertFunctionalProperty(p rdf.VocabularyProperty, if e != nil { return } - var pkg string - pkg, e = c.propertyPackageName(p) + var pm *props.PackageManager + pm, e = c.propertyPackageManager(p) if e != nil { return } + pkg := pm.PrivatePackage() fp = props.NewFunctionalPropertyGenerator( pkg, c.toIdentifier(p), @@ -339,11 +356,12 @@ func (c Converter) convertNonFunctionalProperty(p rdf.VocabularyProperty, if e != nil { return } - var pkg string - pkg, e = c.propertyPackageName(p) + var pm *props.PackageManager + pm, e = c.propertyPackageManager(p) if e != nil { return } + pkg := pm.PrivatePackage() nfp = props.NewNonFunctionalPropertyGenerator( pkg, c.toIdentifier(p), @@ -357,9 +375,10 @@ func (c Converter) convertValue(v rdf.VocabularyValue) (k *props.Kind) { Name: c.toIdentifier(v), ConcreteKind: v.DefinitionType, Nilable: c.isNilable(v.DefinitionType), - SerializeFn: v.SerializeFn, - DeserializeFn: v.DeserializeFn, - LessFn: v.LessFn, + // TODO + SerializeFn: jen.Empty().Add(v.SerializeFn.Call()), + DeserializeFn: jen.Empty().Add(v.DeserializeFn.Call()), + LessFn: jen.Empty().Add(v.LessFn.Call()), } return } @@ -445,12 +464,12 @@ func (c Converter) propertyKinds(v rdf.VocabularyProperty, return } -func (c Converter) typePackageName(v rdf.VocabularyType) (pkg string, e error) { +func (c Converter) typePackageManager(v typeNamer) (pkg *props.PackageManager, e error) { switch c.TypePackagePolicy { case TypeFlatUnderRoot: pkg = c.TypePackageRoot case TypeIndividualUnderRoot: - pkg = v.Name + pkg = c.TypePackageRoot.Sub(v.TypeName()) case TypeFlatUnderVocabularyRoot: pkg = c.VocabularyRoot default: @@ -459,12 +478,12 @@ func (c Converter) typePackageName(v rdf.VocabularyType) (pkg string, e error) { return } -func (c Converter) propertyPackageName(v rdf.VocabularyProperty) (pkg string, e error) { +func (c Converter) propertyPackageManager(v propertyNamer) (pkg *props.PackageManager, e error) { switch c.PropertyPackagePolicy { case PropertyFlatUnderRoot: pkg = c.PropertyPackageRoot case PropertyIndividualUnderRoot: - pkg = v.Name + pkg = c.PropertyPackageRoot.Sub(v.PropertyName()) case PropertyFlatUnderVocabularyRoot: pkg = c.VocabularyRoot default: @@ -473,33 +492,14 @@ func (c Converter) propertyPackageName(v rdf.VocabularyProperty) (pkg string, e return } -func (c Converter) typePackageDirectory(v *props.TypeGenerator) (dir string, e error) { - switch c.TypePackagePolicy { - case TypeFlatUnderRoot: - dir = fmt.Sprintf("%s/%s/", c.VocabularyRoot, c.TypePackageRoot) - case TypeIndividualUnderRoot: - dir = fmt.Sprintf("%s/%s/%s/", c.VocabularyRoot, c.TypePackageRoot, v.TypeName()) - case TypeFlatUnderVocabularyRoot: - dir = c.VocabularyRoot + "/" - default: - e = fmt.Errorf("unrecognized TypePackagePolicy: %v", c.TypePackagePolicy) - } - return +type typeNamer interface { + TypeName() string } -func (c Converter) typePackageFile(v *props.TypeGenerator) (pkg string, e error) { - switch c.TypePackagePolicy { - case TypeFlatUnderRoot: - pkg = fmt.Sprintf("%s/%s", c.VocabularyRoot, c.TypePackageRoot) - case TypeIndividualUnderRoot: - pkg = fmt.Sprintf("%s/%s/%s", c.VocabularyRoot, c.TypePackageRoot, v.TypeName()) - case TypeFlatUnderVocabularyRoot: - pkg = c.VocabularyRoot - default: - e = fmt.Errorf("unrecognized TypePackagePolicy: %v", c.TypePackagePolicy) - } - return -} +var ( + _ typeNamer = &props.TypeGenerator{} + _ typeNamer = &rdf.VocabularyType{} +) type propertyNamer interface { PropertyName() string @@ -508,36 +508,9 @@ type propertyNamer interface { var ( _ propertyNamer = &props.FunctionalPropertyGenerator{} _ propertyNamer = &props.NonFunctionalPropertyGenerator{} + _ propertyNamer = &rdf.VocabularyProperty{} ) -func (c Converter) propertyPackageDirectory(v propertyNamer) (dir string, e error) { - switch c.PropertyPackagePolicy { - case PropertyFlatUnderRoot: - dir = fmt.Sprintf("%s/%s/", c.VocabularyRoot, c.PropertyPackageRoot) - case PropertyIndividualUnderRoot: - dir = fmt.Sprintf("%s/%s/%s/", c.VocabularyRoot, c.PropertyPackageRoot, v.PropertyName()) - case PropertyFlatUnderVocabularyRoot: - dir = c.VocabularyRoot + "/" - default: - e = fmt.Errorf("unrecognized PropertyPackagePolicy: %v", c.PropertyPackagePolicy) - } - return -} - -func (c Converter) propertyPackageFile(v propertyNamer) (pkg string, e error) { - switch c.PropertyPackagePolicy { - case PropertyFlatUnderRoot: - pkg = fmt.Sprintf("%s/%s", c.VocabularyRoot, c.PropertyPackageRoot) - case PropertyIndividualUnderRoot: - pkg = fmt.Sprintf("%s/%s/%s", c.VocabularyRoot, c.PropertyPackageRoot, v.PropertyName()) - case PropertyFlatUnderVocabularyRoot: - pkg = c.VocabularyRoot - default: - e = fmt.Errorf("unrecognized PropertyPackagePolicy: %v", c.PropertyPackagePolicy) - } - return -} - func (c Converter) toIdentifier(n rdf.NameGetter) props.Identifier { return props.Identifier{ LowerName: n.GetName(), diff --git a/tools/exp/main.go b/tools/exp/main.go index 37b5730..1f9c2e8 100644 --- a/tools/exp/main.go +++ b/tools/exp/main.go @@ -6,6 +6,7 @@ import ( "fmt" "github.com/cjslep/activity/tools/exp/convert" "github.com/cjslep/activity/tools/exp/rdf" + "github.com/cjslep/activity/tools/exp/props" "github.com/cjslep/activity/tools/exp/rdf/owl" "github.com/cjslep/activity/tools/exp/rdf/rdfs" "github.com/cjslep/activity/tools/exp/rdf/schema" @@ -81,11 +82,11 @@ func main() { } c := &convert.Converter{ Registry: registry, - VocabularyRoot: "as", + VocabularyRoot: props.NewPackageManager("as"), PropertyPackagePolicy: convert.PropertyFlatUnderRoot, - PropertyPackageRoot: "props", + PropertyPackageRoot: props.NewPackageManager("props"), TypePackagePolicy: convert.TypeFlatUnderRoot, - TypePackageRoot: "types", + TypePackageRoot: props.NewPackageManager("types"), } f, err := c.Convert(p) if err != nil { diff --git a/tools/exp/props/funcprop.go b/tools/exp/props/funcprop.go index 860cc3e..60f9170 100644 --- a/tools/exp/props/funcprop.go +++ b/tools/exp/props/funcprop.go @@ -19,7 +19,10 @@ type FunctionalPropertyGenerator struct { // NewFunctionalPropertyGenerator is a convenience constructor to create // FunctionalPropertyGenerators. -func NewFunctionalPropertyGenerator(pkg string, +// +// PropertyGenerators shoulf be in the first pass to construct, before types and +// other generators are constructed. +func NewFunctionalPropertyGenerator(pkg Package, name Identifier, kinds []Kind, hasNaturalLanguageMap bool) *FunctionalPropertyGenerator { @@ -33,6 +36,12 @@ func NewFunctionalPropertyGenerator(pkg string, } } +// toInterface creates the interface version of the definition generated. +func (p *FunctionalPropertyGenerator) toInterface(pkg Package) *codegen.Interface { + s := p.Definition() + return s.ToInterface(pkg.Path(), p.InterfaceName(), "") +} + // isSingleTypeDef determines whether a special-case API can be generated for // one allowed Kind. func (p *FunctionalPropertyGenerator) isSingleTypeDef() bool { @@ -113,7 +122,7 @@ func (p *FunctionalPropertyGenerator) funcs() []*codegen.Method { } methods := []*codegen.Method{ codegen.NewCommentedValueMethod( - p.PackageName(), + p.Package.Path(), kindIndexMethod, p.StructName(), /*params=*/ nil, @@ -129,7 +138,7 @@ func (p *FunctionalPropertyGenerator) funcs() []*codegen.Method { // IsLanguageMap Method methods = append(methods, codegen.NewCommentedValueMethod( - p.PackageName(), + p.Package.Path(), isLanguageMapMethod, p.StructName(), /*params=*/ nil, @@ -154,7 +163,7 @@ func (p *FunctionalPropertyGenerator) funcs() []*codegen.Method { // HasLanguage Method methods = append(methods, codegen.NewCommentedValueMethod( - p.PackageName(), + p.Package.Path(), hasLanguageMethod, p.StructName(), []jen.Code{jen.Id("bcp47").String()}, @@ -182,7 +191,7 @@ func (p *FunctionalPropertyGenerator) funcs() []*codegen.Method { // GetLanguage Method methods = append(methods, codegen.NewCommentedValueMethod( - p.PackageName(), + p.Package.Path(), getLanguageMethod, p.StructName(), []jen.Code{jen.Id("bcp47").String()}, @@ -214,7 +223,7 @@ func (p *FunctionalPropertyGenerator) funcs() []*codegen.Method { // SetLanguage Method methods = append(methods, codegen.NewCommentedPointerMethod( - p.PackageName(), + p.Package.Path(), setLanguageMethod, p.StructName(), []jen.Code{ @@ -248,7 +257,7 @@ func (p *FunctionalPropertyGenerator) funcs() []*codegen.Method { // serializationFuncs produces the Methods and Functions needed for a // functional property to be serialized and deserialized to and from an // encoding. -func (p *FunctionalPropertyGenerator) serializationFuncs() ([]*codegen.Method, []*codegen.Function) { +func (p *FunctionalPropertyGenerator) serializationFuncs() (*codegen.Method, *codegen.Function) { serializeFns := jen.Empty() for i, kind := range p.Kinds { if i > 0 { @@ -271,20 +280,17 @@ func (p *FunctionalPropertyGenerator) serializationFuncs() ([]*codegen.Method, [ ), ) } - serialize := []*codegen.Method{ - codegen.NewCommentedValueMethod( - p.PackageName(), - p.serializeFnName(), - p.StructName(), - /*params=*/ nil, - []jen.Code{jen.Interface(), jen.Error()}, - []jen.Code{serializeFns, jen.Return( - jen.Id(codegen.This()).Dot(unknownMemberName), - jen.Nil(), - )}, - jen.Commentf("%s converts this into an interface representation suitable for marshalling into a text or binary format.", p.serializeFnName()), - ), - } + serialize := codegen.NewCommentedValueMethod( + p.Package.Path(), + p.serializeFnName(), + p.StructName(), + /*params=*/ nil, + []jen.Code{jen.Interface(), jen.Error()}, + []jen.Code{serializeFns, jen.Return( + jen.Id(codegen.This()).Dot(unknownMemberName), + jen.Nil(), + )}, + jen.Commentf("%s converts this into an interface representation suitable for marshalling into a text or binary format.", p.serializeFnName())) deserializeFns := jen.Empty() for i, kind := range p.Kinds { if i > 0 { @@ -315,49 +321,45 @@ func (p *FunctionalPropertyGenerator) serializationFuncs() ([]*codegen.Method, [ ), ) } - var deserialize []*codegen.Function + var deserialize *codegen.Function if p.asIterator { - deserialize = append(deserialize, - codegen.NewCommentedFunction( - p.PackageName(), - p.DeserializeFnName(), - []jen.Code{jen.Id("i").Interface()}, - []jen.Code{jen.Op("*").Id(p.StructName()), jen.Error()}, - []jen.Code{ - p.addUnknownDeserializeCode(deserializeFns), - jen.Return( - jen.Nil(), - jen.Nil(), - ), - }, - jen.Commentf("%s creates an iterator from an element that has been unmarshalled from a text or binary format.", p.DeserializeFnName()), - )) + deserialize = codegen.NewCommentedFunction( + p.Package.Path(), + p.DeserializeFnName(), + []jen.Code{jen.Id("i").Interface()}, + []jen.Code{jen.Op("*").Id(p.StructName()), jen.Error()}, + []jen.Code{ + p.addUnknownDeserializeCode(deserializeFns), + jen.Return( + jen.Nil(), + jen.Nil(), + ), + }, + jen.Commentf("%s creates an iterator from an element that has been unmarshalled from a text or binary format.", p.DeserializeFnName())) } else { - deserialize = append(deserialize, - codegen.NewCommentedFunction( - p.PackageName(), - p.DeserializeFnName(), - []jen.Code{jen.Id("m").Map(jen.String()).Interface()}, - []jen.Code{jen.Op("*").Id(p.StructName()), jen.Error()}, - []jen.Code{ - jen.If( - jen.List( - jen.Id("i"), - jen.Id("ok"), - ).Op(":=").Id("m").Index( - jen.Lit(p.PropertyName()), - ), + deserialize = codegen.NewCommentedFunction( + p.Package.Path(), + p.DeserializeFnName(), + []jen.Code{jen.Id("m").Map(jen.String()).Interface()}, + []jen.Code{jen.Op("*").Id(p.StructName()), jen.Error()}, + []jen.Code{ + jen.If( + jen.List( + jen.Id("i"), jen.Id("ok"), - ).Block( - p.addUnknownDeserializeCode(deserializeFns), + ).Op(":=").Id("m").Index( + jen.Lit(p.PropertyName()), ), - jen.Return( - jen.Nil(), - jen.Nil(), - ), - }, - jen.Commentf("%s creates a %q property from an interface representation that has been unmarshalled from a text or binary format.", p.DeserializeFnName(), p.PropertyName()), - )) + jen.Id("ok"), + ).Block( + p.addUnknownDeserializeCode(deserializeFns), + ), + jen.Return( + jen.Nil(), + jen.Nil(), + ), + }, + jen.Commentf("%s creates a %q property from an interface representation that has been unmarshalled from a text or binary format.", p.DeserializeFnName(), p.PropertyName())) } return serialize, deserialize } @@ -389,7 +391,11 @@ func (p *FunctionalPropertyGenerator) singleTypeDef() *codegen.Struct { if p.HasNaturalLanguageMap { kindMembers = append(kindMembers, jen.Id(langMapMember).Map(jen.String()).String()) } - methods, funcs := p.serializationFuncs() + var methods []*codegen.Method + var funcs []*codegen.Function + ser, deser := p.serializationFuncs() + methods = append(methods, ser) + funcs = append(funcs, deser) methods = append(methods, p.singleTypeFuncs()...) methods = append(methods, p.funcs()...) methods = append(methods, p.commonMethods()...) @@ -421,7 +427,7 @@ func (p *FunctionalPropertyGenerator) singleTypeFuncs() []*codegen.Method { } if p.Kinds[0].Nilable { methods = append(methods, codegen.NewCommentedValueMethod( - p.PackageName(), + p.Package.Path(), hasMethod, p.StructName(), /*params=*/ nil, @@ -431,7 +437,7 @@ func (p *FunctionalPropertyGenerator) singleTypeFuncs() []*codegen.Method { )) } else { methods = append(methods, codegen.NewCommentedValueMethod( - p.PackageName(), + p.Package.Path(), hasMethod, p.StructName(), /*params=*/ nil, @@ -443,7 +449,7 @@ func (p *FunctionalPropertyGenerator) singleTypeFuncs() []*codegen.Method { // Get Method getComment := jen.Commentf("%s returns the value of this property. When %s returns false, %s will return any arbitrary value.", getMethod, hasMethod, getMethod) methods = append(methods, codegen.NewCommentedValueMethod( - p.PackageName(), + p.Package.Path(), p.getFnName(0), p.StructName(), /*params=*/ nil, @@ -465,7 +471,7 @@ func (p *FunctionalPropertyGenerator) singleTypeFuncs() []*codegen.Method { } if p.Kinds[0].Nilable { methods = append(methods, codegen.NewCommentedPointerMethod( - p.PackageName(), + p.Package.Path(), p.setFnName(0), p.StructName(), []jen.Code{jen.Id("v").Id(p.Kinds[0].ConcreteKind)}, @@ -478,7 +484,7 @@ func (p *FunctionalPropertyGenerator) singleTypeFuncs() []*codegen.Method { )) } else { methods = append(methods, codegen.NewCommentedPointerMethod( - p.PackageName(), + p.Package.Path(), p.setFnName(0), p.StructName(), []jen.Code{jen.Id("v").Id(p.Kinds[0].ConcreteKind)}, @@ -507,7 +513,7 @@ func (p *FunctionalPropertyGenerator) singleTypeFuncs() []*codegen.Method { } if p.Kinds[0].Nilable { methods = append(methods, codegen.NewCommentedPointerMethod( - p.PackageName(), + p.Package.Path(), p.clearMethodName(), p.StructName(), /*params=*/ nil, @@ -517,7 +523,7 @@ func (p *FunctionalPropertyGenerator) singleTypeFuncs() []*codegen.Method { )) } else { methods = append(methods, codegen.NewCommentedPointerMethod( - p.PackageName(), + p.Package.Path(), p.clearMethodName(), p.StructName(), /*params=*/ nil, @@ -562,7 +568,11 @@ func (p *FunctionalPropertyGenerator) multiTypeDef() *codegen.Struct { "%s is an iterator for a property. It is permitted to be one of multiple value types.", p.StructName(), ).Line().Comment("").Line().Add(explanation) } - methods, funcs := p.serializationFuncs() + var methods []*codegen.Method + var funcs []*codegen.Function + ser, deser := p.serializationFuncs() + methods = append(methods, ser) + funcs = append(funcs, deser) methods = append(methods, p.multiTypeFuncs()...) methods = append(methods, p.funcs()...) methods = append(methods, p.commonMethods()...) @@ -602,7 +612,7 @@ func (p *FunctionalPropertyGenerator) multiTypeFuncs() []*codegen.Method { ) } methods = append(methods, codegen.NewCommentedPointerMethod( - p.PackageName(), + p.Package.Path(), hasAnyMethodName, p.StructName(), /*params=*/ nil, @@ -626,7 +636,7 @@ func (p *FunctionalPropertyGenerator) multiTypeFuncs() []*codegen.Method { clearLine = append(clearLine, jen.Id(codegen.This()).Dot(langMapMember).Op("=").Nil()) } methods = append(methods, codegen.NewCommentedPointerMethod( - p.PackageName(), + p.Package.Path(), p.clearMethodName(), p.StructName(), /*params=*/ nil, @@ -649,7 +659,7 @@ func (p *FunctionalPropertyGenerator) multiTypeFuncs() []*codegen.Method { } if kind.Nilable { methods = append(methods, codegen.NewCommentedValueMethod( - p.PackageName(), + p.Package.Path(), p.isMethodName(i), p.StructName(), /*params=*/ nil, @@ -659,7 +669,7 @@ func (p *FunctionalPropertyGenerator) multiTypeFuncs() []*codegen.Method { )) } else { methods = append(methods, codegen.NewCommentedValueMethod( - p.PackageName(), + p.Package.Path(), p.isMethodName(i), p.StructName(), /*params=*/ nil, @@ -684,7 +694,7 @@ func (p *FunctionalPropertyGenerator) multiTypeFuncs() []*codegen.Method { } if kind.Nilable { methods = append(methods, codegen.NewCommentedPointerMethod( - p.PackageName(), + p.Package.Path(), p.setFnName(i), p.StructName(), []jen.Code{jen.Id("v").Id(kind.ConcreteKind)}, @@ -697,7 +707,7 @@ func (p *FunctionalPropertyGenerator) multiTypeFuncs() []*codegen.Method { )) } else { methods = append(methods, codegen.NewCommentedPointerMethod( - p.PackageName(), + p.Package.Path(), p.setFnName(i), p.StructName(), []jen.Code{jen.Id("v").Id(kind.ConcreteKind)}, @@ -715,7 +725,7 @@ func (p *FunctionalPropertyGenerator) multiTypeFuncs() []*codegen.Method { for i, kind := range p.Kinds { getComment := jen.Commentf("%s returns the value of this property. When %s returns false, %s will return an arbitrary value.", p.getFnName(i), p.isMethodName(i), p.getFnName(i)) methods = append(methods, codegen.NewCommentedValueMethod( - p.PackageName(), + p.Package.Path(), p.getFnName(i), p.StructName(), /*params=*/ nil, diff --git a/tools/exp/props/manager.go b/tools/exp/props/manager.go new file mode 100644 index 0000000..0346345 --- /dev/null +++ b/tools/exp/props/manager.go @@ -0,0 +1,289 @@ +package props + +import ( + "fmt" + "github.com/cjslep/activity/tools/exp/codegen" + "github.com/dave/jennifer/jen" + "strings" + "sync" +) + +const ( + managerName = "ASManager" + managerInitVarName = "mgr" +) + +func managerInitName() string { + return managerInitVarName +} + +// Generates the ActivityStreamManager that handles the static and/or dynamic +// loading of ActivityStream Core, Extended, and any extension types. +// +// This also provides interfaces to break the recursive/cyclic dependencies +// between properties and types. The previous version of this tool did not +// attempt to solve this problem, and instead just created one big and bloated +// library in order to avoid having to break the dependence. This version of +// the tool instead will generate interfaces for all of the required types. +// +// This means that developers will only ever need to interact with these +// interfaces, and could switch out using this implementation for another one of +// their own choosing. +// +// Also note that the manager links against all the implementations to generate +// a comprehensive registry. So while individual properties and types are able +// to be compiled separately, this generated output will link against all of +// these libraries. +// +// TODO: Improve the code generation to examine specific Golang code to +// determine which types to actually generate, and prune the unneeded types. +// This would cut down on the bloat on a per-program basis. +type ManagerGenerator struct { + pm PackageManager + tg []*TypeGenerator + fp []*FunctionalPropertyGenerator + nfp []*NonFunctionalPropertyGenerator + // Constructed at creation time. These rely on pointer stability, + // which should happen as none of these generators are treated as + // values. + tgManagedMethods map[*TypeGenerator]*managedMethods + fpManagedMethods map[*FunctionalPropertyGenerator]*managedMethods + nfpManagedMethods map[*NonFunctionalPropertyGenerator]*managedMethods + // Cached during manager code generation. + cacheOnce sync.Once + cachedStruct *codegen.Struct +} + +// managedMethods caches the specific methods and interfaces mapped to specific +// properties and types. +type managedMethods struct { + deserializor *codegen.Method + publicDeserializor *codegen.Method + // Interface for a manager + mIface *codegen.Interface + // Interface(s) for the property/type + ifaces []*codegen.Interface +} + +// NewManagerGenerator creates a new manager system. +// +// This generator should be constructed in the third pass, after types and +// property generators are all constructed. +func NewManagerGenerator(pm PackageManager, + tg []*TypeGenerator, + fp []*FunctionalPropertyGenerator, + nfp []*NonFunctionalPropertyGenerator) (*ManagerGenerator, error) { + mg := &ManagerGenerator{ + pm: pm, + tg: tg, + fp: fp, + nfp: nfp, + tgManagedMethods: make(map[*TypeGenerator]*managedMethods, len(tg)), + fpManagedMethods: make(map[*FunctionalPropertyGenerator]*managedMethods, len(fp)), + nfpManagedMethods: make(map[*NonFunctionalPropertyGenerator]*managedMethods, len(nfp)), + } + // Pass 1: Get all deserializor-like methods created. Further passes may + // rely on already having this data available in the manager. + for _, t := range tg { + mg.tgManagedMethods[t] = &managedMethods{ + // TODO: Figure out how to use this instead of the Kind abstraction + deserializor: mg.createPrivateDeserializationMethodForType(t), + publicDeserializor: nil, + mIface: nil, + } + } + for _, p := range fp { + mg.fpManagedMethods[p] = &managedMethods{ + deserializor: mg.createPrivateDeserializationMethodForFuncProperty(p), + publicDeserializor: nil, + mIface: nil, + } + } + for _, p := range nfp { + mg.nfpManagedMethods[p] = &managedMethods{ + deserializor: mg.createPrivateDeserializationMethodForNonFuncProperty(p), + publicDeserializor: nil, + mIface: nil, + } + } + // Pass 2: Inform the type of this ManagerGenerator so that it can keep + // all of its bookkeeping straight. + for _, t := range tg { + if e := t.apply(mg); e != nil { + return nil, e + } + } + // Pass 3: Populate interfaces of the types and properties, which relies + // on the first pass's data population. + for _, t := range tg { + publicPkg := t.Package().Parent().PublicPackage() + mg.tgManagedMethods[t].ifaces = []*codegen.Interface{t.toInterface(publicPkg)} + } + // TODO: Move these back to pass 1 + for _, p := range fp { + publicPkg := p.GetPackage().Parent().PublicPackage() + mg.fpManagedMethods[p].ifaces = []*codegen.Interface{p.toInterface(publicPkg)} + } + for _, p := range nfp { + publicPkg := p.GetPackage().Parent().PublicPackage() + mg.nfpManagedMethods[p].ifaces = p.toInterfaces(publicPkg) + } + return mg, nil +} + +func (m *ManagerGenerator) publicManager() *codegen.Struct { + // TODO + return nil +} + +func (m *ManagerGenerator) privatePackage() Package { + return m.pm.PrivatePackage() +} + +func (m *ManagerGenerator) getPrivateDeserializationMethodForType(t *TypeGenerator) *codegen.Method { + return m.tgManagedMethods[t].deserializor +} + +func (m *ManagerGenerator) getPrivateDeserializationMethodForProperty(p Property) *codegen.Method { + switch v := p.(type) { + case *FunctionalPropertyGenerator: + return m.fpManagedMethods[v].deserializor + case *NonFunctionalPropertyGenerator: + return m.nfpManagedMethods[v].deserializor + default: + panic("unknown property type") + } +} + +// PrivateManager creates a manager implementation that works with the concrete +// types required by the other PropertyGenerators and TypeGenerators for +// serializing and deserializing. +// +// Applications should NOT use this private manager as it will force them to +// rely on the concrete type implementations. The public version uses interfaces +// which isolates Application code from this specific go-fed implementation. If +// another alternative to go-fed were to be created, it could target those +// interfaces and be a drop-in replacement for everyone's applications. +// +// It is necessary to acheive isolation without cyclic dependencies: types and +// properties can each belong in their own package (if desired) to minimize +// binary bloat. This is theoretical until the below TODO is tackled because +// this concrete implementation relies on linking against all of the types and +// properties to behave correctly. There is no simple way around this. But I am +// preparing for the better solution here. +// +// TODO: Analyze a given program, and only generate the types and properties in +// use by the go program. +func (m *ManagerGenerator) PrivateManager() *codegen.Struct { + var methods []*codegen.Method + // TODO: Interface versions of these methods should also be present for + // these specific types. + for _, tg := range m.tgManagedMethods { + methods = append(methods, tg.deserializor) + } + for _, fp := range m.fpManagedMethods { + methods = append(methods, fp.deserializor) + } + for _, nfp := range m.nfpManagedMethods { + methods = append(methods, nfp.deserializor) + } + var functions []*codegen.Function + var members []jen.Code + s := codegen.NewStruct( + jen.Commentf(fmt.Sprintf("%s privately manages concrete types and deserializations for internal use by generated code. Application code should use the public version instead, which uses interfaces to abstract away the generated code and allow apps to not entirely rely on go-fed should they choose not to.", managerName)), + managerName, + methods, + functions, + members) + return s +} + +// createPrivateDeserializationMethodForType creates a new method for the +// private manager. +// +// TODO: Unify these three methods behind some kind of interface. +func (m *ManagerGenerator) createPrivateDeserializationMethodForType(tg *TypeGenerator) *codegen.Method { + dn := tg.deserializationFnName() + pkg := m.pm.PrivatePackage() + name := fmt.Sprintf("%s%s%s", dn, tg.Package().Name(), strings.Title(pkg.Name())) + return codegen.NewCommentedValueMethod( + pkg.Path(), + name, + managerName, + /*param=*/ nil, + []jen.Code{ + jen.Func().Params( + jen.Map(jen.String()).Interface(), + ).Params( + // TODO: Qualify this. + jen.Op("*").Id(tg.TypeName()), + jen.Error(), + ), + }, + []jen.Code{ + jen.Return( + jen.Qual(tg.Package().Path(), dn), + ), + }, + jen.Commentf("%s returns the deserialization method for the %q type in package %q", name, tg.TypeName(), tg.Package().Name())) +} + +// createPrivateDeserializationMethodForFuncProperty creates a new method for the +// private manager. +// +// TODO: Unify these three methods behind some kind of interface. +func (m *ManagerGenerator) createPrivateDeserializationMethodForFuncProperty(fp *FunctionalPropertyGenerator) *codegen.Method { + dn := fp.DeserializeFnName() + pkg := m.pm.PrivatePackage() + name := fmt.Sprintf("%s%s%s", dn, fp.Package.Name(), strings.Title(pkg.Name())) + return codegen.NewCommentedValueMethod( + pkg.Path(), + name, + managerName, + /*param=*/ nil, + []jen.Code{ + jen.Func().Params( + jen.Map(jen.String()).Interface(), + ).Params( + // TODO: Qualify this. + jen.Op("*").Id(fp.StructName()), + jen.Error(), + ), + }, + []jen.Code{ + jen.Return( + jen.Qual(fp.Package.Path(), dn), + ), + }, + jen.Commentf("%s returns the deserialization method for the %q functional property in package %q", name, fp.StructName(), fp.Package.Name())) +} + +// createPrivateDeserializationMethodForNonFuncProperty creates a new method for the +// private manager. +// +// TODO: Unify these three methods behind some kind of interface. +func (m *ManagerGenerator) createPrivateDeserializationMethodForNonFuncProperty(nfp *NonFunctionalPropertyGenerator) *codegen.Method { + dn := nfp.DeserializeFnName() + pkg := m.pm.PrivatePackage() + name := fmt.Sprintf("%s%s%s", dn, nfp.Package.Name(), strings.Title(pkg.Name())) + return codegen.NewCommentedValueMethod( + pkg.Path(), + name, + managerName, + /*param=*/ nil, + []jen.Code{ + jen.Func().Params( + jen.Map(jen.String()).Interface(), + ).Params( + // TODO: Qualify this. + jen.Op("*").Id(nfp.StructName()), + jen.Error(), + ), + }, + []jen.Code{ + jen.Return( + jen.Qual(nfp.Package.Path(), dn), + ), + }, + jen.Commentf("%s returns the deserialization method for the %q non-functional property in package %q", name, nfp.StructName(), nfp.Package.Name())) +} diff --git a/tools/exp/props/nonfuncprop.go b/tools/exp/props/nonfuncprop.go index 9094abd..044680a 100644 --- a/tools/exp/props/nonfuncprop.go +++ b/tools/exp/props/nonfuncprop.go @@ -20,7 +20,10 @@ type NonFunctionalPropertyGenerator struct { // NewNonFunctionalPropertyGenerator is a convenience constructor to create // NonFunctionalPropertyGenerators. -func NewNonFunctionalPropertyGenerator(pkg string, +// +// PropertyGenerators shoulf be in the first pass to construct, before types and +// other generators are constructed. +func NewNonFunctionalPropertyGenerator(pkg Package, name Identifier, kinds []Kind, hasNaturalLanguageMap bool) *NonFunctionalPropertyGenerator { @@ -34,12 +37,25 @@ func NewNonFunctionalPropertyGenerator(pkg string, } } +// toInterfaces creates the interface versions of the definitions generated. +func (p *NonFunctionalPropertyGenerator) toInterfaces(pkg Package) []*codegen.Interface { + s, t := p.Definitions() + return []*codegen.Interface{ + s.ToInterface(pkg.Path(), p.InterfaceName(), ""), + t.ToInterface(pkg.Path(), p.elementTypeGenerator().InterfaceName(), ""), + } +} + // Definitions produces the Go code definitions, which can generate their Go // implementations. The struct is the iterator for various values of the // property, which is defined by the type definition. func (p *NonFunctionalPropertyGenerator) Definitions() (*codegen.Struct, *codegen.Typedef) { p.cacheOnce.Do(func() { - methods, funcs := p.serializationFuncs() + var methods []*codegen.Method + var funcs []*codegen.Function + ser, deser := p.serializationFuncs() + methods = append(methods, ser) + funcs = append(funcs, deser) methods = append(methods, p.funcs()...) property := codegen.NewTypedef( jen.Commentf("%s is the non-functional property %q. It is permitted to have one or more values, and of different value types.", p.StructName(), p.PropertyName()), @@ -90,7 +106,7 @@ func (p *NonFunctionalPropertyGenerator) funcs() []*codegen.Method { prependMethodName := fmt.Sprintf("%s%s", prependMethod, p.kindCamelName(i)) methods = append(methods, codegen.NewCommentedPointerMethod( - p.PackageName(), + p.Package.Path(), prependMethodName, p.StructName(), []jen.Code{jen.Id("v").Id(kind.ConcreteKind)}, @@ -108,7 +124,7 @@ func (p *NonFunctionalPropertyGenerator) funcs() []*codegen.Method { appendMethodName := fmt.Sprintf("%s%s", appendMethod, p.kindCamelName(i)) methods = append(methods, codegen.NewCommentedPointerMethod( - p.PackageName(), + p.Package.Path(), appendMethodName, p.StructName(), []jen.Code{jen.Id("v").Id(kind.ConcreteKind)}, @@ -140,7 +156,7 @@ func (p *NonFunctionalPropertyGenerator) funcs() []*codegen.Method { // Remove Method methods = append(methods, codegen.NewCommentedPointerMethod( - p.PackageName(), + p.Package.Path(), removeMethod, p.StructName(), []jen.Code{jen.Id("idx").Int()}, @@ -176,7 +192,7 @@ func (p *NonFunctionalPropertyGenerator) funcs() []*codegen.Method { // Len Method methods = append(methods, codegen.NewCommentedValueMethod( - p.PackageName(), + p.Package.Path(), lenMethod, p.StructName(), /*params=*/ nil, @@ -192,7 +208,7 @@ func (p *NonFunctionalPropertyGenerator) funcs() []*codegen.Method { // Swap Method methods = append(methods, codegen.NewCommentedValueMethod( - p.PackageName(), + p.Package.Path(), swapMethod, p.StructName(), []jen.Code{ @@ -213,7 +229,7 @@ func (p *NonFunctionalPropertyGenerator) funcs() []*codegen.Method { // Less Method methods = append(methods, codegen.NewCommentedValueMethod( - p.PackageName(), + p.Package.Path(), lessMethod, p.StructName(), []jen.Code{ @@ -235,7 +251,7 @@ func (p *NonFunctionalPropertyGenerator) funcs() []*codegen.Method { // Kind Method methods = append(methods, codegen.NewCommentedValueMethod( - p.PackageName(), + p.Package.Path(), kindIndexMethod, p.StructName(), []jen.Code{jen.Id("idx").Int()}, @@ -252,52 +268,49 @@ func (p *NonFunctionalPropertyGenerator) funcs() []*codegen.Method { // serializationFuncs produces the Methods and Functions needed for a // NonFunctional property to be serialized and deserialized to and from an // encoding. -func (p *NonFunctionalPropertyGenerator) serializationFuncs() ([]*codegen.Method, []*codegen.Function) { - serialize := []*codegen.Method{ - codegen.NewCommentedValueMethod( - p.PackageName(), - p.serializeFnName(), - p.StructName(), - /*params=*/ nil, - []jen.Code{jen.Interface(), jen.Error()}, - []jen.Code{ - jen.Id("s").Op(":=").Make( - jen.Index().Interface(), - jen.Lit(0), - jen.Len(jen.Id(codegen.This())), - ), - jen.For( +func (p *NonFunctionalPropertyGenerator) serializationFuncs() (*codegen.Method, *codegen.Function) { + serialize := codegen.NewCommentedValueMethod( + p.Package.Path(), + p.serializeFnName(), + p.StructName(), + /*params=*/ nil, + []jen.Code{jen.Interface(), jen.Error()}, + []jen.Code{ + jen.Id("s").Op(":=").Make( + jen.Index().Interface(), + jen.Lit(0), + jen.Len(jen.Id(codegen.This())), + ), + jen.For( + jen.List( + jen.Id("_"), + jen.Id("iterator"), + ).Op(":=").Range().Id(codegen.This()), + ).Block( + jen.If( jen.List( - jen.Id("_"), - jen.Id("iterator"), - ).Op(":=").Range().Id(codegen.This()), + jen.Id("b"), + jen.Err(), + ).Op(":=").Id("iterator").Dot(serializeIteratorMethod).Call(), + jen.Err().Op("!=").Nil(), ).Block( - jen.If( - jen.List( - jen.Id("b"), - jen.Err(), - ).Op(":=").Id("iterator").Dot(serializeIteratorMethod).Call(), - jen.Err().Op("!=").Nil(), - ).Block( - jen.Return( - jen.Id("s"), - jen.Err(), - ), - ).Else().Block( - jen.Id("s").Op("=").Append( - jen.Id("s"), - jen.Id("b"), - ), + jen.Return( + jen.Id("s"), + jen.Err(), + ), + ).Else().Block( + jen.Id("s").Op("=").Append( + jen.Id("s"), + jen.Id("b"), ), ), - jen.Return( - jen.Id("s"), - jen.Nil(), - ), - }, - jen.Commentf("%s converts this into an interface representation suitable for marshalling into a text or binary format.", p.serializeFnName()), - ), - } + ), + jen.Return( + jen.Id("s"), + jen.Nil(), + ), + }, + jen.Commentf("%s converts this into an interface representation suitable for marshalling into a text or binary format.", p.serializeFnName())) deserializeFn := func(variable string) jen.Code { return jen.If( jen.List( @@ -321,51 +334,48 @@ func (p *NonFunctionalPropertyGenerator) serializationFuncs() ([]*codegen.Method ), ) } - deserialize := []*codegen.Function{ - codegen.NewCommentedFunction( - p.PackageName(), - p.DeserializeFnName(), - []jen.Code{jen.Id("m").Map(jen.String()).Interface()}, - []jen.Code{jen.Id(p.StructName()), jen.Error()}, - []jen.Code{ - jen.Var().Id(codegen.This()).Index().Id(p.iteratorTypeName().CamelName), + deserialize := codegen.NewCommentedFunction( + p.Package.Path(), + p.DeserializeFnName(), + []jen.Code{jen.Id("m").Map(jen.String()).Interface()}, + []jen.Code{jen.Id(p.StructName()), jen.Error()}, + []jen.Code{ + jen.Var().Id(codegen.This()).Index().Id(p.iteratorTypeName().CamelName), + jen.If( + jen.List( + jen.Id("i"), + jen.Id("ok"), + ).Op(":=").Id("m").Index( + jen.Lit(p.PropertyName()), + ), + jen.Id("ok"), + ).Block( jen.If( jen.List( - jen.Id("i"), + jen.Id("list"), jen.Id("ok"), - ).Op(":=").Id("m").Index( - jen.Lit(p.PropertyName()), + ).Op(":=").Id("i").Assert( + jen.Index().Interface(), ), jen.Id("ok"), ).Block( - jen.If( + jen.For( jen.List( - jen.Id("list"), - jen.Id("ok"), - ).Op(":=").Id("i").Assert( - jen.Index().Interface(), - ), - jen.Id("ok"), + jen.Id("_"), + jen.Id("iterator"), + ).Op(":=").Range().Id("list"), ).Block( - jen.For( - jen.List( - jen.Id("_"), - jen.Id("iterator"), - ).Op(":=").Range().Id("list"), - ).Block( - deserializeFn("iterator"), - ), - ).Else().Block( - deserializeFn("i"), + deserializeFn("iterator"), ), + ).Else().Block( + deserializeFn("i"), ), - jen.Return( - jen.Id(codegen.This()), - jen.Nil(), - ), - }, - jen.Commentf("%s creates a %q property from an interface representation that has been unmarshalled from a text or binary format.", p.DeserializeFnName(), p.PropertyName()), - ), - } + ), + jen.Return( + jen.Id(codegen.This()), + jen.Nil(), + ), + }, + jen.Commentf("%s creates a %q property from an interface representation that has been unmarshalled from a text or binary format.", p.DeserializeFnName(), p.PropertyName())) return serialize, deserialize } diff --git a/tools/exp/props/pkg.go b/tools/exp/props/pkg.go new file mode 100644 index 0000000..5d96d68 --- /dev/null +++ b/tools/exp/props/pkg.go @@ -0,0 +1,79 @@ +package props + +import ( + "fmt" + "strings" +) + +// PackageManager manages the path and names of a package consisting of a public +// and a private portion. +type PackageManager struct { + root string + public string + private string +} + +func NewPackageManager(root string) *PackageManager { + return &PackageManager{ + root: root, + public: "", + private: "internal", + } +} + +// PublicPackage returns the public package. +func (p *PackageManager) PublicPackage() Package { + return p.toPackage(p.public, true) +} + +// PrivatePackage returns the private package. +func (p *PackageManager) PrivatePackage() Package { + return p.toPackage(p.private, false) +} + +// Sub creates a PackageManager clone that manages a subdirectory. +func (p *PackageManager) Sub(name string) *PackageManager { + return &PackageManager{ + root: fmt.Sprintf("%s/%s", p.root, name), + public: p.public, + private: p.private, + } +} + +func (p *PackageManager) toPackage(suffix string, public bool) Package { + path := p.root + if len(suffix) > 0 { + path = strings.Join([]string{p.root, suffix}, "/") + } + s := strings.Split(path, "/") + name := s[len(s)-1] + return Package{ + path: path, + name: name, + isPublic: public, + parent: p, + } +} + +type Package struct { + path string + name string + isPublic bool + parent *PackageManager +} + +func (p Package) Path() string { + return p.path +} + +func (p Package) Name() string { + return p.name +} + +func (p Package) IsPublic() bool { + return p.isPublic +} + +func (p Package) Parent() *PackageManager { + return p.parent +} diff --git a/tools/exp/props/property.go b/tools/exp/props/property.go index 3145b87..c6ca6d9 100644 --- a/tools/exp/props/property.go +++ b/tools/exp/props/property.go @@ -59,12 +59,13 @@ type Identifier struct { // deserialize such types, compare the types, and other meta-information to use // during Go code generation. type Kind struct { - Name Identifier - ConcreteKind string - Nilable bool - SerializeFn *codegen.Function - DeserializeFn *codegen.Function - LessFn *codegen.Function + Name Identifier + ConcreteKind string + Nilable bool + // Expected to be a jen.Qual + SerializeFn *jen.Statement + DeserializeFn *jen.Statement + LessFn *jen.Statement } // PropertyGenerator is a common base struct used in both Functional and @@ -77,15 +78,15 @@ type Kind struct { // TODO: Make this type private type PropertyGenerator struct { // TODO: Make these private - Package string + Package Package Name Identifier Kinds []Kind HasNaturalLanguageMap bool asIterator bool } -// PackageName returns the name of the package for the property to be generated. -func (p *PropertyGenerator) PackageName() string { +// GetPackage gets this property's Package. +func (p *PropertyGenerator) GetPackage() Package { return p.Package } @@ -95,7 +96,7 @@ func (p *PropertyGenerator) PackageName() string { // The name parameter must match the LowerName of an Identifier. // // This feels very hacky. -func (p *PropertyGenerator) SetKindFns(name string, ser, deser, less *codegen.Function) error { +func (p *PropertyGenerator) SetKindFns(name string, ser, deser, less *jen.Statement) error { for i, kind := range p.Kinds { if kind.Name.LowerName == name { if kind.SerializeFn != nil || kind.DeserializeFn != nil || kind.LessFn != nil { @@ -120,6 +121,11 @@ func (p *PropertyGenerator) StructName() string { return fmt.Sprintf("%sProperty", p.Name.CamelName) } +// InterfaceNAme returns the interface name of the property type. +func (p *PropertyGenerator) InterfaceName() string { + return fmt.Sprintf("%sInterface", p.StructName()) +} + // PropertyName returns the name of this property, as defined in // specifications. It is not suitable for use in generated code function // identifiers. @@ -201,7 +207,7 @@ func (p *PropertyGenerator) clearMethodName() string { func (p *PropertyGenerator) commonMethods() []*codegen.Method { return []*codegen.Method{ codegen.NewCommentedValueMethod( - p.PackageName(), + p.Package.Path(), nameMethod, p.StructName(), /*params=*/ nil, diff --git a/tools/exp/props/type.go b/tools/exp/props/type.go index 682e58f..7a51533 100644 --- a/tools/exp/props/type.go +++ b/tools/exp/props/type.go @@ -27,7 +27,7 @@ const ( // TypeInterface returns the Type Interface that is needed for ActivityStream // types to compile for methods dealing with extending, in the inheritance // sense. -func TypeInterface(pkg string) *codegen.Interface { +func TypeInterface(pkg Package) *codegen.Interface { comment := fmt.Sprintf("%s represents an ActivityStreams type.", typeInterfaceName) funcs := []codegen.FunctionSignature{ { @@ -37,28 +37,30 @@ func TypeInterface(pkg string) *codegen.Interface { Comment: fmt.Sprintf("%s returns the ActivityStreams type name.", typeNameMethod), }, } - return codegen.NewInterface(pkg, typeInterfaceName, funcs, comment) + return codegen.NewInterface(pkg.Path(), typeInterfaceName, funcs, comment) } // Property represents a property of an ActivityStreams type. type Property interface { - PackageName() string + GetPackage() Package PropertyName() string StructName() string - SetKindFns(name string, ser, deser, less *codegen.Function) error + SetKindFns(name string, ser, deser, less *jen.Statement) error DeserializeFnName() string } // TypeGenerator represents an ActivityStream type definition to generate in Go. type TypeGenerator struct { - packageName string + pkg Package typeName string comment string properties map[string]Property withoutProperties map[string]Property + rangeProperties []Property extends []*TypeGenerator disjoint []*TypeGenerator extendedBy []*TypeGenerator + m *ManagerGenerator cacheOnce sync.Once cachedStruct *codegen.Struct } @@ -67,23 +69,32 @@ type TypeGenerator struct { // or extension type. It will return an error if there are multiple properties // have the same Name. // +// The TypeGenerator should be in the second pass to construct, relying on the +// fact that properties have already been constructed. +// // The extends and disjoint parameters are allowed to be nil. These lists must -// also have unique (non-duplicated) elements. +// also have unique (non-duplicated) elements. Note that the disjoint entries +// will be set up bi-directionally properly; no need to go back to an existing +// TypeGenerator to set up the link correctly. +// +// The rangeProperties list is allowed to be nil. Any passed in will properly +// have their SetKindFns bookkeeping done. // // All TypeGenerators must be created before the Definition method is called, to // ensure that type extension, in the inheritence sense, is properly set up. -// Additionally, all properties whose range is this type should have their -// SetKindFns method called with this TypeGenerator's KindSerializationFuncs for -// all code generation to correctly reference each other. -func NewTypeGenerator(packageName, typeName, comment string, - properties, withoutProperties []Property, +// +// A ManagerGenerator must be created with this type before Definition is +// called, to ensure that the serialization functions are properly set up. +func NewTypeGenerator(pkg Package, typeName, comment string, + properties, withoutProperties, rangeProperties []Property, extends, disjoint []*TypeGenerator) (*TypeGenerator, error) { t := &TypeGenerator{ - packageName: packageName, + pkg: pkg, typeName: typeName, comment: comment, properties: make(map[string]Property, len(properties)), withoutProperties: make(map[string]Property, len(withoutProperties)), + rangeProperties: rangeProperties, extends: extends, disjoint: disjoint, } @@ -103,12 +114,34 @@ func NewTypeGenerator(packageName, typeName, comment string, for _, ext := range extends { ext.extendedBy = append(ext.extendedBy, t) } + // Complete doubly-linked disjoint types. + for _, disj := range disjoint { + disj.disjoint = append(disj.disjoint, t) + } return t, nil } -// AddDisjoint adds another TypeGenerator that is disjoint to this one. -func (t *TypeGenerator) AddDisjoint(o *TypeGenerator) { - t.disjoint = append(t.disjoint, o) +// apply propagates the manager's functions referring to this type's +// implementation as if this type were a Kind. +// +// Prepares to use the manager for the Definition generation. +func (t *TypeGenerator) apply(m *ManagerGenerator) error { + t.m = m + // Set up Kind functions + ser := jen.Qual(t.Package().Path(), t.serializationFnName()) + deser := jen.Qual(m.privatePackage().Path(), m.getPrivateDeserializationMethodForType(t).Name()) + less := jen.Qual(t.Package().Path(), t.lessFnName()) + for _, p := range t.rangeProperties { + if e := p.SetKindFns(t.TypeName(), ser, deser, less); e != nil { + return e + } + } + return nil +} + +// Package gets this TypeGenerator's Package. +func (t *TypeGenerator) Package() Package { + return t.pkg } // Comment returns the comment for this type. @@ -121,6 +154,11 @@ func (t *TypeGenerator) TypeName() string { return t.typeName } +// InterfaceName returns the interface name for this type. +func (t *TypeGenerator) InterfaceName() string { + return fmt.Sprintf("%sInterface", t.TypeName()) +} + // Extends returns the generators of types that this ActivityStreams type // extends from. func (t *TypeGenerator) Extends() []*TypeGenerator { @@ -169,12 +207,37 @@ func (t *TypeGenerator) disjointWithFnName() string { return fmt.Sprintf("%s%s", t.TypeName(), disjointWithMethod) } +// deserializationFnName determines the name of the deserialize function for +// this type. +func (t *TypeGenerator) deserializationFnName() string { + return fmt.Sprintf("%s%s", deserializeFnName, t.TypeName()) +} + +// serializationFnName determines the name of the serialize function for this +// type. +func (t *TypeGenerator) serializationFnName() string { + return fmt.Sprintf("%s%s", serializeMethodName, t.TypeName()) +} + +// lessFnName determines the name of the less function for this type. +func (t *TypeGenerator) lessFnName() string { + return fmt.Sprintf("%s%s", lessFnName, t.TypeName()) +} + +// toInterface creates the interface version of the definition generated. +// +// Requires apply to have already been called. +func (t *TypeGenerator) toInterface(pkg Package) *codegen.Interface { + s := t.Definition() + return s.ToInterface(pkg.Path(), t.InterfaceName(), "") +} + // Definition generates the golang code for this ActivityStreams type. func (t *TypeGenerator) Definition() *codegen.Struct { t.cacheOnce.Do(func() { members := t.members() m := t.serializationMethod() - ser, deser, less := t.KindSerializationFuncs() + ser, deser, less := t.kindSerializationFuncs() extendsFn, extendsMethod := t.extendsDefinition() t.cachedStruct = codegen.NewStruct( jen.Commentf(t.Comment()), @@ -241,7 +304,7 @@ func (t *TypeGenerator) members() (members []jen.Code) { // Convert to jen.Code members = make([]jen.Code, 0, len(p)) for _, property := range sortedMembers { - members = append(members, jen.Id(strings.Title(property.PropertyName())).Qual(property.PackageName(), property.StructName())) + members = append(members, jen.Id(strings.Title(property.PropertyName())).Qual(property.GetPackage().Path(), property.StructName())) } return } @@ -250,7 +313,7 @@ func (t *TypeGenerator) members() (members []jen.Code) { // type name. func (t *TypeGenerator) nameDefinition() *codegen.Method { return codegen.NewCommentedValueMethod( - t.packageName, + t.pkg.Path(), typeNameMethod, t.TypeName(), /*params=*/ nil, @@ -300,14 +363,14 @@ func (t *TypeGenerator) extendsDefinition() (*codegen.Function, *codegen.Method) jen.Return(jen.False())} } f := codegen.NewCommentedFunction( - t.packageName, + t.pkg.Path(), t.extendsFnName(), []jen.Code{jen.Id("other").Id(typeInterfaceName)}, []jen.Code{jen.Bool()}, impl, jen.Commentf("%s returns true if the %s type extends from the other type.", t.extendsFnName(), t.TypeName())) m := codegen.NewCommentedValueMethod( - t.packageName, + t.pkg.Path(), extendingMethod, t.TypeName(), []jen.Code{jen.Id("other").Id(typeInterfaceName)}, @@ -356,7 +419,7 @@ func (t *TypeGenerator) extendedByDefinition() *codegen.Function { jen.Return(jen.False())} } return codegen.NewCommentedFunction( - t.packageName, + t.pkg.Path(), t.extendedByFnName(), []jen.Code{jen.Id("other").Id(typeInterfaceName)}, []jen.Code{jen.Bool()}, @@ -402,7 +465,7 @@ func (t *TypeGenerator) disjointWithDefinition() *codegen.Function { jen.Return(jen.False())} } return codegen.NewCommentedFunction( - t.packageName, + t.pkg.Path(), t.disjointWithFnName(), []jen.Code{jen.Id("other").Id(typeInterfaceName)}, []jen.Code{jen.Bool()}, @@ -414,7 +477,7 @@ func (t *TypeGenerator) disjointWithDefinition() *codegen.Function { // a property. func (t *TypeGenerator) serializationMethod() (ser *codegen.Method) { ser = codegen.NewCommentedValueMethod( - t.packageName, + t.pkg.Path(), serializeMethodName, t.TypeName(), /*params=*/ nil, @@ -427,13 +490,12 @@ func (t *TypeGenerator) serializationMethod() (ser *codegen.Method) { return } -// KindSerializationFuncs returns free function references that can be used to +// kindSerializationFuncs returns free function references that can be used to // treat a TypeGenerator as another property's Kind. -func (t *TypeGenerator) KindSerializationFuncs() (ser, deser, less *codegen.Function) { - serName := fmt.Sprintf("%s%s", serializeMethodName, t.TypeName()) +func (t *TypeGenerator) kindSerializationFuncs() (ser, deser, less *codegen.Function) { ser = codegen.NewCommentedFunction( - t.packageName, - serName, + t.pkg.Path(), + t.serializationFnName(), []jen.Code{jen.Id("s").Id(t.TypeName())}, []jen.Code{jen.Interface(), jen.Error()}, []jen.Code{ @@ -441,16 +503,16 @@ func (t *TypeGenerator) KindSerializationFuncs() (ser, deser, less *codegen.Func jen.Id("s").Dot(serializeMethodName).Call(), ), }, - jen.Commentf("%s calls %s on the %s type.", serName, serializeMethodName, t.TypeName())) - deserName := fmt.Sprintf("%s%s", deserializeFnName, t.TypeName()) + jen.Commentf("%s calls %s on the %s type.", t.serializationFnName(), serializeMethodName, t.TypeName())) deserCode := jen.Empty() for name, prop := range t.allProperties() { + deserMethod := t.m.getPrivateDeserializationMethodForProperty(prop) deserCode = deserCode.Add( jen.If( jen.List( jen.Id("p"), jen.Err(), - ).Op(":=").Qual(prop.PackageName(), prop.DeserializeFnName()).Call(jen.Id("m")), + ).Op(":=").Add(deserMethod.Call(managerInitName())).Call(jen.Id("m")), jen.Err().Op("!=").Nil(), ).Block( jen.Return(jen.Nil(), jen.Err()), @@ -459,8 +521,8 @@ func (t *TypeGenerator) KindSerializationFuncs() (ser, deser, less *codegen.Func ).Line()) } deser = codegen.NewCommentedFunction( - t.packageName, - deserName, + t.pkg.Path(), + t.deserializationFnName(), []jen.Code{jen.Id("m").Map(jen.String()).Interface()}, []jen.Code{jen.Op("*").Id(t.TypeName()), jen.Error()}, []jen.Code{ @@ -468,11 +530,10 @@ func (t *TypeGenerator) KindSerializationFuncs() (ser, deser, less *codegen.Func deserCode, jen.Return(jen.Id(codegen.This()), jen.Nil()), }, - jen.Commentf("%s creates a %s from a map representation that has been unmarshalled from a text or binary format.", deserName, t.TypeName())) - lessName := fmt.Sprintf("%s%s", lessFnName, t.TypeName()) + jen.Commentf("%s creates a %s from a map representation that has been unmarshalled from a text or binary format.", t.deserializationFnName(), t.TypeName())) less = codegen.NewCommentedFunction( - t.packageName, - lessName, + t.pkg.Path(), + t.lessFnName(), []jen.Code{ jen.Id("i"), jen.Id("j").Op("*").Id(t.TypeName()), @@ -482,6 +543,6 @@ func (t *TypeGenerator) KindSerializationFuncs() (ser, deser, less *codegen.Func // TODO jen.Commentf("TODO: Less code for %s", t.TypeName()), }, - jen.Commentf("%s computes which %s is lesser, with an arbitrary but stable determination", lessName, t.TypeName())) + jen.Commentf("%s computes which %s is lesser, with an arbitrary but stable determination", t.lessFnName(), t.TypeName())) return } diff --git a/tools/exp/rdf/data.go b/tools/exp/rdf/data.go index ecc9046..23f1776 100644 --- a/tools/exp/rdf/data.go +++ b/tools/exp/rdf/data.go @@ -153,6 +153,10 @@ func (v VocabularyType) GetName() string { return v.Name } +func (v VocabularyType) TypeName() string { + return v.Name +} + func (v *VocabularyType) SetURI(s string) error { var e error v.URI, e = url.Parse(s) @@ -203,6 +207,10 @@ func (v VocabularyProperty) GetName() string { return v.Name } +func (v VocabularyProperty) PropertyName() string { + return v.Name +} + func (v *VocabularyProperty) SetURI(s string) error { var e error v.URI, e = url.Parse(s)