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 <command> [<path>...]
Run '%[1]s --help' for details.
`, binName)
longUsage = fmt.Sprintf(`usage: %s <command> [<path>...] [-- <arg>...]
%[1]s -h|--help
%[1]s -v|--version
Compiler for the snow programming language.
The <command> 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())))
}