activity/tools/exp/funcprop.go

492 行
15 KiB
Go

package exp
import (
"fmt"
"github.com/dave/jennifer/jen"
)
// FunctionalPropertyGenerator produces Go code for properties that can have
// only one value. The resulting property is a struct type that can have one
// value that could be from multiple Kinds of values. If there is only one
// allowed Kind, then a smaller API is generated as a special case.
type FunctionalPropertyGenerator struct {
PropertyGenerator
}
// Definition produces the Go Struct code definition, which can generate its Go
// implementations.
func (p *FunctionalPropertyGenerator) Definition() *Struct {
if len(p.Kinds) == 1 {
return p.singleTypeDef()
} else {
return p.multiTypeDef()
}
}
// funcs produces the methods needed for the functional property.
func (p *FunctionalPropertyGenerator) funcs() []*Method {
kindIndexFns := make([]jen.Code, 0, len(p.Kinds))
for i, _ := range p.Kinds {
if len(p.Kinds) > 1 {
kindIndexFns = append(kindIndexFns, jen.If(
jen.Id(This()).Dot(p.isMethodName(i)).Call(),
).Block(
jen.Return(jen.Lit(i)),
))
} else {
kindIndexFns = append(kindIndexFns, jen.If(
jen.Id(This()).Dot(hasMethod).Call(),
).Block(
jen.Return(jen.Lit(i)),
))
}
}
return []*Method{
NewCommentedValueMethod(
p.packageName(),
kindIndexMethod,
p.structName(),
/*params=*/ nil,
[]jen.Code{jen.Int()},
[]jen.Code{
join(kindIndexFns),
jen.Return(jen.Lit(-1)),
},
jen.Commentf("%s computes an arbitrary value for indexing this kind of value.", kindIndexMethod),
),
}
}
// 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() ([]*Method, []*Function) {
serializeFns := jen.Empty()
for i, kind := range p.Kinds {
if i > 0 {
serializeFns = serializeFns.Else()
}
if len(p.Kinds) == 1 {
serializeFns = serializeFns.If(
jen.Id(This()).Dot(hasMethod).Call(),
)
} else {
serializeFns = serializeFns.If(
jen.Id(This()).Dot(p.isMethodName(i)).Call(),
)
}
serializeFns = serializeFns.Block(
jen.Return(
jen.Id(kind.SerializeFnName).Call(
jen.Id(This()).Dot(p.getFnName(i)).Call(),
),
),
)
}
serialize := []*Method{
NewCommentedValueMethod(
p.packageName(),
p.serializeFnName(),
p.structName(),
/*params=*/ nil,
[]jen.Code{jen.Interface(), jen.Error()},
[]jen.Code{serializeFns, jen.Return(
jen.Id(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 {
deserializeFns = deserializeFns.Else()
}
values := jen.Dict{
jen.Id(p.memberName(i)): jen.Id("v"),
}
if !kind.Nilable {
values[jen.Id(p.hasMemberName(i))] = jen.True()
}
deserializeFns = deserializeFns.If(
jen.List(
jen.Id("v"),
jen.Id("handled"),
jen.Err(),
).Op(":=").Id(kind.DeserializeFnName).Call(
jen.Id("i"),
),
jen.Id("handled"),
).Block(
jen.Id(This()).Op(":=").Op("&").Id(p.structName()).Values(
values,
),
jen.Return(
jen.Id(This()),
jen.Err(),
),
)
}
var deserialize []*Function
if p.asIterator {
deserialize = append(deserialize,
NewCommentedFunction(
p.packageName(),
p.deserializeFnName(),
[]jen.Code{jen.Id("i").Interface()},
[]jen.Code{jen.Op("*").Id(p.structName()), jen.Error()},
[]jen.Code{
deserializeFns.Add(p.unknownDeserializeCode()),
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,
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()),
),
jen.Id("ok"),
).Block(
deserializeFns.Add(p.unknownDeserializeCode()),
),
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
}
// singleTypeDef generates a special-case simplified API for a functional
// property that can only be a single Kind of value.
func (p *FunctionalPropertyGenerator) singleTypeDef() *Struct {
var comment jen.Code
var kindMembers []jen.Code
if p.Kinds[0].Nilable {
comment = jen.Commentf("%s is the functional property %q. It is permitted to be a single nilable value type.", p.structName(), p.propertyName())
if p.asIterator {
comment = jen.Commentf("%s is an iterator for a property. It is permitted to be a single nilable value type.", p.structName())
}
kindMembers = []jen.Code{
jen.Id(p.memberName(0)).Id(p.Kinds[0].ConcreteKind),
p.unknownMemberDef(),
}
} else {
comment = jen.Commentf("%s is the functional property %q. It is permitted to be a single default-valued value type.", p.structName(), p.propertyName())
if p.asIterator {
comment = jen.Commentf("%s is an iterator for a property. It is permitted to be a single default-valued value type.", p.structName())
}
kindMembers = []jen.Code{
jen.Id(p.memberName(0)).Id(p.Kinds[0].ConcreteKind),
jen.Id(p.hasMemberName(0)).Bool(),
p.unknownMemberDef(),
}
}
methods, funcs := p.serializationFuncs()
methods = append(methods, p.singleTypeFuncs()...)
methods = append(methods, p.funcs()...)
methods = append(methods, p.commonMethods()...)
return NewStruct(comment,
p.structName(),
methods,
funcs,
kindMembers)
}
// singleTypeFuncs generates the special-case simplified methods for a
// functional property with exactly one Kind of value.
func (p *FunctionalPropertyGenerator) singleTypeFuncs() []*Method {
var methods []*Method
// Has Method
hasComment := jen.Commentf("%s returns true if this property is set.", hasMethod)
if p.Kinds[0].Nilable {
methods = append(methods, NewCommentedValueMethod(
p.packageName(),
hasMethod,
p.structName(),
/*params=*/ nil,
[]jen.Code{jen.Bool()},
[]jen.Code{jen.Return(jen.Id(This()).Dot(p.memberName(0)).Op("!=").Nil())},
hasComment,
))
} else {
methods = append(methods, NewCommentedValueMethod(
p.packageName(),
hasMethod,
p.structName(),
/*params=*/ nil,
[]jen.Code{jen.Bool()},
[]jen.Code{jen.Return(jen.Id(This()).Dot(p.hasMemberName(0)))},
hasComment,
))
}
// 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, NewCommentedValueMethod(
p.packageName(),
p.getFnName(0),
p.structName(),
/*params=*/ nil,
[]jen.Code{jen.Id(p.Kinds[0].ConcreteKind)},
[]jen.Code{jen.Return(jen.Id(This()).Dot(p.memberName(0)))},
getComment,
))
// Set Method
setComment := jen.Commentf("%s sets the value of this property. Calling %s afterwards will return true.", setMethod, hasMethod)
if p.Kinds[0].Nilable {
methods = append(methods, NewCommentedPointerMethod(
p.packageName(),
setMethod,
p.structName(),
[]jen.Code{jen.Id("v").Id(p.Kinds[0].ConcreteKind)},
/*ret=*/ nil,
[]jen.Code{jen.Id(This()).Dot(p.memberName(0)).Op("=").Id("v")},
setComment,
))
} else {
methods = append(methods, NewCommentedPointerMethod(
p.packageName(),
setMethod,
p.structName(),
[]jen.Code{jen.Id("v").Id(p.Kinds[0].ConcreteKind)},
/*ret=*/ nil,
[]jen.Code{
jen.Id(This()).Dot(p.memberName(0)).Op("=").Id("v"),
jen.Id(This()).Dot(p.hasMemberName(0)).Op("=").True(),
},
setComment,
))
}
// Clear Method
clearComment := jen.Commentf("%s ensures no value of this property is set. Calling %s afterwards will return false.", p.clearMethodName(), hasMethod).Line()
if p.Kinds[0].Nilable {
methods = append(methods, NewCommentedPointerMethod(
p.packageName(),
p.clearMethodName(),
p.structName(),
/*params=*/ nil,
/*ret=*/ nil,
[]jen.Code{jen.Id(This()).Dot(p.memberName(0)).Op("=").Nil()},
clearComment,
))
} else {
methods = append(methods, NewCommentedPointerMethod(
p.packageName(),
p.clearMethodName(),
p.structName(),
/*params=*/ nil,
/*ret=*/ nil,
[]jen.Code{jen.Id(This()).Dot(p.hasMemberName(0)).Op("=").False()},
clearComment,
))
}
return methods
}
// multiTypeDef generates an API for a functional property that can be multiple
// Kinds of value.
func (p *FunctionalPropertyGenerator) multiTypeDef() *Struct {
kindMembers := make([]jen.Code, 0, len(p.Kinds))
for i, kind := range p.Kinds {
if kind.Nilable {
kindMembers = append(kindMembers, jen.Id(p.memberName(i)).Id(p.Kinds[i].ConcreteKind))
} else {
kindMembers = append(kindMembers, jen.Id(p.memberName(i)).Id(p.Kinds[i].ConcreteKind))
kindMembers = append(kindMembers, jen.Id(p.hasMemberName(i)).Bool())
}
}
kindMembers = append(kindMembers, p.unknownMemberDef())
explanation := jen.Commentf(
"At most, one type of value can be present, or none at all. Setting a value will",
).Line().Commentf(
"clear the other types of values so that only one of the 'Is' methods will return",
).Line().Commentf(
"true.",
).Line().Comment("").Line().Commentf(
"It is possible to clear all values, so that this property is empty.",
)
comment := jen.Commentf(
"%s is the functional property %q. It is permitted to be one of multiple value types.", p.structName(), p.propertyName(),
).Line().Comment("").Line().Add(explanation)
if p.asIterator {
comment = jen.Commentf(
"%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()
methods = append(methods, p.multiTypeFuncs()...)
methods = append(methods, p.funcs()...)
methods = append(methods, p.commonMethods()...)
return NewStruct(comment,
p.structName(),
methods,
funcs,
kindMembers)
}
// multiTypeFuncs generates the methods for a functional property with more than
// one Kind of value.
func (p *FunctionalPropertyGenerator) multiTypeFuncs() []*Method {
var methods []*Method
// HasAny Method
hasAnyMethodName := fmt.Sprintf("%sAny", hasMethod)
isLine := make([]jen.Code, len(p.Kinds))
for i := range p.Kinds {
or := jen.Empty()
if i != len(p.Kinds)-1 {
or = jen.Op("||")
}
isLine[i] = jen.Id(This()).Dot(p.isMethodName(i)).Parens(nil).Add(or)
}
methods = append(methods, NewCommentedPointerMethod(
p.packageName(),
hasAnyMethodName,
p.structName(),
/*params=*/ nil,
[]jen.Code{jen.Bool()},
[]jen.Code{jen.Return(join(isLine))},
jen.Commentf(
"%s returns true if any of the different values is set.", hasAnyMethodName,
),
))
// Clear Method
clearLine := make([]jen.Code, len(p.Kinds))
for i, kind := range p.Kinds {
if kind.Nilable {
clearLine[i] = jen.Id(This()).Dot(p.memberName(i)).Op("=").Nil()
} else {
clearLine[i] = jen.Id(This()).Dot(p.hasMemberName(i)).Op("=").False()
}
}
methods = append(methods, NewCommentedPointerMethod(
p.packageName(),
p.clearMethodName(),
p.structName(),
/*params=*/ nil,
/*ret=*/ nil,
clearLine,
jen.Commentf(
"%s ensures no value of this property is set. Calling %s or any of the 'Is' methods afterwards will return false.", p.clearMethodName(), hasAnyMethodName,
),
))
// Is Method
for i, kind := range p.Kinds {
isComment := jen.Commentf("%s returns true if this property has a type of value of %q.", p.isMethodName(i), kind.ConcreteKind)
if kind.Nilable {
methods = append(methods, NewCommentedValueMethod(
p.packageName(),
p.isMethodName(i),
p.structName(),
/*params=*/ nil,
[]jen.Code{jen.Bool()},
[]jen.Code{jen.Return(jen.Id(This()).Dot(p.memberName(i)).Op("!=").Nil())},
isComment,
))
} else {
methods = append(methods, NewCommentedValueMethod(
p.packageName(),
p.isMethodName(i),
p.structName(),
/*params=*/ nil,
[]jen.Code{jen.Bool()},
[]jen.Code{jen.Return(jen.Id(This()).Dot(p.hasMemberName(i)))},
isComment,
))
}
}
// Set Method
for i, kind := range p.Kinds {
setMethodName := fmt.Sprintf("%s%s", setMethod, p.kindCamelName(i))
setComment := jen.Commentf("%s sets the value of this property. Calling %s afterwards returns true.", setMethodName, p.isMethodName(i))
if kind.Nilable {
methods = append(methods, NewCommentedPointerMethod(
p.packageName(),
setMethodName,
p.structName(),
[]jen.Code{jen.Id("v").Id(kind.ConcreteKind)},
/*ret=*/ nil,
[]jen.Code{
jen.Id(This()).Dot(p.clearMethodName()).Call(),
jen.Id(This()).Dot(p.memberName(i)).Op("=").Id("v"),
},
setComment,
))
} else {
methods = append(methods, NewCommentedPointerMethod(
p.packageName(),
setMethodName,
p.structName(),
[]jen.Code{jen.Id("v").Id(kind.ConcreteKind)},
/*ret=*/ nil,
[]jen.Code{
jen.Id(This()).Dot(p.clearMethodName()).Call(),
jen.Id(This()).Dot(p.memberName(i)).Op("=").Id("v"),
jen.Id(This()).Dot(p.hasMemberName(i)).Op("=").True(),
},
setComment,
))
}
}
// Get 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, NewCommentedValueMethod(
p.packageName(),
p.getFnName(i),
p.structName(),
/*params=*/ nil,
[]jen.Code{jen.Id(kind.ConcreteKind)},
[]jen.Code{jen.Return(jen.Id(This()).Dot(p.memberName(i)))},
getComment,
))
}
return methods
}
// unknownMemberDef returns the definition of a struct member that handles
// a property whose type is unknown.
func (p *FunctionalPropertyGenerator) unknownMemberDef() jen.Code {
return jen.Id(unknownMemberName).Index().Byte()
}
// unknownDeserializeCode generates the "else if it's a []byte" code used for
// deserializing unknown values.
func (p *FunctionalPropertyGenerator) unknownDeserializeCode() jen.Code {
return jen.Else().If(
jen.List(
jen.Id("v"),
jen.Id("ok"),
).Op(":=").Id("i").Assert(
jen.Index().Byte(),
),
jen.Id("ok"),
).Block(
jen.Id(This()).Op(":=").Op("&").Id(p.structName()).Values(
jen.Dict{
jen.Id(unknownMemberName): jen.Id("v"),
},
),
jen.Return(
jen.Id(This()),
jen.Err(),
),
)
}