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/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= | ||||
| golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= | ||||
|  |  | |||
|  | @ -88,7 +88,7 @@ var keywords = map[string]TokenType{ | |||
| 
 | ||||
| type Token struct { | ||||
| 	ttype   TokenType | ||||
| 	lexeme  string | ||||
| 	Lexeme  string | ||||
| 	literal interface{} | ||||
| 	line    int | ||||
| } | ||||
|  | @ -99,7 +99,7 @@ func NewToken(ttype TokenType, lexeme string, literal interface{}, line int) Tok | |||
| } | ||||
| 
 | ||||
| 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 { | ||||
|  |  | |||
|  | @ -10,16 +10,16 @@ import ( | |||
| 	"git.bonsai.cool/kayprish/pj1/pj1-go/util" | ||||
| ) | ||||
| 
 | ||||
| func main() { | ||||
| 	if len(os.Args) > 2 { | ||||
| 		fmt.Println("Usage: pj1-go [script]") | ||||
| 		os.Exit(64) | ||||
| 	} else if len(os.Args) == 2 { | ||||
| 		runFile(os.Args[0]) | ||||
| 	} else { | ||||
| 		runPrompt() | ||||
| 	} | ||||
| } | ||||
| // func main() { | ||||
| // 	if len(os.Args) > 2 { | ||||
| // 		fmt.Println("Usage: pj1-go [script]") | ||||
| // 		os.Exit(64) | ||||
| // 	} else if len(os.Args) == 2 { | ||||
| // 		runFile(os.Args[0]) | ||||
| // 	} else { | ||||
| // 		runPrompt() | ||||
| // 	} | ||||
| // } | ||||
| 
 | ||||
| func runFile(path string) { | ||||
| 	bytes, err := ioutil.ReadFile(path) | ||||
|  |  | |||
		Loading…
	
	Add table
		
		Reference in a new issue
	
	 Petar Kapriš
						Petar Kapriš