Skip to content

Commit

Permalink
use ast stack to find active verb/variable declaration
Browse files Browse the repository at this point in the history
  • Loading branch information
matt2e committed May 24, 2024
1 parent b9e5ae6 commit dcc26cc
Show file tree
Hide file tree
Showing 2 changed files with 193 additions and 376 deletions.
133 changes: 71 additions & 62 deletions go-runtime/compile/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,30 +144,21 @@ func ExtractModuleSchema(dir string, sch *schema.Schema) (optional.Option[ParseR
return optional.None[ParseResult](), err
}
for _, file := range pkg.Syntax {
err := goast.Visit(file, func(node ast.Node, next func() error) (err error) {
err := goast.Visit(file, func(stack []ast.Node, next func() error) (err error) {
node := stack[len(stack)-1]
switch node := node.(type) {
case *ast.CallExpr:
visitCallExpr(pctx, node)
visitCallExpr(pctx, node, stack)

case *ast.File:
visitFile(pctx, node)

case *ast.FuncDecl:
verb := visitFuncDecl(pctx, node)
pctx.activeVerb = verb
err = next()
if err != nil {
return err
}
pctx.activeVerb = nil
return nil
visitFuncDecl(pctx, node)

case *ast.GenDecl:
visitGenDecl(pctx, node)

case *ast.CommentGroup:
visitComments(pctx, node)

default:
}
return next()
Expand Down Expand Up @@ -204,8 +195,8 @@ func ExtractModuleSchema(dir string, sch *schema.Schema) (optional.Option[ParseR
// - This get's filled in with the next pass
func extractTypeDecls(pctx *parseContext) error {
for _, file := range pctx.pkg.Syntax {
err := goast.Visit(file, func(aNode ast.Node, next func() error) (err error) {
node, ok := aNode.(*ast.GenDecl)
err := goast.Visit(file, func(stack []ast.Node, next func() error) (err error) {
node, ok := (stack[len(stack)-1]).(*ast.GenDecl)
if !ok || node.Tok != token.TYPE {
return next()
}
Expand Down Expand Up @@ -278,10 +269,10 @@ func extractTypeDeclsForNode(pctx *parseContext, node *ast.GenDecl) {
Name: strcase.ToUpperCamel(t.Name.Name),
Export: dir.IsExported(),
}
if typ, ok := typ.(*types.Interface); ok {
if iTyp, ok := typ.(*types.Interface); ok {
pctx.nativeNames[enum] = nativeName
pctx.module.Decls = append(pctx.module.Decls, enum)
pctx.enumInterfaces[t.Name.Name] = typ
pctx.enumInterfaces[t.Name.Name] = iTyp
} else {
pctx.errors.add(errorf(node, "expected interface for type enum but got %q", typ))
}
Expand Down Expand Up @@ -310,29 +301,47 @@ func extractTypeDeclsForNode(pctx *parseContext, node *ast.GenDecl) {
}
}

func visitCallExpr(pctx *parseContext, node *ast.CallExpr) {
func visitCallExpr(pctx *parseContext, node *ast.CallExpr, stack []ast.Node) {
_, fn := deref[*types.Func](pctx.pkg, node.Fun)
if fn == nil {
return
}
switch fn.FullName() {
case ftlCallFuncPath:
parseCall(pctx, node)
parseCall(pctx, node, stack)

case ftlConfigFuncPath, ftlSecretFuncPath:
// Secret/config declaration: ftl.Config[<type>](<name>)
parseConfigDecl(pctx, node, fn)

case ftlFSMFuncPath:
parseFSMDecl(pctx, node)
parseFSMDecl(pctx, node, stack)

case ftlPostgresDBFuncPath:
parseDatabaseDecl(pctx, node, schema.PostgresDatabaseType)
}
}

func parseCall(pctx *parseContext, node *ast.CallExpr) {
if pctx.activeVerb == nil {
func parseCall(pctx *parseContext, node *ast.CallExpr, stack []ast.Node) {
var activeFuncDecl *ast.FuncDecl
for i := len(stack) - 1; i >= 0; i-- {
if found, ok := stack[i].(*ast.FuncDecl); ok {
activeFuncDecl = found
break
}
// use element
}
if activeFuncDecl == nil {
return
}
var activeVerb *schema.Verb
for _, decl := range pctx.module.Decls {
if aVerb, ok := decl.(*schema.Verb); ok && aVerb.Name == activeFuncDecl.Name.Name {
activeVerb = aVerb
break
}
}
if activeVerb == nil {
return
}
if len(node.Args) != 3 {
Expand All @@ -352,7 +361,7 @@ func parseCall(pctx *parseContext, node *ast.CallExpr) {
pctx.errors.add(errorf(node.Args[1], "call first argument must be a function in an ftl module%s", suffix))
return
}
pctx.activeVerb.AddCall(ref)
activeVerb.AddCall(ref)
}

func parseSelectorRef(node ast.Expr) *schema.Ref {
Expand Down Expand Up @@ -388,7 +397,7 @@ func parseVerbRef(pctx *parseContext, node ast.Expr) *schema.Ref {
}
}

func parseFSMDecl(pctx *parseContext, node *ast.CallExpr) {
func parseFSMDecl(pctx *parseContext, node *ast.CallExpr, stack []ast.Node) {
var literal *ast.BasicLit
if len(node.Args) > 0 {
literal, _ = node.Args[0].(*ast.BasicLit)
Expand Down Expand Up @@ -428,22 +437,31 @@ func parseFSMDecl(pctx *parseContext, node *ast.CallExpr) {
parseFSMTransition(pctx, call, fn, fsm)
}

if commentGroup, ok := pctx.commentNodeByLine[fset.Position(node.Pos()).Line-1]; ok {
directives, err := parseDirectives(node, fset, commentGroup)
if err != nil {
pctx.errors.add(err)
// find variable so we can look for attached directives
var variableDecl *ast.GenDecl
for i := len(stack) - 1; i >= 0; i-- {
if decl, ok := stack[i].(*ast.GenDecl); ok && decl.Tok == token.VAR {
variableDecl = decl
break
}
for _, dir := range directives {
if dir, ok := dir.(*directiveRetry); ok {
fsm.Metadata = append(fsm.Metadata, &schema.MetadataRetry{
Pos: dir.Pos,
Count: dir.Count,
MinBackoff: dir.MinBackoff,
MaxBackoff: dir.MaxBackoff,
})
} else {
pctx.errors.add(errorf(node, "unexpected directive %T", dir))
}
}
if variableDecl == nil || variableDecl.Doc == nil {
return
}
directives, schemaErr := parseDirectives(node, fset, variableDecl.Doc)
if schemaErr != nil {
pctx.errors.add(schemaErr)
}
for _, dir := range directives {
if retryDir, ok := dir.(*directiveRetry); ok {
fsm.Metadata = append(fsm.Metadata, &schema.MetadataRetry{
Pos: retryDir.Pos,
Count: retryDir.Count,
MinBackoff: retryDir.MinBackoff,
MaxBackoff: retryDir.MaxBackoff,
})
} else {
pctx.errors.add(errorf(node, "unexpected directive %T", dir))
}
}
}
Expand Down Expand Up @@ -1092,12 +1110,6 @@ func visitFuncDecl(pctx *parseContext, node *ast.FuncDecl) (verb *schema.Verb) {
return verb
}

func visitComments(pctx *parseContext, node *ast.CommentGroup) {
for line := fset.Position(node.Pos()).Line; line <= fset.Position(node.End()).Line; line++ {
pctx.commentNodeByLine[line] = node
}
}

func parseComments(doc *ast.CommentGroup) []string {
comments := []string{}
if doc := doc.Text(); doc != "" {
Expand Down Expand Up @@ -1496,27 +1508,24 @@ func deref[T types.Object](pkg *packages.Package, node ast.Expr) (string, T) {
}

type parseContext struct {
pkg *packages.Package
pkgs []*packages.Package
module *schema.Module
nativeNames NativeNames
enumInterfaces enumInterfaces
activeVerb *schema.Verb
commentNodeByLine map[int]*ast.CommentGroup
errors errorSet
schema *schema.Schema
pkg *packages.Package
pkgs []*packages.Package
module *schema.Module
nativeNames NativeNames
enumInterfaces enumInterfaces
errors errorSet
schema *schema.Schema
}

func newParseContext(pkg *packages.Package, pkgs []*packages.Package, module *schema.Module, sch *schema.Schema) *parseContext {
return &parseContext{
pkg: pkg,
pkgs: pkgs,
module: module,
nativeNames: NativeNames{},
enumInterfaces: enumInterfaces{},
commentNodeByLine: map[int]*ast.CommentGroup{},
errors: errorSet{},
schema: sch,
pkg: pkg,
pkgs: pkgs,
module: module,
nativeNames: NativeNames{},
enumInterfaces: enumInterfaces{},
errors: errorSet{},
schema: sch,
}
}

Expand Down
Loading

0 comments on commit dcc26cc

Please sign in to comment.