interpreter
This commit is contained in:
		
							parent
							
								
									680df31650
								
							
						
					
					
						commit
						50ecb3dc2e
					
				
					 7 changed files with 302 additions and 115 deletions
				
			
		
							
								
								
									
										127
									
								
								ast.go
									
										
									
									
									
								
							
							
						
						
									
										127
									
								
								ast.go
									
										
									
									
									
								
							|  | @ -1,20 +1,20 @@ | ||||||
| package main | package main | ||||||
| 
 | 
 | ||||||
| import ( |  | ||||||
| 	"fmt" |  | ||||||
| 	"strings" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| type Visitor interface { | type Visitor interface { | ||||||
| 	visitBinary(b *Binary) | 	visitUnary(u *Unary) any | ||||||
| 	visitLiteral(l *Literal) | 	visitBinary(b *Binary) any | ||||||
| 	visitGrouping(g *Grouping) | 	visitLiteral(l *Literal) any | ||||||
| 	visitUnary(u *Unary) | 	visitGrouping(g *Grouping) any | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type Expr interface { | type Expr interface { | ||||||
| 	expr() | 	expr() | ||||||
| 	accept(v Visitor) | 	accept(v Visitor) any | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type Unary struct { | ||||||
|  | 	op    Token | ||||||
|  | 	right Expr | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type Binary struct { | type Binary struct { | ||||||
|  | @ -23,114 +23,31 @@ type Binary struct { | ||||||
| 	right Expr | 	right Expr | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type Unary struct { | type Literal struct { | ||||||
| 	op    Token | 	value any | ||||||
| 	right Expr |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type Grouping struct { | type Grouping struct { | ||||||
| 	expression Expr | 	expression Expr | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type Literal struct { |  | ||||||
| 	value any |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (u *Unary) expr()    {} | func (u *Unary) expr()    {} | ||||||
| func (g *Grouping) expr() {} |  | ||||||
| func (l *Literal) expr()  {} |  | ||||||
| func (b *Binary) expr()   {} | func (b *Binary) expr()   {} | ||||||
|  | func (l *Literal) expr()  {} | ||||||
|  | func (g *Grouping) expr() {} | ||||||
| 
 | 
 | ||||||
| func (u *Unary) accept(v Visitor) { | func (u *Unary) accept(v Visitor) any { | ||||||
| 	v.visitUnary(u) | 	return v.visitUnary(u) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (g *Grouping) accept(v Visitor) { | func (b *Binary) accept(v Visitor) any { | ||||||
| 	v.visitGrouping(g) | 	return v.visitBinary(b) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (l *Literal) accept(v Visitor) { | func (l *Literal) accept(v Visitor) any { | ||||||
| 	v.visitLiteral(l) | 	return v.visitLiteral(l) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (b *Binary) accept(v Visitor) { | func (g *Grouping) accept(v Visitor) any { | ||||||
| 	v.visitBinary(b) | 	return v.visitGrouping(g) | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type AstStringer struct { |  | ||||||
| 	str strings.Builder |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (as AstStringer) String(expr Expr) string { |  | ||||||
| 
 |  | ||||||
| 	if expr == nil { |  | ||||||
| 		return "" |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	expr.accept(&as) |  | ||||||
| 	return as.str.String() |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (as *AstStringer) visitBinary(b *Binary) { |  | ||||||
| 	as.str.WriteString("(") |  | ||||||
| 	as.str.WriteString(b.op.lexeme) |  | ||||||
| 	as.str.WriteString(" ") |  | ||||||
| 	b.left.accept(as) |  | ||||||
| 	as.str.WriteString(" ") |  | ||||||
| 	b.right.accept(as) |  | ||||||
| 	as.str.WriteString(")") |  | ||||||
| 
 |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (as *AstStringer) visitLiteral(l *Literal) { |  | ||||||
| 	as.str.WriteString(fmt.Sprintf("%v", l.value)) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (as *AstStringer) visitGrouping(g *Grouping) { |  | ||||||
| 	as.str.WriteString("(group ") |  | ||||||
| 	g.expression.accept(as) |  | ||||||
| 	as.str.WriteString(")") |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (as *AstStringer) visitUnary(u *Unary) { |  | ||||||
| 	as.str.WriteString(fmt.Sprintf("(%s ", u.op.lexeme)) |  | ||||||
| 	u.right.accept(as) |  | ||||||
| 	as.str.WriteString(")") |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type AstToRPN struct { |  | ||||||
| 	str strings.Builder |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (as AstToRPN) String(expr Expr) string { |  | ||||||
| 
 |  | ||||||
| 	if expr == nil { |  | ||||||
| 		return "" |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	expr.accept(&as) |  | ||||||
| 	return as.str.String() |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (as *AstToRPN) visitBinary(b *Binary) { |  | ||||||
| 	b.left.accept(as) |  | ||||||
| 	as.str.WriteString(" ") |  | ||||||
| 	b.right.accept(as) |  | ||||||
| 	as.str.WriteString(" ") |  | ||||||
| 	as.str.WriteString(b.op.lexeme) |  | ||||||
| 
 |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (as *AstToRPN) visitLiteral(l *Literal) { |  | ||||||
| 	as.str.WriteString(fmt.Sprintf("%v", l.value)) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (as *AstToRPN) visitGrouping(g *Grouping) { |  | ||||||
| 	g.expression.accept(as) |  | ||||||
| 	as.str.WriteString(" group") |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (as *AstToRPN) visitUnary(u *Unary) { |  | ||||||
| 	u.right.accept(as) |  | ||||||
| 	as.str.WriteString(fmt.Sprintf(" %s", u.op.lexeme)) |  | ||||||
| } | } | ||||||
|  |  | ||||||
							
								
								
									
										46
									
								
								ast_rpn.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								ast_rpn.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,46 @@ | ||||||
|  | package main | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"strings" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | type AstToRPN struct { | ||||||
|  | 	str strings.Builder | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (as AstToRPN) String(expr Expr) string { | ||||||
|  | 
 | ||||||
|  | 	if expr == nil { | ||||||
|  | 		return "" | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	expr.accept(&as) | ||||||
|  | 	return as.str.String() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (as *AstToRPN) visitBinary(b *Binary) any { | ||||||
|  | 	b.left.accept(as) | ||||||
|  | 	as.str.WriteString(" ") | ||||||
|  | 	b.right.accept(as) | ||||||
|  | 	as.str.WriteString(" ") | ||||||
|  | 	as.str.WriteString(b.op.lexeme) | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (as *AstToRPN) visitLiteral(l *Literal) any { | ||||||
|  | 	as.str.WriteString(fmt.Sprintf("%v", l.value)) | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (as *AstToRPN) visitGrouping(g *Grouping) any { | ||||||
|  | 	g.expression.accept(as) | ||||||
|  | 	as.str.WriteString(" group") | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (as *AstToRPN) visitUnary(u *Unary) any { | ||||||
|  | 	u.right.accept(as) | ||||||
|  | 	as.str.WriteString(fmt.Sprintf(" %s", u.op.lexeme)) | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
							
								
								
									
										51
									
								
								ast_string.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								ast_string.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,51 @@ | ||||||
|  | package main | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"strings" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | type AstStringer struct { | ||||||
|  | 	str strings.Builder | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (as AstStringer) String(expr Expr) string { | ||||||
|  | 
 | ||||||
|  | 	if expr == nil { | ||||||
|  | 		return "" | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	expr.accept(&as) | ||||||
|  | 	return as.str.String() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (as *AstStringer) visitBinary(b *Binary) any { | ||||||
|  | 	as.str.WriteString("(") | ||||||
|  | 	as.str.WriteString(b.op.lexeme) | ||||||
|  | 	as.str.WriteString(" ") | ||||||
|  | 	b.left.accept(as) | ||||||
|  | 	as.str.WriteString(" ") | ||||||
|  | 	b.right.accept(as) | ||||||
|  | 	as.str.WriteString(")") | ||||||
|  | 	return nil | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (as *AstStringer) visitLiteral(l *Literal) any { | ||||||
|  | 	as.str.WriteString(fmt.Sprintf("%v", l.value)) | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (as *AstStringer) visitGrouping(g *Grouping) any { | ||||||
|  | 	as.str.WriteString("(group ") | ||||||
|  | 	g.expression.accept(as) | ||||||
|  | 	as.str.WriteString(")") | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (as *AstStringer) visitUnary(u *Unary) any { | ||||||
|  | 	as.str.WriteString(fmt.Sprintf("(%s ", u.op.lexeme)) | ||||||
|  | 	u.right.accept(as) | ||||||
|  | 	as.str.WriteString(")") | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
							
								
								
									
										6
									
								
								error.go
									
										
									
									
									
								
							
							
						
						
									
										6
									
								
								error.go
									
										
									
									
									
								
							|  | @ -6,6 +6,7 @@ import ( | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| var hadError = false | var hadError = false | ||||||
|  | var hadRuntimeError = false | ||||||
| 
 | 
 | ||||||
| func printError(token Token, message string) { | func printError(token Token, message string) { | ||||||
| 	if token.typ == EOF { | 	if token.typ == EOF { | ||||||
|  | @ -19,3 +20,8 @@ func report(line int, where string, message string) { | ||||||
| 	log.Printf("[%d] Error %s: %s", line, where, message) | 	log.Printf("[%d] Error %s: %s", line, where, message) | ||||||
| 	hadError = true | 	hadError = true | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | func reportRuntimeError(token Token, message string) { | ||||||
|  | 	log.Printf("[%d] Error: %s", token.line, message) | ||||||
|  | 	hadRuntimeError = true | ||||||
|  | } | ||||||
|  |  | ||||||
							
								
								
									
										28
									
								
								glox.go
									
										
									
									
									
								
							
							
						
						
									
										28
									
								
								glox.go
									
										
									
									
									
								
							|  | @ -2,6 +2,7 @@ package main | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"bufio" | 	"bufio" | ||||||
|  | 	"fmt" | ||||||
| 	"log" | 	"log" | ||||||
| 	"os" | 	"os" | ||||||
| ) | ) | ||||||
|  | @ -31,6 +32,7 @@ func runPrompt() { | ||||||
| 		} | 		} | ||||||
| 		run([]byte(scanner.Text())) | 		run([]byte(scanner.Text())) | ||||||
| 		hadError = false | 		hadError = false | ||||||
|  | 		hadRuntimeError = false | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -40,15 +42,39 @@ func runFile(path string) { | ||||||
| 	try(err) | 	try(err) | ||||||
| 
 | 
 | ||||||
| 	run(file) | 	run(file) | ||||||
|  | 
 | ||||||
|  | 	switch { | ||||||
|  | 	case hadError: | ||||||
|  | 		os.Exit(65) | ||||||
|  | 	case hadRuntimeError: | ||||||
|  | 		os.Exit(70) | ||||||
|  | 	default: | ||||||
|  | 		os.Exit(0) | ||||||
|  | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func run(source []byte) { | func run(source []byte) { | ||||||
| 	tokens := newScanner(source).scan() | 	tokens := newScanner(source).scan() | ||||||
| 
 | 
 | ||||||
|  | 	if hadError { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	ast := newParser(tokens).parse() | 	ast := newParser(tokens).parse() | ||||||
| 
 | 
 | ||||||
|  | 	if hadError { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	println(AstStringer{}.String(ast)) | 	println(AstStringer{}.String(ast)) | ||||||
| 	println(AstToRPN{}.String(ast)) | 
 | ||||||
|  | 	res := newInterpreter().evaluate(ast) | ||||||
|  | 
 | ||||||
|  | 	if hadRuntimeError { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	fmt.Printf("%v\n", res) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func try(err error) { | func try(err error) { | ||||||
|  |  | ||||||
							
								
								
									
										148
									
								
								interpreter.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										148
									
								
								interpreter.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,148 @@ | ||||||
|  | package main | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"reflect" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | type Interpreter struct{} | ||||||
|  | 
 | ||||||
|  | type RuntimeError struct { | ||||||
|  | 	token Token | ||||||
|  | 	msg   string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (re RuntimeError) Error() string { | ||||||
|  | 	return re.msg | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func newInterpreter() *Interpreter { | ||||||
|  | 	return &Interpreter{} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (i *Interpreter) evaluate(e Expr) any { | ||||||
|  | 	defer i.recover() | ||||||
|  | 	return e.accept(i) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (i *Interpreter) recover() { | ||||||
|  | 	if err := recover(); err != nil { | ||||||
|  | 		pe, ok := err.(RuntimeError) | ||||||
|  | 
 | ||||||
|  | 		if !ok { | ||||||
|  | 			panic(err) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		reportRuntimeError(pe.token, pe.msg) | ||||||
|  | 		hadRuntimeError = true | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (i *Interpreter) visitBinary(b *Binary) any { | ||||||
|  | 	left := i.evaluate(b.left) | ||||||
|  | 	right := i.evaluate(b.right) | ||||||
|  | 
 | ||||||
|  | 	switch b.op.typ { | ||||||
|  | 	case MINUS: | ||||||
|  | 		checkIfFloats(b.op, left, right) | ||||||
|  | 		return left.(float64) - right.(float64) | ||||||
|  | 	case SLASH: | ||||||
|  | 		checkIfFloats(b.op, left, right) | ||||||
|  | 		return left.(float64) / right.(float64) | ||||||
|  | 	case STAR: | ||||||
|  | 		checkIfFloats(b.op, left, right) | ||||||
|  | 		return left.(float64) * right.(float64) | ||||||
|  | 	case GREATER: | ||||||
|  | 		checkIfFloats(b.op, left, right) | ||||||
|  | 		return left.(float64) > right.(float64) | ||||||
|  | 	case LESS: | ||||||
|  | 		checkIfFloats(b.op, left, right) | ||||||
|  | 		return left.(float64) < right.(float64) | ||||||
|  | 	case GREATER_EQUAL: | ||||||
|  | 		checkIfFloats(b.op, left, right) | ||||||
|  | 		return left.(float64) >= right.(float64) | ||||||
|  | 	case LESS_EQUAL: | ||||||
|  | 		checkIfFloats(b.op, left, right) | ||||||
|  | 		return left.(float64) <= right.(float64) | ||||||
|  | 	case BANG_EQUAL: | ||||||
|  | 		return !reflect.DeepEqual(left, right) | ||||||
|  | 	case EQUAL_EQUAL: | ||||||
|  | 		return reflect.DeepEqual(left, right) | ||||||
|  | 	case PLUS: | ||||||
|  | 		if isFloats(left, right) { | ||||||
|  | 			return left.(float64) + right.(float64) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if isStrings(left, right) { | ||||||
|  | 			return left.(string) + right.(string) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	panic(RuntimeError{b.op, fmt.Sprintf("Operands must be numbers or strings: %v %s %v", left, b.op.lexeme, right)}) | ||||||
|  | 
 | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (i *Interpreter) visitLiteral(l *Literal) any { | ||||||
|  | 	return l.value | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (i *Interpreter) visitGrouping(g *Grouping) any { | ||||||
|  | 	return i.evaluate(g.expression) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (i *Interpreter) visitUnary(u *Unary) any { | ||||||
|  | 	val := i.evaluate(u.right) | ||||||
|  | 
 | ||||||
|  | 	switch u.op.typ { | ||||||
|  | 	case MINUS: | ||||||
|  | 		checkIfFloat(u.op, val) | ||||||
|  | 		return -val.(float64) | ||||||
|  | 	case BANG: | ||||||
|  | 		return !isTruthy(val) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func checkIfFloat(op Token, val any) { | ||||||
|  | 	if _, ok := val.(float64); ok { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	panic(RuntimeError{op, "value must ne a number."}) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func checkIfFloats(op Token, a any, b any) { | ||||||
|  | 	if isFloats(a, b) { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	panic(RuntimeError{op, fmt.Sprintf("Operands must be numbers: %v %s %v", a, op.lexeme, b)}) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func isFloats(a any, b any) bool { | ||||||
|  | 	ltype := reflect.TypeOf(a) | ||||||
|  | 	rtype := reflect.TypeOf(b) | ||||||
|  | 
 | ||||||
|  | 	return ltype.Kind() == rtype.Kind() && ltype.Kind() == reflect.Float64 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func isStrings(a any, b any) bool { | ||||||
|  | 	ltype := reflect.TypeOf(a) | ||||||
|  | 	rtype := reflect.TypeOf(b) | ||||||
|  | 
 | ||||||
|  | 	return ltype.Kind() == rtype.Kind() && ltype.Kind() == reflect.String | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func isTruthy(val any) bool { | ||||||
|  | 	if val == nil { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if boolean, ok := val.(bool); ok { | ||||||
|  | 		return boolean | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return true | ||||||
|  | } | ||||||
							
								
								
									
										11
									
								
								parser.go
									
										
									
									
									
								
							
							
						
						
									
										11
									
								
								parser.go
									
										
									
									
									
								
							|  | @ -88,7 +88,7 @@ func (p *Parser) factor() Expr { | ||||||
| 	for p.match(SLASH, STAR) { | 	for p.match(SLASH, STAR) { | ||||||
| 		op := p.previous() | 		op := p.previous() | ||||||
| 		right := p.unary() | 		right := p.unary() | ||||||
| 		exp = &Unary{op, right} | 		exp = &Binary{exp, op, right} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return exp | 	return exp | ||||||
|  | @ -190,14 +190,7 @@ func (p *Parser) synchronize() { | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		switch p.peek().typ { | 		switch p.peek().typ { | ||||||
| 		case CLASS: | 		case CLASS, FOR, FUN, IF, PRINT, RETURN, VAR, WHILE: | ||||||
| 		case FOR: |  | ||||||
| 		case FUN: |  | ||||||
| 		case IF: |  | ||||||
| 		case PRINT: |  | ||||||
| 		case RETURN: |  | ||||||
| 		case VAR: |  | ||||||
| 		case WHILE: |  | ||||||
| 			return | 			return | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		
		Reference in a new issue