From ce36bc72c9c5a762474654fcd9eeb2998a116a47 Mon Sep 17 00:00:00 2001 From: Ethan Reesor Date: Tue, 13 Apr 2021 01:30:54 -0500 Subject: [PATCH] interp: produce meaningful output for file statements --- go.sum | 0 interp/interp.go | 38 +++++++++++++++++++++++++++ interp/result.go | 30 ++++++++++++++++++++++ interp/result_test.go | 60 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 128 insertions(+) create mode 100644 go.sum create mode 100644 interp/result.go create mode 100644 interp/result_test.go diff --git a/go.sum b/go.sum new file mode 100644 index 000000000..e69de29bb diff --git a/interp/interp.go b/interp/interp.go index 8109fba00..3ccb726c3 100644 --- a/interp/interp.go +++ b/interp/interp.go @@ -500,6 +500,40 @@ func isFile(filesystem fs.FS, path string) bool { return err == nil && fi.Mode().IsRegular() } +func getFileResult(n *node) *FileResult { + res := new(FileResult) + + n.Walk(func(n *node) bool { + var sres FileStatementResult + switch n.kind { + case fileStmt, importDecl, typeDecl: + return true + + case importSpec: + if len(n.child) == 2 { + sres = &PackageImportResult{ + Name: n.child[0].ident, + Path: constToString(n.child[1].rval), + } + } else { + sres = &PackageImportResult{Path: constToString(n.child[0].rval)} + } + case funcDecl: + sres = &FunctionDeclarationResult{Name: n.child[1].ident} + case typeSpec: + sres = &TypeDeclarationResult{Name: n.child[0].ident} + default: + return false + } + if sres != nil { + res.Statements = append(res.Statements, sres) + } + return false + }, nil) + + return res +} + func (interp *Interpreter) eval(src, name string, inc bool) (res reflect.Value, err error) { if name != "" { interp.name = name @@ -617,6 +651,10 @@ func (interp *Interpreter) eval(src, name string, inc bool) (res reflect.Value, } } + if root.kind == fileStmt { + res = reflect.ValueOf(getFileResult(root)) + } + return res, err } diff --git a/interp/result.go b/interp/result.go new file mode 100644 index 000000000..3d97c7b62 --- /dev/null +++ b/interp/result.go @@ -0,0 +1,30 @@ +package interp + +// FileResult is the result of evaluating a file-like source. +type FileResult struct { + Statements []FileStatementResult +} + +// FileStatementResult is the result of a top-level statement in a file-like source. +type FileStatementResult interface { + isFileStatementResult() +} + +func (*PackageImportResult) isFileStatementResult() {} +func (*FunctionDeclarationResult) isFileStatementResult() {} +func (*TypeDeclarationResult) isFileStatementResult() {} + +// PackageImportResult is the result of a package import statement. +type PackageImportResult struct { + Name, Path string +} + +// FunctionDeclarationResult is the result of a top-level function declaration statement. +type FunctionDeclarationResult struct { + Name string +} + +// TypeDeclarationResult is the result of a top-level type declaration statement. +type TypeDeclarationResult struct { + Name string +} diff --git a/interp/result_test.go b/interp/result_test.go new file mode 100644 index 000000000..b5d556a01 --- /dev/null +++ b/interp/result_test.go @@ -0,0 +1,60 @@ +package interp_test + +import ( + "reflect" + "testing" + + "github.com/traefik/yaegi/interp" + "github.com/traefik/yaegi/stdlib" +) + +type resultTestCase struct { + desc, src string + res interface{} +} + +func TestEvalFileResult(t *testing.T) { + type Results = []interp.FileStatementResult + type Import = interp.PackageImportResult + type Func = interp.FunctionDeclarationResult + type Type = interp.TypeDeclarationResult + + i := interp.New(interp.Options{}) + _ = i.Use(stdlib.Symbols) + runResultTests(t, i, []resultTestCase{ + {desc: "bare import", src: `import "time"`, res: &Import{Path: "time"}}, + {desc: "named import", src: `import x "time"`, res: &Import{Name: "x", Path: "time"}}, + {desc: "multiple imports", src: "import (\ny \"time\"\nz \"fmt\"\n)", res: Results{&Import{Name: "y", Path: "time"}, &Import{Name: "z", Path: "fmt"}}}, + + {desc: "func", src: `func foo() { }`, res: &Func{Name: "foo"}}, + + {desc: "struct type", src: `type Foo struct {}`, res: &Type{Name: "Foo"}}, + }) +} + +func runResultTests(t *testing.T, i *interp.Interpreter, tests []resultTestCase) { + t.Helper() + + for _, test := range tests { + expected := test.res + if stmtResult, ok := expected.(interp.FileStatementResult); ok { + expected = []interp.FileStatementResult{stmtResult} + } + if stmtResults, ok := expected.([]interp.FileStatementResult); ok { + expected = &interp.FileResult{Statements: stmtResults} + } + + t.Run(test.desc, func(t *testing.T) { + res, err := i.Eval(test.src) + if err != nil { + t.Fatal(err) + } + if !res.IsValid() { + t.Fatal("Result is not valid") + } + if !reflect.DeepEqual(expected, res.Interface()) { + t.Fatalf("Got %v, expected %v", res, expected) + } + }) + } +}