~mna/snow unlisted

ref: df88c582d9748238dda46ee3b0c471278d86d04f snow/cmd/snowc/main.go -rw-r--r-- 2.7 KiB
df88c582Martin Angers cmd/snowc: implement basic compiler command 1 year, 2 months ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
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())))
}