diff --git a/pj1-go/ast.go b/pj1-go/ast.go new file mode 100644 index 0000000..bedfeb3 --- /dev/null +++ b/pj1-go/ast.go @@ -0,0 +1,36 @@ +// Code generated by tools/gen-ast.go DO NOT EDIT. +package main + +import "git.bonsai.cool/kayprish/pj1/pj1-go/lexer" + +type Expr interface { + isExpr() +} + +type Binary struct { + left Expr + operator lexer.Token + right Expr +} + +func (x Binary) isExpr() {} + +type Grouping struct { + expression Expr +} + +func (x Grouping) isExpr() {} + +type Literal struct { + value interface{} +} + +func (x Literal) isExpr() {} + +type Unary struct { + operator lexer.Token + right Expr +} + +func (x Unary) isExpr() {} + diff --git a/pj1-go/gen-ast.go b/pj1-go/gen-ast.go new file mode 100644 index 0000000..2da29fa --- /dev/null +++ b/pj1-go/gen-ast.go @@ -0,0 +1,112 @@ +//go:build generate +//go:generate go run ./gen-ast.go ./ + +package main + +import ( + "bufio" + "fmt" + "io" + "os" + "strings" + "unicode" + "unicode/utf8" +) + +var modulePath string + +// 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 +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] + modulePath = getModulePath() + defineAst(outputDir, "Expr", + []string{"Binary : left Expr, operator lexer.Token, right Expr", + "Grouping : expression Expr", + "Literal : value interface{}", + "Unary : operator lexer.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) + fmt.Fprintln(f, "import \"" + modulePath + "/lexer\"") + 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 +}