diff --git a/tools/exp/gen/docs.go b/tools/exp/gen/docs.go index 734de59..f3cf8d3 100644 --- a/tools/exp/gen/docs.go +++ b/tools/exp/gen/docs.go @@ -120,7 +120,13 @@ func VocabPackageComment(pkgName, vocabName string) string { "functional property with \"Next\" and \"Previous\" methods. "+ "Applications should not use the \"KindIndex\" methods as it "+ "is a comparison mechanism only for those looking to write an "+ - "alternate implementation of this library.", + "alternate implementation of this library.\n\n"+ + "Types, functional properties, and non-functional properties "+ + "are not designed for concurrent usage by two or more "+ + "goroutines. Also, certain methods on a non-functional "+ + "property will invalidate iterators and possibly cause "+ + "unexpected behaviors. To avoid this, re-obtain an iterator "+ + "after modifying a non-functional property.", pkgName, vocabName)) } diff --git a/tools/exp/gen/funcprop.go b/tools/exp/gen/funcprop.go index a46de10..c0e8ad0 100644 --- a/tools/exp/gen/funcprop.go +++ b/tools/exp/gen/funcprop.go @@ -305,7 +305,7 @@ func (p *FunctionalPropertyGenerator) serializationFuncs() (*codegen.Method, *co jen.Id(codegen.This()).Dot(unknownMemberName), jen.Nil(), )}, - fmt.Sprintf("%s converts this into an interface representation suitable for marshalling into a text or binary format.", p.serializeFnName())) + fmt.Sprintf("%s converts this into an interface representation suitable for marshalling into a text or binary format. Applications should not need this function as most typical use cases serialize types instead of individual properties. It is exposed for alternatives to go-fed implementations to use.", p.serializeFnName())) valueDeserializeFns := jen.Empty() typeDeserializeFns := jen.Empty() foundValue := false @@ -423,6 +423,10 @@ func (p *FunctionalPropertyGenerator) singleTypeDef() *codegen.Struct { if p.hasNaturalLanguageMap { kindMembers = append(kindMembers, jen.Id(langMapMember).Map(jen.String()).String()) } + if p.asIterator { + kindMembers = append(kindMembers, jen.Id(myIndexMemberName).Int()) + kindMembers = append(kindMembers, jen.Id(parentMemberName).Qual(p.GetPublicPackage().Path(), p.parentTypeInterfaceName())) + } var methods []*codegen.Method var funcs []*codegen.Function ser, deser := p.serializationFuncs() @@ -633,7 +637,7 @@ func (p *FunctionalPropertyGenerator) singleTypeFuncs() []*codegen.Method { jen.Return(lessCode), ), }, - fmt.Sprintf("%s compares two instances of this property with an arbitrary but stable comparison.", compareLessMethod), + fmt.Sprintf("%s compares two instances of this property with an arbitrary but stable comparison. Applications should not use this because it is only meant to help alternative implementations to go-fed to be able to normalize nonfunctional properties.", compareLessMethod), )) return methods } @@ -668,6 +672,8 @@ func (p *FunctionalPropertyGenerator) multiTypeDef() *codegen.Struct { p.StructName(), explanation, ) + kindMembers = append(kindMembers, jen.Id(myIndexMemberName).Int()) + kindMembers = append(kindMembers, jen.Id(parentMemberName).Qual(p.GetPublicPackage().Path(), p.parentTypeInterfaceName())) } var methods []*codegen.Method var funcs []*codegen.Function @@ -894,7 +900,7 @@ func (p *FunctionalPropertyGenerator) multiTypeFuncs() []*codegen.Method { lessCode, jen.Return(jen.False()), }, - fmt.Sprintf("%s compares two instances of this property with an arbitrary but stable comparison.", compareLessMethod), + fmt.Sprintf("%s compares two instances of this property with an arbitrary but stable comparison. Applications should not use this because it is only meant to help alternative implementations to go-fed to be able to normalize nonfunctional properties.", compareLessMethod), )) return methods } diff --git a/tools/exp/gen/nonfuncprop.go b/tools/exp/gen/nonfuncprop.go index 49f1bff..8353719 100644 --- a/tools/exp/gen/nonfuncprop.go +++ b/tools/exp/gen/nonfuncprop.go @@ -7,10 +7,6 @@ import ( "sync" ) -const ( - atMethodName = "At" -) - // NonFunctionalPropertyGenerator produces Go code for properties that can have // more than one value. The resulting property is a type that is a list of // iterators; each iterator is a concrete struct type. The property can be @@ -102,12 +98,18 @@ func (p *NonFunctionalPropertyGenerator) funcs() []*codegen.Method { less := jen.Empty() for i, kind := range p.kinds { dict := jen.Dict{ - jen.Id(p.memberName(i)): jen.Id("v"), + jen.Id(parentMemberName): jen.Id(codegen.This()), + jen.Id(p.memberName(i)): jen.Id("v"), } if !kind.Nilable { dict[jen.Id(p.hasMemberName(i))] = jen.True() } // Prepend Method + prependDict := jen.Dict{} + for k, v := range dict { + prependDict[k] = v + } + prependDict[jen.Id(myIndexMemberName)] = jen.Lit(0) prependMethodName := fmt.Sprintf("%s%s", prependMethod, p.kindCamelName(i)) methods = append(methods, codegen.NewCommentedPointerMethod( @@ -119,13 +121,27 @@ func (p *NonFunctionalPropertyGenerator) funcs() []*codegen.Method { []jen.Code{ jen.Op("*").Id(codegen.This()).Op("=").Append( jen.Index().Op("*").Id(p.iteratorTypeName().CamelName).Values( - jen.Values(dict), + jen.Values(prependDict), ), jen.Op("*").Id(codegen.This()).Op("..."), ), + jen.For( + jen.Id("i").Op(":=").Lit(1), + jen.Id("i").Op("<").Id(codegen.This()).Dot(lenMethod).Call(), + jen.Id("i").Op("++"), + ).Block( + jen.Parens( + jen.Op("*").Id(codegen.This()), + ).Index(jen.Id("i")).Dot(myIndexMemberName).Op("=").Id("i"), + ), }, - fmt.Sprintf("%s prepends a %s value to the front of a list of the property %q.", prependMethodName, kind.Name.LowerName, p.PropertyName()))) + fmt.Sprintf("%s prepends a %s value to the front of a list of the property %q. Invalidates all iterators.", prependMethodName, kind.Name.LowerName, p.PropertyName()))) // Append Method + appendDict := jen.Dict{} + for k, v := range dict { + appendDict[k] = v + } + appendDict[jen.Id(myIndexMemberName)] = jen.Id(codegen.This()).Dot(lenMethod).Call() appendMethodName := fmt.Sprintf("%s%s", appendMethod, p.kindCamelName(i)) methods = append(methods, codegen.NewCommentedPointerMethod( @@ -138,12 +154,17 @@ func (p *NonFunctionalPropertyGenerator) funcs() []*codegen.Method { jen.Op("*").Id(codegen.This()).Op("=").Append( jen.Op("*").Id(codegen.This()), jen.Op("&").Id(p.iteratorTypeName().CamelName).Values( - dict, + appendDict, ), ), }, - fmt.Sprintf("%s appends a %s value to the back of a list of the property %q", appendMethodName, kind.Name.LowerName, p.PropertyName()))) + fmt.Sprintf("%s appends a %s value to the back of a list of the property %q. Invalidates iterators that are traversing using %s.", appendMethodName, kind.Name.LowerName, p.PropertyName(), prevMethod))) // Set Method + setDict := jen.Dict{} + for k, v := range dict { + setDict[k] = v + } + setDict[jen.Id(myIndexMemberName)] = jen.Id("idx") setMethodName := p.setFnName(i) methods = append(methods, codegen.NewCommentedPointerMethod( @@ -153,11 +174,12 @@ func (p *NonFunctionalPropertyGenerator) funcs() []*codegen.Method { []jen.Code{jen.Id("idx").Int(), jen.Id("v").Add(kind.ConcreteKind)}, /*ret=*/ nil, []jen.Code{ + jen.Parens(jen.Op("*").Id(codegen.This())).Index(jen.Id("idx")).Dot(parentMemberName).Op("=").Nil(), jen.Parens(jen.Op("*").Id(codegen.This())).Index(jen.Id("idx")).Op("=").Op("&").Id(p.iteratorTypeName().CamelName).Values( - dict, + setDict, ), }, - fmt.Sprintf("%s sets a %s value to be at the specified index for the property %q. Panics if the index is out of bounds.", setMethodName, kind.Name.LowerName, p.PropertyName()))) + fmt.Sprintf("%s sets a %s value to be at the specified index for the property %q. Panics if the index is out of bounds. Invalidates all iterators.", setMethodName, kind.Name.LowerName, p.PropertyName()))) // Less logic if i > 0 { less.Else() @@ -183,11 +205,22 @@ func (p *NonFunctionalPropertyGenerator) funcs() []*codegen.Method { jen.Op("*").Id(codegen.This()).Op("=").Append( jen.Index().Op("*").Id(p.iteratorTypeName().CamelName).Values( jen.Values(jen.Dict{ - jen.Id(iriMember): jen.Id("v"), + jen.Id(iriMember): jen.Id("v"), + jen.Id(parentMemberName): jen.Id(codegen.This()), + jen.Id(myIndexMemberName): jen.Lit(0), }), ), jen.Op("*").Id(codegen.This()).Op("..."), ), + jen.For( + jen.Id("i").Op(":=").Lit(1), + jen.Id("i").Op("<").Id(codegen.This()).Dot(lenMethod).Call(), + jen.Id("i").Op("++"), + ).Block( + jen.Parens( + jen.Op("*").Id(codegen.This()), + ).Index(jen.Id("i")).Dot(myIndexMemberName).Op("=").Id("i"), + ), }, fmt.Sprintf("%sIRI prepends an IRI value to the front of a list of the property %q.", prependMethod, p.PropertyName()))) methods = append(methods, @@ -202,7 +235,9 @@ func (p *NonFunctionalPropertyGenerator) funcs() []*codegen.Method { jen.Op("*").Id(codegen.This()), jen.Op("&").Id(p.iteratorTypeName().CamelName).Values( jen.Dict{ - jen.Id(iriMember): jen.Id("v"), + jen.Id(iriMember): jen.Id("v"), + jen.Id(parentMemberName): jen.Id(codegen.This()), + jen.Id(myIndexMemberName): jen.Id(codegen.This()).Dot(lenMethod).Call(), }, ), ), @@ -216,9 +251,12 @@ func (p *NonFunctionalPropertyGenerator) funcs() []*codegen.Method { []jen.Code{jen.Id("idx").Int(), jen.Id("v").Op("*").Qual("net/url", "URL")}, /*ret=*/ nil, []jen.Code{ + jen.Parens(jen.Op("*").Id(codegen.This())).Index(jen.Id("idx")).Dot(parentMemberName).Op("=").Nil(), jen.Parens(jen.Op("*").Id(codegen.This())).Index(jen.Id("idx")).Op("=").Op("&").Id(p.iteratorTypeName().CamelName).Values( jen.Dict{ - jen.Id(iriMember): jen.Id("v"), + jen.Id(iriMember): jen.Id("v"), + jen.Id(parentMemberName): jen.Id(codegen.This()), + jen.Id(myIndexMemberName): jen.Id("idx"), }, ), }, @@ -241,6 +279,7 @@ func (p *NonFunctionalPropertyGenerator) funcs() []*codegen.Method { []jen.Code{jen.Id("idx").Int()}, /*ret=*/ nil, []jen.Code{ + jen.Parens(jen.Op("*").Id(codegen.This())).Index(jen.Id("idx")).Dot(parentMemberName).Op("=").Nil(), jen.Copy( jen.Parens( jen.Op("*").Id(codegen.This()), @@ -266,8 +305,17 @@ func (p *NonFunctionalPropertyGenerator) funcs() []*codegen.Method { jen.Empty(), jen.Len(jen.Op("*").Id(codegen.This())).Op("-").Lit(1), ), + jen.For( + jen.Id("i").Op(":=").Id("idx"), + jen.Id("i").Op("<").Id(codegen.This()).Dot(lenMethod).Call(), + jen.Id("i").Op("++"), + ).Block( + jen.Parens( + jen.Op("*").Id(codegen.This()), + ).Index(jen.Id("i")).Dot(myIndexMemberName).Op("=").Id("i"), + ), }, - fmt.Sprintf("%s deletes an element at the specified index from a list of the property %q, regardless of its type.", removeMethod, p.PropertyName()))) + fmt.Sprintf("%s deletes an element at the specified index from a list of the property %q, regardless of its type. Panics if the index is out of bounds. Invalidates all iterators.", removeMethod, p.PropertyName()))) // Len Method methods = append(methods, codegen.NewCommentedValueMethod( @@ -340,7 +388,7 @@ func (p *NonFunctionalPropertyGenerator) funcs() []*codegen.Method { jen.Id(codegen.This()).Index(jen.Id("idx")).Dot(kindIndexMethod).Call(), ), }, - fmt.Sprintf("%s computes an arbitrary value for indexing this kind of value.", kindIndexMethod))) + fmt.Sprintf("%s computes an arbitrary value for indexing this kind of value. This is a leaky API method specifically needed only for alternate implementations for go-fed. Applications should not use this method. Panics if the index is out of bounds.", kindIndexMethod))) // LessThan Method lessCode := jen.Empty().Add( jen.Id("l1").Op(":=").Id(codegen.This()).Dot(lenMethod).Call().Line(), @@ -376,7 +424,7 @@ func (p *NonFunctionalPropertyGenerator) funcs() []*codegen.Method { ), jen.Return(jen.Id("l1").Op("<").Id("l2")), }, - fmt.Sprintf("%s compares two instances of this property with an arbitrary but stable comparison.", compareLessMethod), + fmt.Sprintf("%s compares two instances of this property with an arbitrary but stable comparison. Applications should not use this because it is only meant to help alternative implementations to go-fed to be able to normalize nonfunctional properties.", compareLessMethod), )) // At Method methods = append(methods, codegen.NewCommentedValueMethod( @@ -390,7 +438,50 @@ func (p *NonFunctionalPropertyGenerator) funcs() []*codegen.Method { jen.Id(codegen.This()).Index(jen.Id("index")), ), }, - fmt.Sprintf("%s returns the property value for the specified index.", atMethodName))) + fmt.Sprintf("%s returns the property value for the specified index. Panics if the index is out of bounds.", atMethodName))) + // Empty Method + methods = append(methods, codegen.NewCommentedValueMethod( + p.GetPrivatePackage().Path(), + emptyMethod, + p.StructName(), + /*params=*/ nil, + []jen.Code{jen.Bool()}, + []jen.Code{ + jen.Return( + jen.Id(codegen.This()).Dot(lenMethod).Call().Op("==").Lit(0), + ), + }, + fmt.Sprintf("%s returns returns true if there are no elements.", emptyMethod))) + // Begin Method + methods = append(methods, codegen.NewCommentedValueMethod( + p.GetPrivatePackage().Path(), + beginMethod, + p.StructName(), + /*params=*/ nil, + []jen.Code{jen.Qual(p.GetPublicPackage().Path(), p.iteratorInterfaceName())}, + []jen.Code{ + jen.If( + jen.Id(codegen.This()).Dot(emptyMethod).Call(), + ).Block( + jen.Return(jen.Nil()), + ).Else().Block( + jen.Return( + jen.Id(codegen.This()).Index(jen.Lit(0)), + ), + ), + }, + fmt.Sprintf("%s returns the first iterator, or nil if empty. Can be used with the iterator's %s method and this property's %s method to iterate from front to back through all values.", beginMethod, nextMethod, endMethod))) + // End Method + methods = append(methods, codegen.NewCommentedValueMethod( + p.GetPrivatePackage().Path(), + endMethod, + p.StructName(), + /*params=*/ nil, + []jen.Code{jen.Qual(p.GetPublicPackage().Path(), p.iteratorInterfaceName())}, + []jen.Code{ + jen.Return(jen.Nil()), + }, + fmt.Sprintf("%s returns beyond-the-last iterator, which is nil. Can be used with the iterator's %s method and this property's %s method to iterate from front to back through all values.", endMethod, nextMethod, beginMethod))) methods = append(methods, p.commonMethods()...) return methods } @@ -440,7 +531,7 @@ func (p *NonFunctionalPropertyGenerator) serializationFuncs() (*codegen.Method, jen.Nil(), ), }, - fmt.Sprintf("%s converts this into an interface representation suitable for marshalling into a text or binary format.", p.serializeFnName())) + fmt.Sprintf("%s converts this into an interface representation suitable for marshalling into a text or binary format. Applications should not need this function as most typical use cases serialize types instead of individual properties. It is exposed for alternatives to go-fed implementations to use.", p.serializeFnName())) deserializeFn := func(variable string) jen.Code { return jen.If( jen.List( @@ -503,8 +594,16 @@ func (p *NonFunctionalPropertyGenerator) serializationFuncs() (*codegen.Method, ), ), jen.Id("ret").Op(":=").Id(p.StructName()).Call(jen.Id(codegen.This())), + jen.Id("pRet").Op(":=").Op("&").Id("ret"), + jen.Commentf("Set up the properties for iteration."), + jen.For( + jen.List(jen.Id("idx"), jen.Id("ele")).Op(":=").Range().Id("ret"), + ).Block( + jen.Id("ele").Dot(parentMemberName).Op("=").Id("pRet"), + jen.Id("ele").Dot(myIndexMemberName).Op("=").Id("idx"), + ), jen.Return( - jen.Op("&").Id("ret"), + jen.Id("pRet"), jen.Nil(), ), }, diff --git a/tools/exp/gen/property.go b/tools/exp/gen/property.go index baf1d92..32e3f83 100644 --- a/tools/exp/gen/property.go +++ b/tools/exp/gen/property.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/cjslep/activity/tools/exp/codegen" "github.com/dave/jennifer/jen" + "strings" ) const ( @@ -14,6 +15,7 @@ const ( clearMethod = "Clear" iteratorClearMethod = "clear" isMethod = "Is" + atMethodName = "At" isIRIMethod = "IsIRI" getIRIMethod = "GetIRI" setIRIMethod = "SetIRI" @@ -33,12 +35,20 @@ const ( hasLanguageMethod = "HasLanguage" getLanguageMethod = "GetLanguage" setLanguageMethod = "SetLanguage" + nextMethod = "Next" + prevMethod = "Prev" + beginMethod = "Begin" + endMethod = "End" + emptyMethod = "Empty" // Member names for generated code unknownMemberName = "unknown" langMapMember = "langMap" // Kind Index constants iriKindIndex = -2 noneOrUnknownKindIndex = -1 + // iterator specific + myIndexMemberName = "myIdx" + parentMemberName = "parent" ) // join appends a bunch of Go Code together, each on their own line. @@ -212,6 +222,12 @@ func (p *PropertyGenerator) InterfaceName() string { return fmt.Sprintf("%sInterface", p.StructName()) } +// parentTypeInterfaceName is useful for iterators that need the base property +// type's interface name. +func (p *PropertyGenerator) parentTypeInterfaceName() string { + return fmt.Sprintf("%sInterface", strings.TrimSuffix(p.StructName(), "Iterator")) +} + // PropertyName returns the name of this property, as defined in // specifications. It is not suitable for use in generated code function // identifiers. @@ -296,7 +312,7 @@ func (p *PropertyGenerator) clearMethodName() string { // commonMethods returns methods common to every property. func (p *PropertyGenerator) commonMethods() []*codegen.Method { - return []*codegen.Method{ + m := []*codegen.Method{ codegen.NewCommentedValueMethod( p.GetPrivatePackage().Path(), nameMethod, @@ -311,6 +327,45 @@ func (p *PropertyGenerator) commonMethods() []*codegen.Method { fmt.Sprintf("%s returns the name of this property: %q.", nameMethod, p.PropertyName()), ), } + if p.asIterator { + m = append(m, codegen.NewCommentedValueMethod( + p.GetPrivatePackage().Path(), + nextMethod, + p.StructName(), + /*params=*/ nil, + []jen.Code{jen.Qual(p.GetPublicPackage().Path(), p.InterfaceName())}, + []jen.Code{ + jen.If( + jen.Id(codegen.This()).Dot(myIndexMemberName).Op("+").Lit(1).Op(">=").Id(codegen.This()).Dot(parentMemberName).Dot(lenMethod).Call(), + ).Block( + jen.Return(jen.Nil()), + ).Else().Block( + jen.Return( + jen.Id(codegen.This()).Dot(parentMemberName).Dot(atMethodName).Call(jen.Id(codegen.This()).Dot(myIndexMemberName).Op("+").Lit(1)), + ), + ), + }, + fmt.Sprintf("%s returns the next iterator, or nil if there is no next iterator.", nextMethod))) + m = append(m, codegen.NewCommentedValueMethod( + p.GetPrivatePackage().Path(), + prevMethod, + p.StructName(), + /*params=*/ nil, + []jen.Code{jen.Qual(p.GetPublicPackage().Path(), p.InterfaceName())}, + []jen.Code{ + jen.If( + jen.Id(codegen.This()).Dot(myIndexMemberName).Op("-").Lit(1).Op("<").Lit(0), + ).Block( + jen.Return(jen.Nil()), + ).Else().Block( + jen.Return( + jen.Id(codegen.This()).Dot(parentMemberName).Dot(atMethodName).Call(jen.Id(codegen.This()).Dot(myIndexMemberName).Op("-").Lit(1)), + ), + ), + }, + fmt.Sprintf("%s returns the previous iterator, or nil if there is no previous iterator.", prevMethod))) + } + return m } // isMethodName returns the identifier to use for methods that determine if a