From 355e2bd106e5ae89c0b001954db9f981d6ede1e3 Mon Sep 17 00:00:00 2001 From: Cory Slep Date: Sun, 20 Jan 2019 00:01:54 +0100 Subject: [PATCH] Initial resolver outline. Need to: - Hook into convert package - Add comments everywhere to generated code - Add version that supports navigating AS hierarchy (or applies interface-first instead of type-first). --- tools/exp/gen/resolver.go | 497 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 497 insertions(+) create mode 100644 tools/exp/gen/resolver.go diff --git a/tools/exp/gen/resolver.go b/tools/exp/gen/resolver.go new file mode 100644 index 0000000..d9b2654 --- /dev/null +++ b/tools/exp/gen/resolver.go @@ -0,0 +1,497 @@ +package gen + +import ( + "fmt" + "github.com/cjslep/activity/tools/exp/codegen" + "github.com/dave/jennifer/jen" + "sync" +) + +const ( + typeResolverStructName = "TypeResolver" + predicateResolverStructName = "PredicateResolver" + resolveMethod = "Resolve" + resolveMethodPrivate = "resolve" + applyMethod = "Apply" + activityStreamInterface = "ActivityStreamsInterface" + callbackMember = "callbacks" + predicateMember = "predicate" + errorNoMatch = "ErrNoCallbackMatch" + errorUnhandled = "ErrUnknownType" + errorPredicateUnmatched = "ErrPredicateUnmatched" + errorCannotTypeAssert = "errCannotTypeAssertType" + errorCannotTypeAssertPredicate = "errCannotTypeAssertPredicate" + isUnFnName = "IsUnknownOrUnmatchedErr" +) + +// TODO: Interface-driven resolvers. For hierarchy. + +// ResolverGenerator generates the code required for the TypeResolver and the +// PredicateResolver. +type ResolverGenerator struct { + pkg Package + types []*TypeGenerator + cacheOnce sync.Once + cachedPredicate *codegen.Struct + cachedType *codegen.Struct + cachedErrNoMatch jen.Code + cachedErrUnhandled jen.Code + cachedErrPredicateUnmatched jen.Code + cachedErrCannotTypeAssert jen.Code + cachedErrCannotTypeAssertPredicate jen.Code + cachedIsUnFn *codegen.Function + cachedASInterface *codegen.Interface +} + +// Creates a new ResolverGenerator for generating all the methods, functions, +// errors, interface, and struct types needed for them. +// +// Must be constructed after all TypeGenerators. +func NewResolverGenerator( + tgs []*TypeGenerator, + pkg Package) *ResolverGenerator { + return &ResolverGenerator{ + pkg: pkg, + types: tgs, + } +} + +// Definition returns the TypeResolver and PredicateResolver. +func (r *ResolverGenerator) Definition() (types, predicates *codegen.Struct, errs []jen.Code, isUnFn *codegen.Function, asInterface *codegen.Interface) { + r.cacheOnce.Do(func() { + r.cachedType = codegen.NewStruct( + // TODO: Comment + fmt.Sprintf("", typeResolverStructName), + typeResolverStructName, + r.typeResolverMethods(), + r.typeResolverFunctions(), + r.typeResolverMembers()) + r.cachedPredicate = codegen.NewStruct( + // TODO: Comment + fmt.Sprintf("", predicateResolverStructName), + predicateResolverStructName, + r.predicateResolverMethods(), + r.predicateResolverFunctions(), + r.predicateResolverMembers()) + r.cachedErrNoMatch = r.errorNoMatch() + r.cachedErrUnhandled = r.errorUnhandled() + r.cachedErrPredicateUnmatched = r.errorPredicateUnmatched() + r.cachedErrCannotTypeAssert = r.errorCannotTypeAssert() + r.cachedErrCannotTypeAssertPredicate = r.errorCannotTypeAssertPredicate() + r.cachedIsUnFn = r.isUnFn() + r.cachedASInterface = r.asInterface() + }) + return r.cachedType, r.cachedPredicate, []jen.Code{ + r.cachedErrNoMatch, + r.cachedErrUnhandled, + r.cachedErrPredicateUnmatched, + r.cachedErrCannotTypeAssert, + r.cachedErrCannotTypeAssertPredicate, + }, r.cachedIsUnFn, r.cachedASInterface +} + +// errorNoMatch returns the declaration for the ErrNoMatch global value. +func (r *ResolverGenerator) errorNoMatch() jen.Code { + // TODO: Comment + return jen.Var().Id(errorNoMatch).Error().Op("=").Qual("errors", "New").Call(jen.Lit("activity stream did not match the callback function")) +} + +// errorUnhandled returns the declaration for the ErrUnhandled global value. +func (r *ResolverGenerator) errorUnhandled() jen.Code { + // TODO: Comment + return jen.Var().Id(errorUnhandled).Error().Op("=").Qual("errors", "New").Call(jen.Lit("activity stream did not match any known types")) +} + +// errorCannotTypeAssert returns the declaration for the errCannotTypeAssert +// global value. +func (r *ResolverGenerator) errorCannotTypeAssert() jen.Code { + // TODO: Comment + return jen.Var().Id(errorCannotTypeAssert).Error().Op("=").Qual("errors", "New").Call(jen.Lit("activity stream type cannot be asserted to its interface")) +} + +// errorCannotTypeAssertPredicate returns the declaration for the +// errCannotTypeAssert global value. +func (r *ResolverGenerator) errorCannotTypeAssertPredicate() jen.Code { + // TODO: Comment + return jen.Var().Id(errorCannotTypeAssertPredicate).Error().Op("=").Qual("errors", "New").Call(jen.Lit("predicate cannot be type asserted to a known function type")) +} + +// errorPredicateUnmatched returns the declaration for the ErrPredicateUnmatched +// global value. +func (r *ResolverGenerator) errorPredicateUnmatched() jen.Code { + // TODO: Comment + return jen.Var().Id(errorPredicateUnmatched).Error().Op("=").Qual("errors", "New").Call(jen.Lit("activity stream did not match type demanded by predicate")) +} + +// isUnFn returns a function that returns true if an error is one dealing with +// Unmatched or Unhandled errors. +func (r *ResolverGenerator) isUnFn() *codegen.Function { + return codegen.NewCommentedFunction( + r.pkg.Path(), + isUnFnName, + []jen.Code{ + jen.Err().Error(), + }, + []jen.Code{ + jen.Bool(), + }, + []jen.Code{ + jen.Return( + jen.Err().Op("==").Id(errorPredicateUnmatched).Op( + "||", + ).Err().Op("==").Id(errorUnhandled).Op( + "||", + ).Err().Op("==").Id(errorNoMatch), + ), + }, + // TODO: Comment + fmt.Sprintf("%s", isUnFnName)) + +} + +// typeResolverMethods returns the methods for the TypeResolver. +func (r *ResolverGenerator) typeResolverMethods() (m []*codegen.Method) { + m = append(m, r.resolveMethod(resolveMethod, typeResolverStructName)) + return +} + +// predicateResolverMethods returns the methods for the PredicateResolver. +func (r *ResolverGenerator) predicateResolverMethods() (m []*codegen.Method) { + m = append(m, r.applyMethod()) + m = append(m, r.resolveMethod(resolveMethodPrivate, predicateResolverStructName)) + return +} + +// typeResolverFunctions returns the functions for the TypeResolver. +func (r *ResolverGenerator) typeResolverFunctions() (f []*codegen.Function) { + f = append(f, codegen.NewCommentedFunction( + r.pkg.Path(), + fmt.Sprintf("%s%s", constructorName, typeResolverStructName), + []jen.Code{ + jen.Id("callbacks").Index().Interface(), + }, + []jen.Code{ + jen.Op("*").Id(typeResolverStructName), + jen.Error(), + }, + []jen.Code{ + jen.For( + jen.List( + jen.Id("_"), + jen.Id("cb"), + ).Op(":=").Range().Id("callbacks"), + ).Block( + jen.Commentf("Each callback function must satisfy one known function signature, or else we will generate a runtime error instead of silently fail."), + jen.Switch( + jen.Id("cb").Assert(jen.Type()), + ).Block( + r.mustAssertToKnownTypes("cb"), + ), + ), + jen.Return( + jen.Op("&").Id(typeResolverStructName).Values( + jen.Dict{ + jen.Id(callbackMember): jen.Id("callbacks"), + }, + ), + jen.Nil(), + ), + }, + // TODO: Comment + fmt.Sprintf("%s%s", constructorName, typeResolverStructName))) + return +} + +// predicateResolverFunctions returns the functions for the PredicateResolver. +func (r *ResolverGenerator) predicateResolverFunctions() (f []*codegen.Function) { + f = append(f, codegen.NewCommentedFunction( + r.pkg.Path(), + fmt.Sprintf("%s%s", constructorName, predicateResolverStructName), + []jen.Code{ + jen.Id("callbacks").Index().Interface(), + jen.Id("predicate").Interface(), + }, + []jen.Code{ + jen.Op("*").Id(predicateResolverStructName), + jen.Error(), + }, + []jen.Code{ + jen.For( + jen.List( + jen.Id("_"), + jen.Id("cb"), + ).Op(":=").Range().Id("callbacks"), + ).Block( + jen.Commentf("Each callback function must satisfy one known function signature, or else we will generate a runtime error instead of silently fail."), + jen.Switch( + jen.Id("cb").Assert(jen.Type()), + ).Block( + r.mustAssertToKnownTypes("cb"), + ), + ), + jen.Commentf("The predicate must satisfy one known predicate function signature, or else we will generate a runtime error instead of silently fail."), + jen.Switch( + jen.Id("predicate").Assert(jen.Type()), + ).Block( + r.mustAssertToKnownPredicate("predicate"), + ), + jen.Return( + jen.Op("&").Id(typeResolverStructName).Values( + jen.Dict{ + jen.Id(callbackMember): jen.Id("callbacks"), + jen.Id(predicateMember): jen.Id("predicate"), + }, + ), + jen.Nil(), + ), + }, + // TODO: Comment + fmt.Sprintf("%s%s", constructorName, predicateResolverStructName))) + return +} + +// typeResolverMembers returns the members for the TypeResolver. +func (r *ResolverGenerator) typeResolverMembers() (m []jen.Code) { + m = append(m, jen.Id(callbackMember).Index().Interface()) + return +} + +// predicateResolverMembers returns the members for the PredicateResolver. +func (r *ResolverGenerator) predicateResolverMembers() (m []jen.Code) { + m = append(m, jen.Id(callbackMember).Index().Interface()) + m = append(m, jen.Id(predicateMember).Interface()) + return +} + +// resolveMethod returns the Resolve method. +func (r *ResolverGenerator) resolveMethod(name, structName string) *codegen.Method { + impl := jen.Empty() + for _, t := range r.types { + impl = impl.Case( + jen.Lit(t.TypeName()), + ).Block( + jen.If( + jen.List( + jen.Id("fn"), + jen.Id("ok"), + ).Op(":=").Id("i").Assert( + jen.Func().Parens( + jen.List( + jen.Qual("context", "Context"), + jen.Qual(t.PublicPackage().Path(), t.InterfaceName()), + ), + ).Error(), + ), + jen.Id("ok"), + ).Block( + jen.If( + jen.List( + jen.Id("v"), + jen.Id("ok"), + ).Op(":=").Id("o").Assert( + jen.Qual(t.PublicPackage().Path(), t.InterfaceName()), + ), + jen.Id("ok"), + ).Block( + jen.Return( + jen.Id("fn").Call(jen.Id("ctx"), jen.Id("v")), + ), + ).Else().Block( + jen.Commentf("This occurs when the implementation is either not a go-fed type and is improperly satisfying various interfaces, or there is a bug in the go-fed generated code."), + jen.Return( + jen.Id(errorCannotTypeAssert), + ), + ), + ), + ).Line() + } + return codegen.NewCommentedValueMethod( + r.pkg.Path(), + name, + structName, + []jen.Code{ + jen.Id("ctx").Qual("context", "Context"), + jen.Id("o").Id(activityStreamInterface), + }, + []jen.Code{ + jen.Error(), + }, + []jen.Code{ + jen.For( + jen.List( + jen.Id("_"), + jen.Id("i"), + ).Op(":=").Range().Id(codegen.This()).Id(callbackMember), + ).Block( + jen.Switch(jen.Id("o").Dot(typeNameMethod).Call()).Block( + impl.Default().Block( + jen.Return( + jen.Id(errorUnhandled), + ), + ), + ), + ), + jen.Return( + jen.Id(errorNoMatch), + ), + }, + // TODO: Comment + fmt.Sprintf("", resolveMethod)) +} + +// mustAssertToKnownTypes creates the type assertion switch statement that will +// return an error if the parameter named does not match any of the expected +// function signatures. +func (r *ResolverGenerator) mustAssertToKnownTypes(paramName string) jen.Code { + c := jen.Empty() + for _, t := range r.types { + c = c.Case( + jen.Func().Parens( + jen.List( + jen.Qual("context", "Context"), + jen.Qual(t.PublicPackage().Path(), t.InterfaceName()), + ), + ).Error(), + ).Block( + jen.Commentf("Do nothing, this callback has a correct signature."), + ).Line() + } + c = c.Default().Block( + jen.Return( + jen.Nil(), + jen.Qual("errors", "New").Call(jen.Lit("a callback function is of the wrong signature and would never be called")), + ), + ) + return c +} + +// applyMethod generates the code required for the Apply method. +func (r *ResolverGenerator) applyMethod() *codegen.Method { + impl := jen.Empty() + for _, t := range r.types { + impl = impl.Case( + jen.Func().Parens( + jen.List( + jen.Qual("context", "Context"), + jen.Qual(t.PublicPackage().Path(), t.InterfaceName()), + ), + ).Parens( + jen.List( + jen.Bool(), + jen.Error(), + ), + ), + ).Block( + jen.If( + jen.List( + jen.Id("v"), + jen.Id("ok"), + ).Op(":=").Id("o").Assert( + jen.Qual(t.PublicPackage().Path(), t.InterfaceName()), + ), + jen.Id("ok"), + ).Block( + jen.List( + jen.Id("predicatePasses"), + jen.Err(), + ).Op("=").Id("fn").Call(jen.Id("ctx"), jen.Id("v")), + ).Else().Block( + jen.Return( + jen.False(), + jen.Id(errorPredicateUnmatched), + ), + ), + ).Line() + } + return codegen.NewCommentedValueMethod( + r.pkg.Path(), + applyMethod, + predicateResolverStructName, + []jen.Code{ + jen.Id("ctx").Qual("context", "Context"), + jen.Id("o").Id(activityStreamInterface), + }, + []jen.Code{ + jen.Bool(), + jen.Error(), + }, + []jen.Code{ + jen.Var().Id("predicatePasses").Bool(), + jen.Var().Err().Error(), + jen.Switch(jen.Id("fn").Op(":=").Id(codegen.This()).Dot(predicateMember).Assert(jen.Type())).Block( + impl.Default().Block( + jen.Commentf("The constructor should guard against this error. If it is encountered, then there is a bug in the code generator."), + jen.Return( + jen.False(), + jen.Id(errorCannotTypeAssertPredicate), + ), + ), + ), + jen.If( + jen.Id("predicatePasses"), + ).Block( + jen.Return( + jen.True(), + jen.Id(codegen.This()).Dot(resolveMethodPrivate).Call( + jen.Id("ctx"), + jen.Id("o"), + ), + ), + ).Else().Block( + jen.Return( + jen.False(), + jen.Nil(), + ), + ), + }, + // TODO: Comment + fmt.Sprintf("%s", applyMethod)) +} + +// mustAssertToKnownPredicate ensures the parameter name types-asserts to a +// known signature, or returns an error. +func (r *ResolverGenerator) mustAssertToKnownPredicate(paramName string) jen.Code { + c := jen.Empty() + for _, t := range r.types { + c = c.Case( + jen.Func().Parens( + jen.List( + jen.Qual("context", "Context"), + jen.Qual(t.PublicPackage().Path(), t.InterfaceName()), + ), + ).Parens( + jen.List( + jen.Bool(), + jen.Error(), + ), + ), + ).Block( + jen.Commentf("Do nothing, this predicate has a correct signature."), + ).Line() + } + c = c.Default().Block( + jen.Return( + jen.Nil(), + jen.Qual("errors", "New").Call(jen.Lit("the predicate function is of the wrong signature and would never be called")), + ), + ) + return c +} + +// asInterface returns the ActivityStreamsInterface. +func (r *ResolverGenerator) asInterface() *codegen.Interface { + return codegen.NewInterface( + r.pkg.Path(), + activityStreamInterface, + []codegen.FunctionSignature{ + { + Name: typeNameMethod, + Params: nil, + Ret: []jen.Code{jen.String()}, + // TODO: Comment + Comment: fmt.Sprintf("%s", typeNameMethod), + }, + }, + // TODO: Comment + fmt.Sprintf("%s", activityStreamInterface)) +}