//go:build generate //go:generate go run ./gen-ast.go ./ package main import ( "bufio" "fmt" "io" "os" "strings" "unicode" "unicode/utf8" ) // returns a string which contains the current module path, keep in mind this // program is meant to be run in the root of the module using go generate // // This function is no longer useful because the lexer package has been // deleted, however, it may become useful again soon func getModulePath() string { goModFile, err := os.Open("./go.mod") if err != nil { fmt.Fprintln(os.Stderr, "Could not open go.mod file, is this command being run in the module root folder?") os.Exit(1) } scanner := bufio.NewScanner(goModFile) scanner.Scan() fstLine := scanner.Text() if fstLine[:7] != "module " { fmt.Fprintln(os.Stderr, "The first line of go.mod seems malformed") os.Exit(1) } goModFile.Close() path := strings.TrimSpace(fstLine[7:]) return path } func main() { if len(os.Args) != 2 { fmt.Fprintln(os.Stderr, "Usage: go run gen-ast.go ") os.Exit(64) } outputDir := os.Args[1] defineAst(outputDir, "Expr", []string{"Binary : left Expr, operator Token, right Expr", "Grouping : expression Expr", "Literal : value interface{}", // a literal can be any value that can be printed "Unary : operator Token, right Expr"}) } func lowerFirst(s string) string { if s == "" { return "" } r, n := utf8.DecodeRuneInString(s) return string(unicode.ToLower(r)) + s[n:] } func defineAst(outputDir string, baseName string, types []string) { fileName := "ast" var path string = outputDir + "/" + fileName + ".go" f, err := os.Create(path) if err != nil { fmt.Fprintln(os.Stderr, "Could not open file \""+path+"\"") return } defer f.Close() fmt.Fprintln(f, "// Code generated by tools/gen-ast.go DO NOT EDIT.") fmt.Fprintln(f, "package main") fmt.Fprintln(f) // Creates a dummy interface just to limit types which can be // considered an "Expr" fmt.Fprintln(f, "type "+baseName+" interface {") fmt.Fprintln(f, "\tis"+baseName+"()") fmt.Fprintln(f, "}") fmt.Fprintln(f) // The AST types. for _, t := range types { tSplit := strings.Split(t, ":") typeName := strings.TrimSpace(tSplit[0]) fields := strings.TrimSpace(tSplit[1]) defineType(f, baseName, typeName, fields) } } func defineType(f io.Writer, baseName string, typeName string, fieldList string) { fmt.Fprintln(f, "type "+typeName+" struct {") // Fields. var fields []string = strings.Split(fieldList, ", ") for _, field := range fields { fmt.Fprintln(f, "\t"+field) } fmt.Fprintln(f, "}") fmt.Fprintln(f) // Interface dummy function fmt.Fprintln(f, "func (x "+typeName+") is"+baseName+"() {}") fmt.Fprintln(f) // TODO: may have to generate constructor, according to top of // page 110, right now it seems that defining structs without // the constructor does the job }