diff --git a/gnovm/cmd/gno/run.go b/gnovm/cmd/gno/run.go index f0c4a1fb290..2c8d8c77350 100644 --- a/gnovm/cmd/gno/run.go +++ b/gnovm/cmd/gno/run.go @@ -2,7 +2,12 @@ package main import ( "context" + "errors" "flag" + "fmt" + "os" + "path/filepath" + "strings" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" "github.com/gnolang/gno/gnovm/tests" @@ -12,6 +17,7 @@ import ( type runCfg struct { verbose bool rootDir string + expr string } func newRunCmd(io *commands.IO) *commands.Command { @@ -44,6 +50,13 @@ func (c *runCfg) RegisterFlags(fs *flag.FlagSet) { "", "clone location of github.com/gnolang/gno (gnodev tries to guess it)", ) + + fs.StringVar( + &c.expr, + "expr", + "main()", + "value of expression to evaluate. Defaults to executing function main() with no args", + ) } func execRun(cfg *runCfg, args []string, io *commands.IO) error { @@ -67,23 +80,87 @@ func execRun(cfg *runCfg, args []string, io *commands.IO) error { testStore.SetLogStoreOps(true) } + if len(args) == 0 { + args = []string{"."} + } + + // read files + files, err := parseFiles(args) + if err != nil { + return err + } + + if len(files) == 0 { + return errors.New("no files to run") + } + m := gno.NewMachineWithOptions(gno.MachineOptions{ - PkgPath: "main", + PkgPath: string(files[0].PkgName), Output: stdout, Store: testStore, }) defer m.Release() - // read files - files := make([]*gno.FileNode, len(args)) - for i, fname := range args { - files[i] = gno.MustReadFile(fname) - } - // run files m.RunFiles(files...) - m.RunMain() + runExpr(m, cfg.expr) return nil } + +func parseFiles(fnames []string) ([]*gno.FileNode, error) { + files := make([]*gno.FileNode, 0, len(fnames)) + for _, fname := range fnames { + if s, err := os.Stat(fname); err == nil && s.IsDir() { + subFns, err := listNonTestFiles(fname) + if err != nil { + return nil, err + } + subFiles, err := parseFiles(subFns) + if err != nil { + return nil, err + } + files = append(files, subFiles...) + continue + } else if err != nil { + // either not found or some other kind of error -- + // in either case not a file we can parse. + return nil, err + } + files = append(files, gno.MustReadFile(fname)) + } + return files, nil +} + +func listNonTestFiles(dir string) ([]string, error) { + fs, err := os.ReadDir(dir) + if err != nil { + return nil, err + } + fn := make([]string, 0, len(fs)) + for _, f := range fs { + n := f.Name() + if isGnoFile(f) && + !strings.HasSuffix(n, "_test.gno") && + !strings.HasPrefix(n, "_filetest.gno") { + fn = append(fn, filepath.Join(dir, n)) + } + } + return fn, nil +} + +func runExpr(m *gno.Machine, expr string) { + defer func() { + if r := recover(); r != nil { + fmt.Printf("panic running expression %s: %v\n%s\n", + expr, r, m.String()) + panic(r) + } + }() + ex, err := gno.ParseExpr(expr) + if err != nil { + panic(fmt.Errorf("could not parse: %w", err)) + } + m.Eval(ex) +} diff --git a/gnovm/cmd/gno/run_test.go b/gnovm/cmd/gno/run_test.go index bccd7d1190c..e439a6bad8d 100644 --- a/gnovm/cmd/gno/run_test.go +++ b/gnovm/cmd/gno/run_test.go @@ -13,20 +13,57 @@ func TestRunApp(t *testing.T) { stdoutShouldContain: "hello world!", }, { - args: []string{"run", "../../tests/integ/run-main/"}, - recoverShouldContain: "read ../../tests/integ/run-main/: is a directory", // FIXME: should work + args: []string{"run", "../../tests/integ/run-main/"}, + stdoutShouldContain: "hello world!", + }, + { + args: []string{"run", "../../tests/integ/does-not-exist"}, + errShouldContain: "no such file or directory", + }, + { + args: []string{"run", "../../tests/integ/run-namedpkg/main.gno"}, + stdoutShouldContain: "hello, other world!", + }, + { + args: []string{"run", "../../tests/integ/run-package"}, + recoverShouldContain: "name main not declared", + }, + { + args: []string{"run", "-expr", "Hello()", "../../tests/integ/run-package"}, + stdoutShouldContain: "called Hello", + }, + { + args: []string{"run", "-expr", "World()", "../../tests/integ/run-package"}, + stdoutShouldContain: "called World", + }, + { + args: []string{"run", "-expr", "otherFile()", "../../tests/integ/run-package"}, + stdoutShouldContain: "hello from package2.gno", + }, + { + args: []string{ + "run", "-expr", "otherFile()", + "../../tests/integ/run-package/package.gno", + }, + recoverShouldContain: "name otherFile not declared", + }, + { + args: []string{ + "run", "-expr", "otherFile()", + "../../tests/integ/run-package/package.gno", + "../../tests/integ/run-package/package2.gno", + }, + stdoutShouldContain: "hello from package2.gno", }, { - args: []string{"run", "../../tests/integ/does-not-exist"}, - recoverShouldContain: "open ../../tests/integ/does-not-exist: no such file or directory", + args: []string{"run", "-expr", "WithArg(1)", "../../tests/integ/run-package"}, + stdoutShouldContain: "one", }, { - args: []string{"run", "../../tests/integ/run-namedpkg/main.gno"}, - recoverShouldContain: "expected package name [main] but got [namedpkg]", // FIXME: should work + args: []string{"run", "-expr", "WithArg(-255)", "../../tests/integ/run-package"}, + stdoutShouldContain: "out of range!", }, - // TODO: multiple files // TODO: a test file - // TODO: a file without main // TODO: args // TODO: nativeLibs VS stdlibs // TODO: with gas meter diff --git a/gnovm/tests/integ/run-namedpkg/main.gno b/gnovm/tests/integ/run-namedpkg/main.gno index 1fe31ed6549..0f1a2476004 100644 --- a/gnovm/tests/integ/run-namedpkg/main.gno +++ b/gnovm/tests/integ/run-namedpkg/main.gno @@ -1,5 +1,5 @@ package namedpkg func main() { - println("hello world!") + println("hello, other world!") } diff --git a/gnovm/tests/integ/run-package/package.gno b/gnovm/tests/integ/run-package/package.gno new file mode 100644 index 00000000000..ebf4ccf4ec4 --- /dev/null +++ b/gnovm/tests/integ/run-package/package.gno @@ -0,0 +1,26 @@ +package pkg + +func Hello() { + println("called Hello") +} + +func World() { + println("called World") +} + +func hello() { + println("called hello") +} + +func WithArg(i int) { + switch i { + case 0: + println("zero") + case 1: + println("one") + case 2: + println("two") + default: + println("out of range! :S") + } +} diff --git a/gnovm/tests/integ/run-package/package2.gno b/gnovm/tests/integ/run-package/package2.gno new file mode 100644 index 00000000000..7b8b1ae6b31 --- /dev/null +++ b/gnovm/tests/integ/run-package/package2.gno @@ -0,0 +1,5 @@ +package pkg + +func otherFile() { + println("hello from package2.gno!") +}