github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/gnovm/cmd/gno/run.go (about) 1 package main 2 3 import ( 4 "context" 5 "errors" 6 "flag" 7 "fmt" 8 "os" 9 "path/filepath" 10 "strings" 11 12 "github.com/gnolang/gno/gnovm/pkg/gnoenv" 13 gno "github.com/gnolang/gno/gnovm/pkg/gnolang" 14 "github.com/gnolang/gno/gnovm/tests" 15 "github.com/gnolang/gno/tm2/pkg/commands" 16 ) 17 18 type runCfg struct { 19 verbose bool 20 rootDir string 21 expr string 22 debug bool 23 debugAddr string 24 } 25 26 func newRunCmd(io commands.IO) *commands.Command { 27 cfg := &runCfg{} 28 29 return commands.NewCommand( 30 commands.Metadata{ 31 Name: "run", 32 ShortUsage: "run [flags] <file> [<file>...]", 33 ShortHelp: "runs the specified gno files", 34 }, 35 cfg, 36 func(_ context.Context, args []string) error { 37 return execRun(cfg, args, io) 38 }, 39 ) 40 } 41 42 func (c *runCfg) RegisterFlags(fs *flag.FlagSet) { 43 fs.BoolVar( 44 &c.verbose, 45 "v", 46 false, 47 "verbose output when running", 48 ) 49 50 fs.StringVar( 51 &c.rootDir, 52 "root-dir", 53 "", 54 "clone location of github.com/gnolang/gno (gno binary tries to guess it)", 55 ) 56 57 fs.StringVar( 58 &c.expr, 59 "expr", 60 "main()", 61 "value of expression to evaluate. Defaults to executing function main() with no args", 62 ) 63 64 fs.BoolVar( 65 &c.debug, 66 "debug", 67 false, 68 "enable interactive debugger using stdin and stdout", 69 ) 70 71 fs.StringVar( 72 &c.debugAddr, 73 "debug-addr", 74 "", 75 "enable interactive debugger using tcp address in the form [host]:port", 76 ) 77 } 78 79 func execRun(cfg *runCfg, args []string, io commands.IO) error { 80 if len(args) == 0 { 81 return flag.ErrHelp 82 } 83 84 if cfg.rootDir == "" { 85 cfg.rootDir = gnoenv.RootDir() 86 } 87 88 stdin := io.In() 89 stdout := io.Out() 90 stderr := io.Err() 91 92 // init store and machine 93 testStore := tests.TestStore(cfg.rootDir, 94 "", stdin, stdout, stderr, 95 tests.ImportModeStdlibsPreferred) 96 if cfg.verbose { 97 testStore.SetLogStoreOps(true) 98 } 99 100 if len(args) == 0 { 101 args = []string{"."} 102 } 103 104 // read files 105 files, err := parseFiles(args) 106 if err != nil { 107 return err 108 } 109 110 if len(files) == 0 { 111 return errors.New("no files to run") 112 } 113 114 m := gno.NewMachineWithOptions(gno.MachineOptions{ 115 PkgPath: string(files[0].PkgName), 116 Input: stdin, 117 Output: stdout, 118 Store: testStore, 119 Debug: cfg.debug || cfg.debugAddr != "", 120 }) 121 122 defer m.Release() 123 124 // If the debug address is set, the debugger waits for a remote client to connect to it. 125 if cfg.debugAddr != "" { 126 if err := m.Debugger.Serve(cfg.debugAddr); err != nil { 127 return err 128 } 129 } 130 131 // run files 132 m.RunFiles(files...) 133 runExpr(m, cfg.expr) 134 135 return nil 136 } 137 138 func parseFiles(fnames []string) ([]*gno.FileNode, error) { 139 files := make([]*gno.FileNode, 0, len(fnames)) 140 for _, fname := range fnames { 141 if s, err := os.Stat(fname); err == nil && s.IsDir() { 142 subFns, err := listNonTestFiles(fname) 143 if err != nil { 144 return nil, err 145 } 146 subFiles, err := parseFiles(subFns) 147 if err != nil { 148 return nil, err 149 } 150 files = append(files, subFiles...) 151 continue 152 } else if err != nil { 153 // either not found or some other kind of error -- 154 // in either case not a file we can parse. 155 return nil, err 156 } 157 files = append(files, gno.MustReadFile(fname)) 158 } 159 return files, nil 160 } 161 162 func listNonTestFiles(dir string) ([]string, error) { 163 fs, err := os.ReadDir(dir) 164 if err != nil { 165 return nil, err 166 } 167 fn := make([]string, 0, len(fs)) 168 for _, f := range fs { 169 n := f.Name() 170 if isGnoFile(f) && 171 !strings.HasSuffix(n, "_test.gno") && 172 !strings.HasSuffix(n, "_filetest.gno") { 173 fn = append(fn, filepath.Join(dir, n)) 174 } 175 } 176 return fn, nil 177 } 178 179 func runExpr(m *gno.Machine, expr string) { 180 defer func() { 181 if r := recover(); r != nil { 182 fmt.Printf("panic running expression %s: %v\n%s\n", 183 expr, r, m.String()) 184 panic(r) 185 } 186 }() 187 ex, err := gno.ParseExpr(expr) 188 if err != nil { 189 panic(fmt.Errorf("could not parse: %w", err)) 190 } 191 m.Eval(ex) 192 }