372 行
11 KiB
Go
372 行
11 KiB
Go
package props
|
|
|
|
import (
|
|
"fmt"
|
|
"github.com/dave/jennifer/jen"
|
|
"github.com/go-fed/activity/tools/exp/codegen"
|
|
"sync"
|
|
)
|
|
|
|
// 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
|
|
// sorted and iterated over so individual elements can be inspected.
|
|
type NonFunctionalPropertyGenerator struct {
|
|
PropertyGenerator
|
|
cacheOnce sync.Once
|
|
cachedStruct *codegen.Struct
|
|
cachedTypedef *codegen.Typedef
|
|
}
|
|
|
|
// NewNonFunctionalPropertyGenerator is a convenience constructor to create
|
|
// NonFunctionalPropertyGenerators.
|
|
func NewNonFunctionalPropertyGenerator(pkg string,
|
|
name Identifier,
|
|
kinds []Kind,
|
|
hasNaturalLanguageMap bool) *NonFunctionalPropertyGenerator {
|
|
return &NonFunctionalPropertyGenerator{
|
|
PropertyGenerator: PropertyGenerator{
|
|
Package: pkg,
|
|
HasNaturalLanguageMap: hasNaturalLanguageMap,
|
|
Name: name,
|
|
Kinds: kinds,
|
|
},
|
|
}
|
|
}
|
|
|
|
// 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()
|
|
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()),
|
|
p.StructName(),
|
|
jen.Index().Id(p.iteratorTypeName().CamelName),
|
|
methods,
|
|
funcs)
|
|
iterator := p.elementTypeGenerator().Definition()
|
|
p.cachedStruct, p.cachedTypedef = iterator, property
|
|
})
|
|
return p.cachedStruct, p.cachedTypedef
|
|
}
|
|
|
|
// iteratorTypeName determines the identifier to use for the iterator type.
|
|
func (p *NonFunctionalPropertyGenerator) iteratorTypeName() Identifier {
|
|
return Identifier{
|
|
LowerName: fmt.Sprintf("%sPropertyIterator", p.Name.LowerName),
|
|
CamelName: fmt.Sprintf("%sPropertyIterator", p.Name.CamelName),
|
|
}
|
|
}
|
|
|
|
// elementTypeGenerator produces a FunctionalPropertyGenerator for the iterator
|
|
// type.
|
|
func (p *NonFunctionalPropertyGenerator) elementTypeGenerator() *FunctionalPropertyGenerator {
|
|
return &FunctionalPropertyGenerator{
|
|
PropertyGenerator: PropertyGenerator{
|
|
Package: p.PropertyGenerator.Package,
|
|
Name: p.iteratorTypeName(),
|
|
Kinds: p.Kinds,
|
|
HasNaturalLanguageMap: p.PropertyGenerator.HasNaturalLanguageMap,
|
|
asIterator: true,
|
|
},
|
|
}
|
|
}
|
|
|
|
// funcs produces the methods needed for the NonFunctional property.
|
|
func (p *NonFunctionalPropertyGenerator) funcs() []*codegen.Method {
|
|
var methods []*codegen.Method
|
|
less := jen.Empty()
|
|
for i, kind := range p.Kinds {
|
|
dict := jen.Dict{
|
|
jen.Id(p.memberName(i)): jen.Id("v"),
|
|
}
|
|
if !kind.Nilable {
|
|
dict[jen.Id(p.hasMemberName(i))] = jen.True()
|
|
}
|
|
// Prepend Method
|
|
prependMethodName := fmt.Sprintf("%s%s", prependMethod, p.kindCamelName(i))
|
|
methods = append(methods,
|
|
codegen.NewCommentedPointerMethod(
|
|
p.packageName(),
|
|
prependMethodName,
|
|
p.StructName(),
|
|
[]jen.Code{jen.Id("v").Id(kind.ConcreteKind)},
|
|
/*ret=*/ nil,
|
|
[]jen.Code{
|
|
jen.Op("*").Id(codegen.This()).Op("=").Append(
|
|
jen.Index().Id(p.iteratorTypeName().CamelName).Values(
|
|
jen.Values(dict),
|
|
),
|
|
jen.Op("*").Id(codegen.This()).Op("..."),
|
|
),
|
|
},
|
|
jen.Commentf("%s prepends a %s value to the front of a list of the property %q.", prependMethodName, kind.ConcreteKind, p.PropertyName())))
|
|
// Append Method
|
|
appendMethodName := fmt.Sprintf("%s%s", appendMethod, p.kindCamelName(i))
|
|
methods = append(methods,
|
|
codegen.NewCommentedPointerMethod(
|
|
p.packageName(),
|
|
appendMethodName,
|
|
p.StructName(),
|
|
[]jen.Code{jen.Id("v").Id(kind.ConcreteKind)},
|
|
/*ret=*/ nil,
|
|
[]jen.Code{
|
|
jen.Op("*").Id(codegen.This()).Op("=").Append(
|
|
jen.Op("*").Id(codegen.This()),
|
|
jen.Id(p.iteratorTypeName().CamelName).Values(
|
|
dict,
|
|
),
|
|
),
|
|
},
|
|
jen.Commentf("%s appends a %s value to the back of a list of the property %q", appendMethodName, kind.ConcreteKind, p.PropertyName())))
|
|
// Less logic
|
|
if i > 0 {
|
|
less.Else()
|
|
}
|
|
less.If(
|
|
jen.Id("idx1").Op("==").Lit(i),
|
|
).Block(
|
|
jen.Id("lhs").Op(":=").Id(codegen.This()).Index(jen.Id("i")).Dot(p.getFnName(i)).Call(),
|
|
jen.Id("rhs").Op(":=").Id(codegen.This()).Index(jen.Id("j")).Dot(p.getFnName(i)).Call(),
|
|
jen.Return(kind.LessFn.Call(
|
|
jen.Id("lhs"),
|
|
jen.Id("rhs"),
|
|
)),
|
|
)
|
|
}
|
|
// Remove Method
|
|
methods = append(methods,
|
|
codegen.NewCommentedPointerMethod(
|
|
p.packageName(),
|
|
removeMethod,
|
|
p.StructName(),
|
|
[]jen.Code{jen.Id("idx").Int()},
|
|
/*ret=*/ nil,
|
|
[]jen.Code{
|
|
jen.Copy(
|
|
jen.Parens(
|
|
jen.Op("*").Id(codegen.This()),
|
|
).Index(
|
|
jen.Id("idx"),
|
|
jen.Empty(),
|
|
),
|
|
jen.Parens(
|
|
jen.Op("*").Id(codegen.This()),
|
|
).Index(
|
|
jen.Id("idx").Op("+").Lit(1),
|
|
jen.Empty(),
|
|
),
|
|
),
|
|
jen.Parens(
|
|
jen.Op("*").Id(codegen.This()),
|
|
).Index(
|
|
jen.Len(jen.Op("*").Id(codegen.This())).Op("-").Lit(1),
|
|
).Op("=").Id(p.iteratorTypeName().CamelName).Values(),
|
|
jen.Op("*").Id(codegen.This()).Op("=").Parens(
|
|
jen.Op("*").Id(codegen.This()),
|
|
).Index(
|
|
jen.Empty(),
|
|
jen.Len(jen.Op("*").Id(codegen.This())).Op("-").Lit(1),
|
|
),
|
|
},
|
|
jen.Commentf("%s deletes an element at the specified index from a list of the property %q, regardless of its type.", removeMethod, p.PropertyName())))
|
|
// Len Method
|
|
methods = append(methods,
|
|
codegen.NewCommentedValueMethod(
|
|
p.packageName(),
|
|
lenMethod,
|
|
p.StructName(),
|
|
/*params=*/ nil,
|
|
[]jen.Code{jen.Id("length").Int()},
|
|
[]jen.Code{
|
|
jen.Return(
|
|
jen.Len(
|
|
jen.Id(codegen.This()),
|
|
),
|
|
),
|
|
},
|
|
jen.Commentf("%s returns the number of values that exist for the %q property.", lenMethod, p.PropertyName())))
|
|
// Swap Method
|
|
methods = append(methods,
|
|
codegen.NewCommentedValueMethod(
|
|
p.packageName(),
|
|
swapMethod,
|
|
p.StructName(),
|
|
[]jen.Code{
|
|
jen.Id("i"),
|
|
jen.Id("j").Int(),
|
|
},
|
|
/*ret=*/ nil,
|
|
[]jen.Code{
|
|
jen.List(
|
|
jen.Id(codegen.This()).Index(jen.Id("i")),
|
|
jen.Id(codegen.This()).Index(jen.Id("j")),
|
|
).Op("=").List(
|
|
jen.Id(codegen.This()).Index(jen.Id("j")),
|
|
jen.Id(codegen.This()).Index(jen.Id("i")),
|
|
),
|
|
},
|
|
jen.Commentf("%s swaps the location of values at two indices for the %q property.", swapMethod, p.PropertyName())))
|
|
// Less Method
|
|
methods = append(methods,
|
|
codegen.NewCommentedValueMethod(
|
|
p.packageName(),
|
|
lessMethod,
|
|
p.StructName(),
|
|
[]jen.Code{
|
|
jen.Id("i"),
|
|
jen.Id("j").Int(),
|
|
},
|
|
[]jen.Code{jen.Bool()},
|
|
[]jen.Code{
|
|
jen.Id("idx1").Op(":=").Id(codegen.This()).Dot(kindIndexMethod).Call(jen.Id("i")),
|
|
jen.Id("idx2").Op(":=").Id(codegen.This()).Dot(kindIndexMethod).Call(jen.Id("j")),
|
|
jen.If(jen.Id("idx1").Op("<").Id("idx2")).Block(
|
|
jen.Return(jen.True()),
|
|
).Else().If(jen.Id("idx1").Op("==").Id("idx2")).Block(
|
|
less,
|
|
),
|
|
jen.Return(jen.False()),
|
|
},
|
|
jen.Commentf("%s computes whether another property is less than this one. Mixing types results in a consistent but arbitrary ordering", lessMethod)))
|
|
// Kind Method
|
|
methods = append(methods,
|
|
codegen.NewCommentedValueMethod(
|
|
p.packageName(),
|
|
kindIndexMethod,
|
|
p.StructName(),
|
|
[]jen.Code{jen.Id("idx").Int()},
|
|
[]jen.Code{jen.Int()},
|
|
[]jen.Code{
|
|
jen.Return(
|
|
jen.Id(codegen.This()).Index(jen.Id("idx")).Dot(kindIndexMethod).Call(),
|
|
),
|
|
},
|
|
jen.Commentf("%s computes an arbitrary value for indexing this kind of value.", kindIndexMethod)))
|
|
return methods
|
|
}
|
|
|
|
// 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(
|
|
jen.List(
|
|
jen.Id("_"),
|
|
jen.Id("iterator"),
|
|
).Op(":=").Range().Id(codegen.This()),
|
|
).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.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(
|
|
jen.Id("p"),
|
|
jen.Err(),
|
|
).Op(":=").Id(p.elementTypeGenerator().deserializeFnName()).Call(
|
|
jen.Id(variable),
|
|
),
|
|
jen.Err().Op("!=").Nil(),
|
|
).Block(
|
|
jen.Return(
|
|
jen.Id(codegen.This()),
|
|
jen.Err(),
|
|
),
|
|
).Else().If(
|
|
jen.Id("p").Op("!=").Nil(),
|
|
).Block(
|
|
jen.Id(codegen.This()).Op("=").Append(
|
|
jen.Id(codegen.This()),
|
|
jen.Op("*").Id("p"),
|
|
),
|
|
)
|
|
}
|
|
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),
|
|
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("list"),
|
|
jen.Id("ok"),
|
|
).Op(":=").Id("i").Assert(
|
|
jen.Index().Interface(),
|
|
),
|
|
jen.Id("ok"),
|
|
).Block(
|
|
jen.For(
|
|
jen.List(
|
|
jen.Id("_"),
|
|
jen.Id("iterator"),
|
|
).Op(":=").Range().Id("list"),
|
|
).Block(
|
|
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()),
|
|
),
|
|
}
|
|
return serialize, deserialize
|
|
}
|