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  }