package main import ( "context" "errors" "fmt" "os" "runtime" "syscall" "git.sr.ht/~mna/snow/cmd/internal/flag" "git.sr.ht/~mna/snow/cmd/internal/mainer" ) var ( // placeholder values, replaced on build version = "{v}" buildDate = "{d}" ) const binName = "snowc" var ( shortUsage = fmt.Sprintf(` usage: %s [...] Run '%[1]s --help' for details. `, binName) longUsage = fmt.Sprintf(`usage: %s [...] [-- ...] %[1]s -h|--help %[1]s -v|--version Compiler for the snow programming language. The can be one of: tokenize Print the stream of tokens parse Print the AST typecheck Print the type-annotated AST codegen Generate the backend-specific code build Invoke the backend compiler to generate a binary or library run Build and execute the resulting binary -h --help Show this help -v --version Print version `, binName) ) var commands = map[string]func(context.Context, []string) error{ "tokenize": nil, "parse": nil, "typecheck": nil, "codegen": nil, "build": nil, "run": nil, } type cmd struct { Help bool `flag:"h,help" ignored:"true"` Version bool `flag:"v,version" ignored:"true"` args []string flags map[string]bool cmdFn func(context.Context, []string) error // for tests only: set a context that will be used as top-level. testCtx context.Context } func (c *cmd) SetArgs(args []string) { c.args = args } func (c *cmd) SetFlags(flags map[string]bool) { c.flags = flags } func (c *cmd) Validate() error { if c.Help || c.Version { return nil } if len(c.args) == 0 { return errors.New("no command specified") } c.cmdFn = commands[c.args[0]] if c.cmdFn == nil { return fmt.Errorf("unknown command: %s", c.args[0]) } return nil } func (c *cmd) Main(args []string, stdio *mainer.Stdio) mainer.ExitCode { var p flag.Parser if err := p.Parse(args, c); err != nil { fmt.Fprintf(stdio.Stderr, "invalid arguments: %s\n%s", err, shortUsage) return mainer.InvalidArgs } switch { case c.Help: fmt.Fprint(stdio.Stdout, longUsage) return mainer.Success case c.Version: fmt.Fprintf(stdio.Stdout, "%s %s %s %s\n", version, buildDate, runtime.GOOS, runtime.GOARCH) return mainer.Success } ctx := context.Background() if c.testCtx != nil { ctx = c.testCtx } ctx = mainer.CancelOnSignal(ctx, syscall.SIGINT, syscall.SIGTERM) if err := c.cmdFn(ctx, c.args[1:]); err != nil { // each command takes care of printing its errors, just return with an error code return mainer.Failure } return mainer.Success } func main() { var c cmd os.Exit(int(c.Main(os.Args, mainer.CurrentStdio()))) }