Add and test abstract syntax tree data type
This commit is contained in:
		
							parent
							
								
									e4733b20a6
								
							
						
					
					
						commit
						475b155228
					
				
					 6 changed files with 220 additions and 12 deletions
				
			
		
							
								
								
									
										56
									
								
								pj1-go/ast-print.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								pj1-go/ast-print.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,56 @@ | ||||||
|  | package main | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"strings" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func print(expr Expr) string { | ||||||
|  | 	switch e := expr.(type) { | ||||||
|  | 	case Binary: | ||||||
|  | 		return parenthesize(e.operator.Lexeme, | ||||||
|  | 			e.left, | ||||||
|  | 			e.right) | ||||||
|  | 	case Grouping: | ||||||
|  | 		return parenthesize("group", e.expression) | ||||||
|  | 	case Literal: | ||||||
|  | 		if e.value == nil { | ||||||
|  | 			return "nil" | ||||||
|  | 		} | ||||||
|  | 		return fmt.Sprintf("%v", e.value) | ||||||
|  | 	case Unary: | ||||||
|  | 		return parenthesize(e.operator.Lexeme, e.right) | ||||||
|  | 	} | ||||||
|  | 	return "ERROR: reached impossible branch in print function" | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func parenthesize(name string, exprs ...Expr) string { | ||||||
|  | 	var sb strings.Builder | ||||||
|  | 
 | ||||||
|  | 	sb.WriteString("(" + name) | ||||||
|  | 
 | ||||||
|  | 	for _, expr := range exprs { | ||||||
|  | 		sb.WriteString(" ") | ||||||
|  | 		sb.WriteString(print(expr)) | ||||||
|  | 	} | ||||||
|  | 	sb.WriteString(")") | ||||||
|  | 
 | ||||||
|  | 	return sb.String() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // This was briefly a testing function for the AST class at the end of ch. 5 | ||||||
|  | /* | ||||||
|  | func main() { | ||||||
|  | 	var expression Expr = Binary{ | ||||||
|  | 		Unary{ | ||||||
|  | 			lexer.NewToken(lexer.MINUS, "-", nil, 1), | ||||||
|  | 			Literal{123}, | ||||||
|  | 		}, | ||||||
|  | 		lexer.NewToken(lexer.STAR, "*", nil, 1), | ||||||
|  | 		Grouping{ | ||||||
|  | 			Literal{45.67}, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | 	fmt.Println(print(expression)) | ||||||
|  | } | ||||||
|  | */ | ||||||
							
								
								
									
										36
									
								
								pj1-go/ast.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								pj1-go/ast.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -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() {} | ||||||
|  | 
 | ||||||
							
								
								
									
										114
									
								
								pj1-go/gen-ast.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								pj1-go/gen-ast.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,114 @@ | ||||||
|  | //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 <output directory>") | ||||||
|  | 		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{}", | ||||||
|  | 			// a literal can be any value that can be printed | ||||||
|  | 			"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 | ||||||
|  | } | ||||||
|  | @ -1,3 +1,5 @@ | ||||||
|  | git.bonsai.cool/kayprish/pj1/pj1-go v0.0.0-20240807135935-04e669c15630 h1:SO1oOi4BAVPmoCkBCeZEcNXiCDZJrM49Y5Fhm/bBuBU= | ||||||
|  | git.bonsai.cool/kayprish/pj1/pj1-go v0.0.0-20240807135935-04e669c15630/go.mod h1:f4dHsvhBf6lTSjuA+gqssxFOHdkOjPnS4QeufMWvHOM= | ||||||
| golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= | golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= | ||||||
| golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= | golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= | ||||||
| golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= | ||||||
|  |  | ||||||
|  | @ -88,7 +88,7 @@ var keywords = map[string]TokenType{ | ||||||
| 
 | 
 | ||||||
| type Token struct { | type Token struct { | ||||||
| 	ttype   TokenType | 	ttype   TokenType | ||||||
| 	lexeme  string | 	Lexeme  string | ||||||
| 	literal interface{} | 	literal interface{} | ||||||
| 	line    int | 	line    int | ||||||
| } | } | ||||||
|  | @ -99,7 +99,7 @@ func NewToken(ttype TokenType, lexeme string, literal interface{}, line int) Tok | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (t Token) String() string { | func (t Token) String() string { | ||||||
| 	return fmt.Sprintf("%v %v %v", t.ttype, t.lexeme, t.literal) | 	return fmt.Sprintf("%v %v %v", t.ttype, t.Lexeme, t.literal) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type Lexer struct { | type Lexer struct { | ||||||
|  |  | ||||||
|  | @ -10,16 +10,16 @@ import ( | ||||||
| 	"git.bonsai.cool/kayprish/pj1/pj1-go/util" | 	"git.bonsai.cool/kayprish/pj1/pj1-go/util" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func main() { | // func main() { | ||||||
| 	if len(os.Args) > 2 { | // 	if len(os.Args) > 2 { | ||||||
| 		fmt.Println("Usage: pj1-go [script]") | // 		fmt.Println("Usage: pj1-go [script]") | ||||||
| 		os.Exit(64) | // 		os.Exit(64) | ||||||
| 	} else if len(os.Args) == 2 { | // 	} else if len(os.Args) == 2 { | ||||||
| 		runFile(os.Args[0]) | // 		runFile(os.Args[0]) | ||||||
| 	} else { | // 	} else { | ||||||
| 		runPrompt() | // 		runPrompt() | ||||||
| 	} | // 	} | ||||||
| } | // } | ||||||
| 
 | 
 | ||||||
| func runFile(path string) { | func runFile(path string) { | ||||||
| 	bytes, err := ioutil.ReadFile(path) | 	bytes, err := ioutil.ReadFile(path) | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		
		Reference in a new issue
	
	 Petar Kapriš
						Petar Kapriš