go-fed-activity/tools/exp/props/type.go

488 lines
16 KiB
Go

package props
import (
"fmt"
"github.com/cjslep/activity/tools/exp/codegen"
"github.com/dave/jennifer/jen"
"sort"
"strings"
"sync"
)
// TODO: Prevent circular dependency by somehow abstracting the requisite
// functions between props and types.
const (
typeInterfaceName = "Type"
extendedByMethod = "IsExtendedBy"
extendingMethod = "IsExtending"
extendsMethod = "Extends"
disjointWithMethod = "IsDisjointWith"
typeNameMethod = "Name"
serializeMethodName = "Serialize"
deserializeFnName = "Deserialize"
lessFnName = "Less"
)
// 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 {
comment := fmt.Sprintf("%s represents an ActivityStreams type.", typeInterfaceName)
funcs := []codegen.FunctionSignature{
{
Name: typeNameMethod,
Params: nil,
Ret: []jen.Code{jen.String()},
Comment: fmt.Sprintf("%s returns the ActivityStreams type name.", typeNameMethod),
},
}
return codegen.NewInterface(pkg, typeInterfaceName, funcs, comment)
}
// Property represents a property of an ActivityStreams type.
type Property interface {
PackageName() string
PropertyName() string
StructName() string
SetKindFns(name string, ser, deser, less *codegen.Function) error
DeserializeFnName() string
}
// TypeGenerator represents an ActivityStream type definition to generate in Go.
type TypeGenerator struct {
packageName string
typeName string
comment string
properties map[string]Property
withoutProperties map[string]Property
extends []*TypeGenerator
disjoint []*TypeGenerator
extendedBy []*TypeGenerator
cacheOnce sync.Once
cachedStruct *codegen.Struct
}
// NewTypeGenerator creates a new generator for a specific ActivityStreams Core
// or extension type. It will return an error if there are multiple properties
// have the same Name.
//
// The extends and disjoint parameters are allowed to be nil. These lists must
// also have unique (non-duplicated) elements.
//
// 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,
extends, disjoint []*TypeGenerator) (*TypeGenerator, error) {
t := &TypeGenerator{
packageName: packageName,
typeName: typeName,
comment: comment,
properties: make(map[string]Property, len(properties)),
withoutProperties: make(map[string]Property, len(withoutProperties)),
extends: extends,
disjoint: disjoint,
}
for _, property := range properties {
if _, has := t.properties[property.PropertyName()]; has {
return nil, fmt.Errorf("type already has property with name %q", property.PropertyName())
}
t.properties[property.PropertyName()] = property
}
for _, wop := range withoutProperties {
if _, has := t.withoutProperties[wop.PropertyName()]; has {
return nil, fmt.Errorf("type already has withoutproperty with name %q", wop.PropertyName())
}
t.withoutProperties[wop.PropertyName()] = wop
}
// Complete doubly-linked extends/extendedBy lists.
for _, ext := range extends {
ext.extendedBy = append(ext.extendedBy, 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)
}
// Comment returns the comment for this type.
func (t *TypeGenerator) Comment() string {
return t.comment
}
// TypeName returns the ActivityStreams name for this type.
func (t *TypeGenerator) TypeName() string {
return t.typeName
}
// Extends returns the generators of types that this ActivityStreams type
// extends from.
func (t *TypeGenerator) Extends() []*TypeGenerator {
return t.extends
}
// ExtendedBy returns the generators of types that extend from this
// ActivityStreams type.
func (t *TypeGenerator) ExtendedBy() []*TypeGenerator {
return t.extendedBy
}
// Disjoint returns the generators of types that this ActivityStreams type is
// disjoint to.
func (t *TypeGenerator) Disjoint() []*TypeGenerator {
return t.disjoint
}
// Properties returns the Properties of this type, mapped by their property
// name.
func (t *TypeGenerator) Properties() map[string]Property {
return t.properties
}
// WithoutProperties returns the properties that do not apply to this type,
// mapped by their property name.
func (t *TypeGenerator) WithoutProperties() map[string]Property {
return t.withoutProperties
}
// extendsFnName determines the name of the Extends function, which
// determines if this ActivityStreams type extends another one.
func (t *TypeGenerator) extendsFnName() string {
return fmt.Sprintf("%s%s", t.TypeName(), extendsMethod)
}
// extendedByFnName determines the name of the ExtendedBy function, which
// determines if another ActivityStreams type extends this one.
func (t *TypeGenerator) extendedByFnName() string {
return fmt.Sprintf("%s%s", t.TypeName(), extendedByMethod)
}
// disjointWithFnName determines the name of the DisjointWith function, which
// determines if another ActivityStreams type is disjoint with this one.
func (t *TypeGenerator) disjointWithFnName() string {
return fmt.Sprintf("%s%s", t.TypeName(), disjointWithMethod)
}
// 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()
extendsFn, extendsMethod := t.extendsDefinition()
t.cachedStruct = codegen.NewStruct(
jen.Commentf(t.Comment()),
t.TypeName(),
[]*codegen.Method{
t.nameDefinition(),
extendsMethod,
m,
},
[]*codegen.Function{
t.extendedByDefinition(),
extendsFn,
t.disjointWithDefinition(),
ser,
deser,
less,
},
members)
})
return t.cachedStruct
}
func (t *TypeGenerator) allProperties() map[string]Property {
p := t.properties
// Properties of parents that are extended, minus DoesNotApplyTo
var extends []*TypeGenerator
extends = t.getAllParentExtends(extends, t)
for _, ext := range t.extends {
for k, v := range ext.Properties() {
p[k] = v
}
}
for _, ext := range t.extends {
for k, _ := range ext.WithoutProperties() {
delete(p, k)
}
}
return p
}
// sortedProperty is a slice of Properties that implements the Sort interface.
type sortedProperty []Property
func (s sortedProperty) Less(i, j int) bool {
return s[i].PropertyName() < s[j].PropertyName()
}
func (s sortedProperty) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
func (s sortedProperty) Len() int {
return len(s)
}
func (t *TypeGenerator) members() (members []jen.Code) {
p := t.allProperties()
// Sort the properties for readability
sortedMembers := make(sortedProperty, 0, len(p))
for _, property := range p {
sortedMembers = append(sortedMembers, property)
}
sort.Sort(sortedMembers)
// 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()))
}
return
}
// nameDefinition generates the golang method for returning the ActivityStreams
// type name.
func (t *TypeGenerator) nameDefinition() *codegen.Method {
return codegen.NewCommentedValueMethod(
t.packageName,
typeNameMethod,
t.TypeName(),
/*params=*/ nil,
[]jen.Code{jen.String()},
[]jen.Code{
jen.Return(jen.Lit(t.TypeName())),
},
jen.Commentf("%s returns the name of this type.", typeNameMethod))
}
// getAllParentExtends recursively determines all the parent types that this
// type extends from.
func (t *TypeGenerator) getAllParentExtends(s []*TypeGenerator, tg *TypeGenerator) []*TypeGenerator {
for _, e := range tg.Extends() {
s = append(s, e)
s = append(s, t.getAllParentExtends(s, e)...)
}
return s
}
// extendsDefinition generates the golang function for determining if this
// ActivityStreams type extends another type. It requires the Type interface.
func (t *TypeGenerator) extendsDefinition() (*codegen.Function, *codegen.Method) {
var extends []*TypeGenerator
extends = t.getAllParentExtends(extends, t)
extendNames := make(map[string]struct{}, len(extends))
for _, ext := range extends {
extendNames[ext.TypeName()] = struct{}{}
}
extensions := make([]jen.Code, len(extendNames))
for e := range extendNames {
extensions = append(extensions, jen.Lit(e))
}
impl := []jen.Code{jen.Comment("Shortcut implementation: this does not extend anything."), jen.Return(jen.False())}
if len(extensions) > 0 {
impl = []jen.Code{jen.Id("extensions").Op(":=").Index().String().Values(extensions...),
jen.For(jen.List(
jen.Id("_"),
jen.Id("ext"),
).Op(":=").Range().Id("extensions")).Block(
jen.If(
jen.Id("ext").Op("==").Id("other").Dot(typeNameMethod).Call(),
).Block(
jen.Return(jen.True()),
),
),
jen.Return(jen.False())}
}
f := codegen.NewCommentedFunction(
t.packageName,
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,
extendingMethod,
t.TypeName(),
[]jen.Code{jen.Id("other").Id(typeInterfaceName)},
[]jen.Code{jen.Bool()},
[]jen.Code{
jen.Return(
jen.Id(t.extendsFnName()).Call(jen.Id("other")),
),
},
jen.Commentf("%s returns true if the %s type extends from the other type.", extendingMethod, t.TypeName()))
return f, m
}
// getAllChildrenExtendBy recursivley determines all the child types that this
// type is extended by.
func (t *TypeGenerator) getAllChildrenExtendedBy(s []string, tg *TypeGenerator) {
for _, e := range tg.ExtendedBy() {
s = append(s, e.TypeName())
t.getAllChildrenExtendedBy(s, e)
}
}
// extendedByDefinition generates the golang function for determining if
// another ActivityStreams type extends this type. It requires the Type
// interface.
func (t *TypeGenerator) extendedByDefinition() *codegen.Function {
var extendNames []string
t.getAllChildrenExtendedBy(extendNames, t)
extensions := make([]jen.Code, len(extendNames))
for i, e := range extendNames {
extensions[i] = jen.Lit(e)
}
impl := []jen.Code{jen.Comment("Shortcut implementation: is not extended by anything."), jen.Return(jen.False())}
if len(extensions) > 0 {
impl = []jen.Code{jen.Id("extensions").Op(":=").Index().String().Values(extensions...),
jen.For(jen.List(
jen.Id("_"),
jen.Id("ext"),
).Op(":=").Range().Id("extensions")).Block(
jen.If(
jen.Id("ext").Op("==").Id("other").Dot(typeNameMethod).Call(),
).Block(
jen.Return(jen.True()),
),
),
jen.Return(jen.False())}
}
return codegen.NewCommentedFunction(
t.packageName,
t.extendedByFnName(),
[]jen.Code{jen.Id("other").Id(typeInterfaceName)},
[]jen.Code{jen.Bool()},
impl,
jen.Commentf("%s returns true if the other provided type extends from the %s type.", t.extendedByFnName(), t.TypeName()))
}
// getAllChildrenDisjointWith recursivley determines all the child types that this
// type is disjoint with.
func (t *TypeGenerator) getAllDisjointWith(s []string) {
for _, e := range t.Disjoint() {
s = append(s, e.TypeName())
// Get all the disjoint type's children.
t.getAllChildrenExtendedBy(s, e)
}
}
// disjointWithDefinition generates the golang function for determining if
// another ActivityStreams type is disjoint with this type. It requires the Type
// interface.
func (t *TypeGenerator) disjointWithDefinition() *codegen.Function {
// TODO: Inherit disjoint from parent and the other extended types of
// the other.
var disjointNames []string
t.getAllDisjointWith(disjointNames)
disjointWith := make([]jen.Code, len(disjointNames))
for i, d := range disjointNames {
disjointWith[i] = jen.Lit(d)
}
impl := []jen.Code{jen.Comment("Shortcut implementation: is not disjoint with anything."), jen.Return(jen.False())}
if len(disjointWith) > 0 {
impl = []jen.Code{jen.Id("disjointWith").Op(":=").Index().String().Values(disjointWith...),
jen.For(jen.List(
jen.Id("_"),
jen.Id("disjoint"),
).Op(":=").Range().Id("disjointWith")).Block(
jen.If(
jen.Id("disjoint").Op("==").Id("other").Dot(typeNameMethod).Call(),
).Block(
jen.Return(jen.True()),
),
),
jen.Return(jen.False())}
}
return codegen.NewCommentedFunction(
t.packageName,
t.disjointWithFnName(),
[]jen.Code{jen.Id("other").Id(typeInterfaceName)},
[]jen.Code{jen.Bool()},
impl,
jen.Commentf("%s returns true if the other provided type is disjoint with the %s type.", t.disjointWithFnName(), t.TypeName()))
}
// serializationMethod returns the method needed to serialize a TypeGenerator as
// a property.
func (t *TypeGenerator) serializationMethod() (ser *codegen.Method) {
ser = codegen.NewCommentedValueMethod(
t.packageName,
serializeMethodName,
t.TypeName(),
/*params=*/ nil,
[]jen.Code{jen.Interface(), jen.Error()},
[]jen.Code{
// TODO
jen.Commentf("TODO: Serialization code for %s", t.TypeName()),
},
jen.Commentf("%s converts this into an interface representation suitable for marshalling into a text or binary format.", serializeMethodName))
return
}
// 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())
ser = codegen.NewCommentedFunction(
t.packageName,
serName,
[]jen.Code{jen.Id("s").Id(t.TypeName())},
[]jen.Code{jen.Interface(), jen.Error()},
[]jen.Code{
jen.Return(
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())
deserCode := jen.Empty()
for name, prop := range t.allProperties() {
deserCode = deserCode.Add(
jen.If(
jen.List(
jen.Id("p"),
jen.Err(),
).Op(":=").Qual(prop.PackageName(), prop.DeserializeFnName()).Call(jen.Id("m")),
jen.Err().Op("!=").Nil(),
).Block(
jen.Return(jen.Nil(), jen.Err()),
).Else().Block(
jen.Id(codegen.This()).Dot(strings.Title(name)).Op("=").Op("*").Id("p"),
).Line())
}
deser = codegen.NewCommentedFunction(
t.packageName,
deserName,
[]jen.Code{jen.Id("m").Map(jen.String()).Interface()},
[]jen.Code{jen.Op("*").Id(t.TypeName()), jen.Error()},
[]jen.Code{
jen.Id(codegen.This()).Op(":=").Op("&").Id(t.TypeName()).Values(),
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())
less = codegen.NewCommentedFunction(
t.packageName,
lessName,
[]jen.Code{
jen.Id("i"),
jen.Id("j").Op("*").Id(t.TypeName()),
},
[]jen.Code{jen.Bool()},
[]jen.Code{
// 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()))
return
}