├── .github └── workflows │ └── go.yml ├── .gitignore ├── Makefile ├── README.md ├── ast ├── expr.go ├── param.go ├── print.go ├── stmt.go ├── token.go └── type.go ├── env └── env.go ├── go.mod ├── go.sum ├── interpret ├── class.go ├── clock.go ├── function.go └── interpreter.go ├── main.go ├── main_test.go ├── parse └── parser.go ├── resolve └── resolver.go ├── scan └── scanner.go ├── test.lox ├── testdata ├── class-with-fields.golden └── class-with-fields.input └── typechecker ├── testdata ├── binary-expr-on-string-type.golden ├── binary-expr-on-string-type.input ├── binary-expr.golden ├── binary-expr.input ├── boolean-expr.golden ├── boolean-expr.input ├── call-non-callable.golden ├── call-non-callable.input ├── class-assign-field.golden ├── class-assign-field.input ├── class-with-init.golden ├── class-with-init.input ├── class-with-super.golden ├── class-with-super.input ├── class-with-two-supers.golden ├── class-with-two-supers.input ├── class-without-init.golden ├── class-without-init.input ├── custom-type.golden ├── custom-type.input ├── fn-body.golden ├── fn-body.input ├── fn-call-native-with-correct-arg-count.golden ├── fn-call-native-with-correct-arg-count.input ├── fn-call-native-with-wrong-arg-count.golden ├── fn-call-native-with-wrong-arg-count.input ├── fn-call-with-correct-arg-types.golden ├── fn-call-with-correct-arg-types.input ├── fn-call-with-wrong-arg-types.golden ├── fn-call-with-wrong-arg-types.input ├── fn-callback.golden ├── fn-callback.input ├── fn-iife.golden ├── fn-iife.input ├── fn-inferred-type.golden ├── fn-inferred-type.input ├── fn-multi-return.golden ├── fn-multi-return.input ├── fn-recursive-factorial.golden ├── fn-recursive-factorial.input ├── fn-return-fn.golden ├── fn-return-fn.input ├── fn-single-return.golden ├── fn-single-return.input ├── ternary-expr.golden ├── ternary-expr.input ├── union-type-allowed-operators.golden ├── union-type-allowed-operators.input ├── union-type-illegal-operators.golden ├── union-type-illegal-operators.input ├── union-type.golden ├── union-type.input ├── variable-re-assignment.golden ├── variable-re-assignment.input ├── while-stmt.golden └── while-stmt.input ├── typechecker.go ├── typechecker_test.go └── types.go /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | 16 | - name: Set up Go 17 | uses: actions/setup-go@v2 18 | with: 19 | go-version: 1.17 20 | 21 | - name: Build 22 | run: go build -v ./... 23 | 24 | - name: Test 25 | run: go test -race -covermode=atomic -coverprofile=coverage.out -v ./... 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Creates a .golden file and a .input file 2 | # for a new test in the typechecker package 3 | add_typechecker_test: 4 | @read -p "Enter test name: " testname; \ 5 | touch typechecker/testdata/$$testname.golden; \ 6 | touch typechecker/testdata/$$testname.input 7 | 8 | add_main_test: 9 | @read -p "Enter test name: " testname; \ 10 | touch testdata/$$testname.golden; \ 11 | touch testdata/$$testname.input 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # glox 2 | 3 | An implementation of the tree-walking interpreter from [_Crafting Interpreters_](https://craftinginterpreters.com/) 4 | written in Go. 5 | 6 | ## Installation 7 | 8 | To install, run: 9 | 10 | ```shell 11 | go get github.com/chidiwilliams/glox 12 | ``` 13 | 14 | ## Usage 15 | 16 | To start a new REPL session: 17 | 18 | ```shell 19 | glox 20 | ``` 21 | 22 | To run a Lox file: 23 | 24 | ```shell 25 | glox -filePath [filePath] 26 | ``` 27 | 28 | ## Parser grammar 29 | 30 | glox implements the [original Lox definition](https://craftinginterpreters.com/appendix-i.html) as well as some features 31 | from the _Challenges_ sections in the book: series expressions, function expressions, anonymous functions, and getter 32 | methods. 33 | 34 | Here's the full parser grammar: 35 | 36 | ```text 37 | program => declaration* EOF 38 | declaration => classDecl | funcDecl | varDecl | statement 39 | classDecl => "class" IDENTIFIER ( "<" IDENTIFIER )? "{" function* "}" 40 | funDecl => "fun" function 41 | function => IDENTIFIER "(" parameters? ")" block 42 | parameters => IDENTIFIER ( "," IDENTIFIER )* 43 | varDecl => "var" IDENTIFIER ( "=" expression )? ";" 44 | statement => exprStmt | ifStmt | forStmt | printStmt | returnStmt | whileStmt 45 | | breakStmt | continueStmt | block 46 | exprStmt => expression ";" 47 | ifStmt => "if" "(" expression ")" statement ( "else" statement )? 48 | forStmt => "for" "(" ( varDecl | exprStmt | ";" ) expression? ";" expression? ")" statement 49 | printStmt => "print" expression ";" 50 | returnStmt => "return" expression? ";" 51 | whileStmt => "while" "(" expression ")" statement 52 | block => "{" declaration* "}" ; 53 | expression => series 54 | series => assignment ( "," assignment )* 55 | assignment => ( call "." )? IDENTIFIER "=" assignment | ternary 56 | ternary => logic_or ( "?" ternary ":" ternary )* 57 | logic_or => logic_and ( "or" logic_and )* 58 | logic_and => equality ( "and" equality )* 59 | equality => comparison ( ( "!=" | "==" ) comparison ) 60 | comparison => term ( ( ">" | ">=" | "<" | "<=" ) term )* 61 | term => factor ( ( "+" | "-" ) factor )* 62 | factor => unary ( ( "/" | "*" ) unary )* 63 | unary => ( "!" | "-" ) unary | call 64 | call => primary ( "(" arguments? ")" | "." IDENTIFIER )* 65 | arguments => expression ( "," expression )* 66 | primary => NUMBER | STRING | "true" | "false" | "nil" | "(" expression ")" 67 | | IDENTIFIER | functionExpr | "super" . IDENTIFIER 68 | functionExpr => "fun" IDENTIFIER? "(" parameters? ")" block 69 | ``` 70 | 71 | ## Examples 72 | 73 | A few example programs from [Crafting Interpreters](https://craftinginterpreters.com/the-lox-language.html): 74 | 75 | ```lox 76 | print "Hello, world!"; 77 | ``` 78 | 79 | If statements: 80 | 81 | ```lox 82 | if (condition) { 83 | print "yes"; 84 | } else { 85 | print "no"; 86 | } 87 | ``` 88 | 89 | Loops: 90 | 91 | ```lox 92 | var a = 1; 93 | while (a < 10) { 94 | print a; 95 | a = a + 1; 96 | } 97 | 98 | for (var a = 1; a < 10; a = a + 1) { 99 | print a; 100 | } 101 | ``` 102 | 103 | Functions and closures: 104 | 105 | ```lox 106 | fun printSum(a, b) { 107 | print a + b; 108 | } 109 | printSum(1, 2); 110 | 111 | fun returnFunction() { 112 | var outside = "outside"; 113 | 114 | fun inner() { 115 | print outside; 116 | } 117 | 118 | return inner; 119 | } 120 | 121 | var fn = returnFunction(); 122 | fn(); 123 | ``` 124 | 125 | Classes: 126 | 127 | ```lox 128 | class Breakfast { 129 | init(meat, bread) { 130 | this.meat = meat; 131 | this.bread = bread; 132 | } 133 | } 134 | 135 | class Brunch < Breakfast { 136 | init(meat, bread, drink) { 137 | super.init(meat, bread); 138 | this.drink = drink; 139 | } 140 | } 141 | ``` 142 | -------------------------------------------------------------------------------- /ast/expr.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | type Expr interface { 4 | Accept(visitor ExprVisitor) interface{} 5 | StartLine() int 6 | EndLine() int 7 | } 8 | 9 | type AssignExpr struct { 10 | Name Token 11 | Value Expr 12 | } 13 | 14 | func (b AssignExpr) StartLine() int { 15 | // TODO implement me 16 | panic("implement me") 17 | } 18 | 19 | func (b AssignExpr) EndLine() int { 20 | // TODO implement me 21 | panic("implement me") 22 | } 23 | 24 | func (b AssignExpr) Accept(visitor ExprVisitor) interface{} { 25 | return visitor.VisitAssignExpr(b) 26 | } 27 | 28 | type BinaryExpr struct { 29 | Left Expr 30 | Operator Token 31 | Right Expr 32 | } 33 | 34 | func (b BinaryExpr) StartLine() int { 35 | return b.Left.StartLine() 36 | } 37 | 38 | func (b BinaryExpr) EndLine() int { 39 | return b.Right.EndLine() 40 | } 41 | 42 | func (b BinaryExpr) Accept(visitor ExprVisitor) interface{} { 43 | return visitor.VisitBinaryExpr(b) 44 | } 45 | 46 | type CallExpr struct { 47 | Callee Expr 48 | Paren Token 49 | Arguments []Expr 50 | } 51 | 52 | func (b CallExpr) StartLine() int { 53 | // TODO implement me 54 | panic("implement me") 55 | } 56 | 57 | func (b CallExpr) EndLine() int { 58 | // TODO implement me 59 | panic("implement me") 60 | } 61 | 62 | func (b CallExpr) Accept(visitor ExprVisitor) interface{} { 63 | return visitor.VisitCallExpr(b) 64 | } 65 | 66 | type FunctionExpr struct { 67 | Name *Token 68 | Params []Param 69 | Body []Stmt 70 | ReturnType Type 71 | } 72 | 73 | func (b FunctionExpr) StartLine() int { 74 | // TODO implement me 75 | panic("implement me") 76 | } 77 | 78 | func (b FunctionExpr) EndLine() int { 79 | // TODO implement me 80 | panic("implement me") 81 | } 82 | 83 | func (b FunctionExpr) Accept(visitor ExprVisitor) interface{} { 84 | return visitor.VisitFunctionExpr(b) 85 | } 86 | 87 | type GetExpr struct { 88 | Object Expr 89 | Name Token 90 | } 91 | 92 | func (b GetExpr) StartLine() int { 93 | // TODO implement me 94 | panic("implement me") 95 | } 96 | 97 | func (b GetExpr) EndLine() int { 98 | // TODO implement me 99 | panic("implement me") 100 | } 101 | 102 | func (b GetExpr) Accept(visitor ExprVisitor) interface{} { 103 | return visitor.VisitGetExpr(b) 104 | } 105 | 106 | type GroupingExpr struct { 107 | Expression Expr 108 | } 109 | 110 | func (b GroupingExpr) StartLine() int { 111 | return b.Expression.StartLine() 112 | } 113 | 114 | func (b GroupingExpr) EndLine() int { 115 | return b.Expression.EndLine() 116 | } 117 | 118 | func (b GroupingExpr) Accept(visitor ExprVisitor) interface{} { 119 | return visitor.VisitGroupingExpr(b) 120 | } 121 | 122 | type LiteralExpr struct { 123 | Value interface{} 124 | LineStart int 125 | LineEnd int 126 | } 127 | 128 | func (b LiteralExpr) StartLine() int { 129 | return b.LineStart 130 | } 131 | 132 | func (b LiteralExpr) EndLine() int { 133 | return b.LineEnd 134 | } 135 | 136 | func (b LiteralExpr) Accept(visitor ExprVisitor) interface{} { 137 | return visitor.VisitLiteralExpr(b) 138 | } 139 | 140 | type LogicalExpr struct { 141 | Left Expr 142 | Operator Token 143 | Right Expr 144 | } 145 | 146 | func (b LogicalExpr) StartLine() int { 147 | // TODO implement me 148 | panic("implement me") 149 | } 150 | 151 | func (b LogicalExpr) EndLine() int { 152 | // TODO implement me 153 | panic("implement me") 154 | } 155 | 156 | func (b LogicalExpr) Accept(visitor ExprVisitor) interface{} { 157 | return visitor.VisitLogicalExpr(b) 158 | } 159 | 160 | type SetExpr struct { 161 | Object Expr 162 | Name Token 163 | Value Expr 164 | } 165 | 166 | func (b SetExpr) StartLine() int { 167 | return b.Object.StartLine() 168 | } 169 | 170 | func (b SetExpr) EndLine() int { 171 | return b.Value.EndLine() 172 | } 173 | 174 | func (b SetExpr) Accept(visitor ExprVisitor) interface{} { 175 | return visitor.VisitSetExpr(b) 176 | } 177 | 178 | type SuperExpr struct { 179 | Keyword Token 180 | Method Token 181 | } 182 | 183 | func (b SuperExpr) StartLine() int { 184 | return b.Keyword.Line 185 | } 186 | 187 | func (b SuperExpr) EndLine() int { 188 | return b.Method.Line 189 | } 190 | 191 | func (b SuperExpr) Accept(visitor ExprVisitor) interface{} { 192 | return visitor.VisitSuperExpr(b) 193 | } 194 | 195 | type ThisExpr struct { 196 | Keyword Token 197 | } 198 | 199 | func (b ThisExpr) StartLine() int { 200 | // TODO implement me 201 | panic("implement me") 202 | } 203 | 204 | func (b ThisExpr) EndLine() int { 205 | // TODO implement me 206 | panic("implement me") 207 | } 208 | 209 | func (b ThisExpr) Accept(visitor ExprVisitor) interface{} { 210 | return visitor.VisitThisExpr(b) 211 | } 212 | 213 | type TernaryExpr struct { 214 | Cond Expr 215 | Consequent Expr 216 | Alternate Expr 217 | } 218 | 219 | func (b TernaryExpr) StartLine() int { 220 | // TODO implement me 221 | panic("implement me") 222 | } 223 | 224 | func (b TernaryExpr) EndLine() int { 225 | // TODO implement me 226 | panic("implement me") 227 | } 228 | 229 | func (b TernaryExpr) Accept(visitor ExprVisitor) interface{} { 230 | return visitor.VisitTernaryExpr(b) 231 | } 232 | 233 | type UnaryExpr struct { 234 | Operator Token 235 | Right Expr 236 | } 237 | 238 | func (b UnaryExpr) StartLine() int { 239 | // TODO implement me 240 | panic("implement me") 241 | } 242 | 243 | func (b UnaryExpr) EndLine() int { 244 | // TODO implement me 245 | panic("implement me") 246 | } 247 | 248 | func (b UnaryExpr) Accept(visitor ExprVisitor) interface{} { 249 | return visitor.VisitUnaryExpr(b) 250 | } 251 | 252 | type VariableExpr struct { 253 | Name Token 254 | } 255 | 256 | func (b VariableExpr) StartLine() int { 257 | return b.Name.Line 258 | } 259 | 260 | func (b VariableExpr) EndLine() int { 261 | return b.Name.Line 262 | } 263 | 264 | func (b VariableExpr) Accept(visitor ExprVisitor) interface{} { 265 | return visitor.VisitVariableExpr(b) 266 | } 267 | 268 | type ExprVisitor interface { 269 | VisitAssignExpr(expr AssignExpr) interface{} 270 | VisitBinaryExpr(expr BinaryExpr) interface{} 271 | VisitCallExpr(expr CallExpr) interface{} 272 | VisitFunctionExpr(expr FunctionExpr) interface{} 273 | VisitGetExpr(expr GetExpr) interface{} 274 | VisitGroupingExpr(expr GroupingExpr) interface{} 275 | VisitLiteralExpr(expr LiteralExpr) interface{} 276 | VisitLogicalExpr(expr LogicalExpr) interface{} 277 | VisitSetExpr(expr SetExpr) interface{} 278 | VisitSuperExpr(expr SuperExpr) interface{} 279 | VisitThisExpr(expr ThisExpr) interface{} 280 | VisitTernaryExpr(expr TernaryExpr) interface{} 281 | VisitUnaryExpr(expr UnaryExpr) interface{} 282 | VisitVariableExpr(expr VariableExpr) interface{} 283 | } 284 | -------------------------------------------------------------------------------- /ast/param.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | type Param struct { 4 | Token Token 5 | Type Type 6 | } 7 | -------------------------------------------------------------------------------- /ast/print.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | type astPrinter struct{} 8 | 9 | func (a astPrinter) VisitSuperExpr(expr SuperExpr) interface{} { 10 | // TODO implement me 11 | panic("implement me") 12 | } 13 | 14 | func (a astPrinter) VisitThisExpr(expr ThisExpr) interface{} { 15 | return expr.Keyword.Lexeme 16 | } 17 | 18 | func (a astPrinter) VisitGetExpr(expr GetExpr) interface{} { 19 | // TODO implement me 20 | panic("implement me") 21 | } 22 | 23 | func (a astPrinter) VisitSetExpr(expr SetExpr) interface{} { 24 | // TODO implement me 25 | panic("implement me") 26 | } 27 | 28 | func (a astPrinter) VisitFunctionExpr(expr FunctionExpr) interface{} { 29 | // TODO implement me 30 | panic("implement me") 31 | } 32 | 33 | func (a astPrinter) VisitCallExpr(expr CallExpr) interface{} { 34 | // TODO implement me 35 | panic("implement me") 36 | } 37 | 38 | // print returns a string representation of an Expr node 39 | func (a astPrinter) print(expr Expr) string { 40 | return expr.Accept(a).(string) 41 | } 42 | 43 | func (a astPrinter) VisitAssignExpr(expr AssignExpr) interface{} { 44 | return a.parenthesize("= "+expr.Name.Lexeme, expr.Value) 45 | } 46 | 47 | func (a astPrinter) VisitVariableExpr(expr VariableExpr) interface{} { 48 | return expr.Name.Lexeme 49 | } 50 | 51 | func (a astPrinter) VisitTernaryExpr(expr TernaryExpr) interface{} { 52 | return a.parenthesize("?:", expr.Cond, expr.Consequent, expr.Alternate) 53 | } 54 | 55 | func (a astPrinter) VisitBinaryExpr(expr BinaryExpr) interface{} { 56 | return a.parenthesize(expr.Operator.Lexeme, expr.Left, expr.Right) 57 | } 58 | 59 | func (a astPrinter) VisitGroupingExpr(expr GroupingExpr) interface{} { 60 | return a.parenthesize("group", expr.Expression) 61 | } 62 | 63 | func (a astPrinter) VisitLiteralExpr(expr LiteralExpr) interface{} { 64 | if expr.Value == nil { 65 | return "nil" 66 | } 67 | 68 | return fmt.Sprint(expr.Value) 69 | } 70 | 71 | func (a astPrinter) VisitUnaryExpr(expr UnaryExpr) interface{} { 72 | return a.parenthesize(expr.Operator.Lexeme, expr.Right) 73 | } 74 | 75 | func (a astPrinter) VisitLogicalExpr(expr LogicalExpr) interface{} { 76 | return a.parenthesize(expr.Operator.Lexeme, expr.Left, expr.Right) 77 | } 78 | 79 | func (a astPrinter) parenthesize(name string, exprs ...Expr) string { 80 | var str string 81 | 82 | str += "(" + name 83 | for _, expr := range exprs { 84 | str += " " + a.print(expr) 85 | } 86 | str += ")" 87 | 88 | return str 89 | } 90 | -------------------------------------------------------------------------------- /ast/stmt.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | type Stmt interface { 4 | Accept(visitor StmtVisitor) interface{} 5 | StartLine() int 6 | EndLine() int 7 | } 8 | 9 | type BlockStmt struct { 10 | Statements []Stmt 11 | } 12 | 13 | func (b BlockStmt) StartLine() int { 14 | // TODO implement me 15 | panic("implement me") 16 | } 17 | 18 | func (b BlockStmt) EndLine() int { 19 | // TODO implement me 20 | panic("implement me") 21 | } 22 | 23 | func (b BlockStmt) Accept(visitor StmtVisitor) interface{} { 24 | return visitor.VisitBlockStmt(b) 25 | } 26 | 27 | type Field struct { 28 | Name Token 29 | Value Expr 30 | Type Type 31 | } 32 | 33 | type ClassStmt struct { 34 | Name Token 35 | Superclass *VariableExpr 36 | Init *FunctionStmt 37 | Methods []FunctionStmt 38 | Fields []Field 39 | LineStart int 40 | LineEnd int 41 | } 42 | 43 | func (b ClassStmt) StartLine() int { 44 | return b.LineStart 45 | } 46 | 47 | func (b ClassStmt) EndLine() int { 48 | return b.LineEnd 49 | } 50 | 51 | func (b ClassStmt) Accept(visitor StmtVisitor) interface{} { 52 | return visitor.VisitClassStmt(b) 53 | } 54 | 55 | type ExpressionStmt struct { 56 | Expr Expr 57 | } 58 | 59 | func (b ExpressionStmt) StartLine() int { 60 | // TODO implement me 61 | panic("implement me") 62 | } 63 | 64 | func (b ExpressionStmt) EndLine() int { 65 | // TODO implement me 66 | panic("implement me") 67 | } 68 | 69 | func (b ExpressionStmt) Accept(visitor StmtVisitor) interface{} { 70 | return visitor.VisitExpressionStmt(b) 71 | } 72 | 73 | type FunctionStmt struct { 74 | Name Token 75 | Params []Param 76 | Body []Stmt 77 | ReturnType Type 78 | } 79 | 80 | func (b FunctionStmt) StartLine() int { 81 | // TODO implement me 82 | panic("implement me") 83 | } 84 | 85 | func (b FunctionStmt) EndLine() int { 86 | // TODO implement me 87 | panic("implement me") 88 | } 89 | 90 | func (b FunctionStmt) Accept(visitor StmtVisitor) interface{} { 91 | return visitor.VisitFunctionStmt(b) 92 | } 93 | 94 | type IfStmt struct { 95 | Condition Expr 96 | ThenBranch Stmt 97 | ElseBranch Stmt 98 | } 99 | 100 | func (b IfStmt) StartLine() int { 101 | // TODO implement me 102 | panic("implement me") 103 | } 104 | 105 | func (b IfStmt) EndLine() int { 106 | // TODO implement me 107 | panic("implement me") 108 | } 109 | 110 | func (b IfStmt) Accept(visitor StmtVisitor) interface{} { 111 | return visitor.VisitIfStmt(b) 112 | } 113 | 114 | type PrintStmt struct { 115 | Expr Expr 116 | } 117 | 118 | func (b PrintStmt) StartLine() int { 119 | // TODO implement me 120 | panic("implement me") 121 | } 122 | 123 | func (b PrintStmt) EndLine() int { 124 | // TODO implement me 125 | panic("implement me") 126 | } 127 | 128 | func (b PrintStmt) Accept(visitor StmtVisitor) interface{} { 129 | return visitor.VisitPrintStmt(b) 130 | } 131 | 132 | type ReturnStmt struct { 133 | Keyword Token 134 | Value Expr 135 | } 136 | 137 | func (b ReturnStmt) StartLine() int { 138 | // TODO implement me 139 | panic("implement me") 140 | } 141 | 142 | func (b ReturnStmt) EndLine() int { 143 | // TODO implement me 144 | panic("implement me") 145 | } 146 | 147 | func (b ReturnStmt) Accept(visitor StmtVisitor) interface{} { 148 | return visitor.VisitReturnStmt(b) 149 | } 150 | 151 | type WhileStmt struct { 152 | Condition Expr 153 | Body Stmt 154 | } 155 | 156 | func (b WhileStmt) StartLine() int { 157 | // TODO implement me 158 | panic("implement me") 159 | } 160 | 161 | func (b WhileStmt) EndLine() int { 162 | // TODO implement me 163 | panic("implement me") 164 | } 165 | 166 | func (b WhileStmt) Accept(visitor StmtVisitor) interface{} { 167 | return visitor.VisitWhileStmt(b) 168 | } 169 | 170 | type ContinueStmt struct { 171 | } 172 | 173 | func (b ContinueStmt) StartLine() int { 174 | // TODO implement me 175 | panic("implement me") 176 | } 177 | 178 | func (b ContinueStmt) EndLine() int { 179 | // TODO implement me 180 | panic("implement me") 181 | } 182 | 183 | func (b ContinueStmt) Accept(visitor StmtVisitor) interface{} { 184 | return visitor.VisitContinueStmt(b) 185 | } 186 | 187 | type BreakStmt struct { 188 | } 189 | 190 | func (b BreakStmt) StartLine() int { 191 | // TODO implement me 192 | panic("implement me") 193 | } 194 | 195 | func (b BreakStmt) EndLine() int { 196 | // TODO implement me 197 | panic("implement me") 198 | } 199 | 200 | func (b BreakStmt) Accept(visitor StmtVisitor) interface{} { 201 | return visitor.VisitBreakStmt(b) 202 | } 203 | 204 | type VarStmt struct { 205 | Name Token 206 | Initializer Expr 207 | TypeDecl Type 208 | } 209 | 210 | func (b VarStmt) StartLine() int { 211 | // TODO implement me 212 | panic("implement me") 213 | } 214 | 215 | func (b VarStmt) EndLine() int { 216 | // TODO implement me 217 | panic("implement me") 218 | } 219 | 220 | func (b VarStmt) Accept(visitor StmtVisitor) interface{} { 221 | return visitor.VisitVarStmt(b) 222 | } 223 | 224 | type TypeDeclStmt struct { 225 | Name Token 226 | Base Type 227 | } 228 | 229 | func (b TypeDeclStmt) StartLine() int { 230 | // TODO implement me 231 | panic("implement me") 232 | } 233 | 234 | func (b TypeDeclStmt) EndLine() int { 235 | // TODO implement me 236 | panic("implement me") 237 | } 238 | 239 | func (b TypeDeclStmt) Accept(visitor StmtVisitor) interface{} { 240 | return visitor.VisitTypeDeclStmt(b) 241 | } 242 | 243 | type StmtVisitor interface { 244 | VisitBlockStmt(stmt BlockStmt) interface{} 245 | VisitClassStmt(stmt ClassStmt) interface{} 246 | VisitExpressionStmt(stmt ExpressionStmt) interface{} 247 | VisitFunctionStmt(stmt FunctionStmt) interface{} 248 | VisitIfStmt(stmt IfStmt) interface{} 249 | VisitPrintStmt(stmt PrintStmt) interface{} 250 | VisitReturnStmt(stmt ReturnStmt) interface{} 251 | VisitWhileStmt(stmt WhileStmt) interface{} 252 | VisitContinueStmt(stmt ContinueStmt) interface{} 253 | VisitBreakStmt(stmt BreakStmt) interface{} 254 | VisitVarStmt(stmt VarStmt) interface{} 255 | VisitTypeDeclStmt(stmt TypeDeclStmt) interface{} 256 | } 257 | -------------------------------------------------------------------------------- /ast/token.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import "fmt" 4 | 5 | type TokenType uint8 6 | 7 | const ( 8 | // single-character tokens 9 | TokenLeftParen TokenType = iota 10 | TokenRightParen 11 | TokenLeftBrace 12 | TokenRightBrace 13 | TokenLeftBracket 14 | TokenRightBracket 15 | TokenComma 16 | TokenDot 17 | TokenMinus 18 | TokenPlus 19 | TokenSemicolon 20 | TokenSlash 21 | TokenStar 22 | TokenColon 23 | TokenQuestionMark 24 | TokenPipe 25 | 26 | // one or two character tokens 27 | TokenBang 28 | TokenBangEqual 29 | TokenEqual 30 | TokenEqualEqual 31 | TokenGreater 32 | TokenGreaterEqual 33 | TokenLess 34 | TokenLessEqual 35 | 36 | // literals 37 | TokenIdentifier 38 | TokenString 39 | TokenNumber 40 | 41 | // keywords 42 | TokenAnd 43 | TokenClass 44 | TokenElse 45 | TokenFalse 46 | TokenFun 47 | TokenFor 48 | TokenIf 49 | TokenNil 50 | TokenOr 51 | TokenPrint 52 | TokenReturn 53 | TokenSuper 54 | TokenThis 55 | TokenTrue 56 | TokenVar 57 | TokenWhile 58 | TokenBreak 59 | TokenContinue 60 | 61 | TokenTypeType 62 | 63 | TokenEof 64 | ) 65 | 66 | type Token struct { 67 | TokenType TokenType 68 | Lexeme string 69 | Literal interface{} 70 | Line int 71 | // Index from the start of the program. 72 | // At the moment, I use this only to discriminate identifier 73 | // tokens on the same line in the interpreter's locals map 74 | Start int 75 | } 76 | 77 | func (t Token) String() string { 78 | return fmt.Sprintf("%d %s %s", t.TokenType, t.Lexeme, t.Literal) 79 | } 80 | -------------------------------------------------------------------------------- /ast/type.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | type Type interface { 4 | } 5 | 6 | type ArrayType struct { 7 | Types []Type 8 | } 9 | 10 | type SingleType struct { 11 | Name string 12 | GenericArgs []Type 13 | } 14 | 15 | type UnionType struct { 16 | Left Type 17 | Right Type 18 | } 19 | -------------------------------------------------------------------------------- /env/env.go: -------------------------------------------------------------------------------- 1 | package env 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | // ErrUndefined is returned when retrieving or assigning to an undefined variable 8 | var ErrUndefined = errors.New("undefined variable") 9 | 10 | // Environment holds a map of key-value pairs as 11 | // well as a reference to an enclosing environment 12 | type Environment struct { 13 | Enclosing *Environment 14 | values map[string]interface{} 15 | } 16 | 17 | // New returns a new environment enclosed by the given environment 18 | func New(enclosing *Environment) *Environment { 19 | return &Environment{Enclosing: enclosing, values: make(map[string]interface{})} 20 | } 21 | 22 | // Define stores a new key-value pair 23 | func (e *Environment) Define(name string, value interface{}) { 24 | e.values[name] = value 25 | } 26 | 27 | // Assign defines a key-value pair in the environment if the key already exists. 28 | // If it doesn't exist in this environment, it checks the enclosing environment 29 | // and tries to assign the value there. If there are no other enclosing 30 | // environments to check and the key-value pair has not been assigned, it returns 31 | // an ErrUndefined. 32 | func (e *Environment) Assign(name string, value interface{}) error { 33 | if _, ok := e.values[name]; ok { 34 | e.Define(name, value) 35 | return nil 36 | } 37 | if e.Enclosing != nil { 38 | return e.Enclosing.Assign(name, value) 39 | } 40 | return ErrUndefined 41 | } 42 | 43 | // AssignAt sets the value of the key-value pair at a given distance from this environment 44 | func (e *Environment) AssignAt(distance int, name string, value interface{}) { 45 | e.ancestor(distance).values[name] = value 46 | } 47 | 48 | // Has returns true if a key-value pair with the given name exists 49 | // in this environment or any of its enclosing environments 50 | func (e *Environment) Has(name string) bool { 51 | _, ok := e.values[name] 52 | if ok { 53 | return true 54 | } 55 | if e.Enclosing != nil { 56 | return e.Enclosing.Has(name) 57 | } 58 | return false 59 | } 60 | 61 | // Get returns the value of the pair with the given name 62 | // in this environment or its enclosing environments. If 63 | // there are no other enclosing environments and the value 64 | // has not been found, it returns an ErrUndefined. 65 | func (e *Environment) Get(name string) (interface{}, error) { 66 | if val, ok := e.values[name]; ok { 67 | return val, nil 68 | } 69 | if e.Enclosing != nil { 70 | return e.Enclosing.Get(name) 71 | } 72 | return nil, ErrUndefined 73 | } 74 | 75 | // GetAt returns the value of the key-value pair at a given distance from this environment 76 | func (e *Environment) GetAt(distance int, name string) interface{} { 77 | return e.ancestor(distance).values[name] 78 | } 79 | 80 | // ancestor returns the environment at a given enclosing distance from this environment 81 | func (e *Environment) ancestor(distance int) *Environment { 82 | env := e 83 | for i := 0; i < distance; i++ { 84 | env = env.Enclosing 85 | } 86 | return env 87 | } 88 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/chidiwilliams/glox 2 | 3 | go 1.17 4 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chidiwilliams/glox/7bae84376ac826fae75e0e67d714ffd1759426c7/go.sum -------------------------------------------------------------------------------- /interpret/class.go: -------------------------------------------------------------------------------- 1 | package interpret 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/chidiwilliams/glox/ast" 7 | "github.com/chidiwilliams/glox/env" 8 | ) 9 | 10 | type class struct { 11 | name string 12 | initializer *function 13 | methods map[string]function 14 | superclass *class 15 | fields []ast.Field 16 | env *env.Environment 17 | } 18 | 19 | // arity returns the arity of the class's constructor 20 | func (c class) arity() int { 21 | if c.initializer == nil { 22 | return 0 23 | } 24 | return c.initializer.arity() 25 | } 26 | 27 | // call-s the class's constructor and returns the new instance 28 | func (c class) call(interpreter *Interpreter, arguments []interface{}) interface{} { 29 | in := &instance{class: c} 30 | 31 | // interpret fields 32 | previous := interpreter.environment 33 | interpreter.environment = c.env 34 | for _, field := range c.fields { 35 | value := interpreter.evaluate(field.Value) 36 | in.set(field.Name, value) 37 | } 38 | interpreter.environment = previous 39 | 40 | // initialize 41 | if c.initializer != nil { 42 | c.initializer.bind(in).call(interpreter, arguments) 43 | } 44 | 45 | return in 46 | } 47 | 48 | // Get returns value of the static method with the given name 49 | func (c class) Get(in *Interpreter, name ast.Token) (interface{}, error) { 50 | method := c.findMethod(name.Lexeme) 51 | if method == nil { 52 | return nil, runtimeError{token: name, msg: fmt.Sprintf("Undefined property '%s'.", name.Lexeme)} 53 | } 54 | return method, nil 55 | } 56 | 57 | // todo: should probably return a pointer 58 | func (c class) findMethod(name string) *function { 59 | if name == "init" { 60 | if c.initializer == nil { 61 | return nil 62 | } 63 | return c.initializer 64 | } 65 | 66 | method, ok := c.methods[name] 67 | if ok { 68 | return &method 69 | } 70 | if c.superclass != nil { 71 | return c.superclass.findMethod(name) 72 | } 73 | return nil 74 | } 75 | 76 | func (c class) String() string { 77 | return c.name 78 | } 79 | 80 | type Instance interface { 81 | Get(in *Interpreter, name ast.Token) (interface{}, error) 82 | } 83 | 84 | // instance is an instance of a class 85 | type instance struct { 86 | class class 87 | fields map[string]interface{} 88 | } 89 | 90 | // Get returns value of the field or method with the given name. If 91 | // the field is a getter, it runs the method body and returns the result. 92 | func (i *instance) Get(in *Interpreter, name ast.Token) (interface{}, error) { 93 | if val, ok := i.fields[name.Lexeme]; ok { 94 | return val, nil 95 | } 96 | 97 | method := i.class.findMethod(name.Lexeme) 98 | if method != nil { 99 | // if the method is a getter, call and return its value 100 | if method.isGetter { 101 | return method.bind(i).call(in, nil), nil 102 | } 103 | return method.bind(i), nil 104 | } 105 | 106 | return nil, runtimeError{token: name, msg: fmt.Sprintf("Undefined property '%s'.", name.Lexeme)} 107 | } 108 | 109 | // set-s the value of a field 110 | func (i *instance) set(name ast.Token, value interface{}) { 111 | if i.fields == nil { 112 | i.fields = make(map[string]interface{}) 113 | } 114 | i.fields[name.Lexeme] = value 115 | } 116 | -------------------------------------------------------------------------------- /interpret/clock.go: -------------------------------------------------------------------------------- 1 | package interpret 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | type clock struct{} 8 | 9 | func (c clock) arity() int { 10 | return 0 11 | } 12 | 13 | func (c clock) call(_ *Interpreter, _ []interface{}) interface{} { 14 | return float64(time.Now().UnixMilli()) 15 | } 16 | 17 | func (c clock) String() string { 18 | return "" 19 | } 20 | -------------------------------------------------------------------------------- /interpret/function.go: -------------------------------------------------------------------------------- 1 | package interpret 2 | 3 | import ( 4 | "github.com/chidiwilliams/glox/ast" 5 | "github.com/chidiwilliams/glox/env" 6 | ) 7 | 8 | type callable interface { 9 | arity() int 10 | call(in *Interpreter, args []interface{}) interface{} 11 | } 12 | 13 | type function struct { 14 | declaration ast.FunctionStmt 15 | closure *env.Environment 16 | isInitializer bool 17 | isGetter bool 18 | } 19 | 20 | func (f function) arity() int { 21 | return len(f.declaration.Params) 22 | } 23 | 24 | func (f function) call(interpreter *Interpreter, args []interface{}) (returnVal interface{}) { 25 | defer func() { 26 | if err := recover(); err != nil { 27 | if v, ok := err.(Return); ok { 28 | if f.isInitializer { 29 | returnVal = f.closure.GetAt(0, "this") 30 | return 31 | } 32 | returnVal = v.Value 33 | return 34 | } 35 | panic(err) 36 | } 37 | }() 38 | 39 | callEnv := env.New(f.closure) 40 | for i, v := range f.declaration.Params { 41 | callEnv.Define(v.Token.Lexeme, args[i]) 42 | } 43 | interpreter.executeBlock(f.declaration.Body, callEnv) 44 | 45 | if f.isInitializer { 46 | return f.closure.GetAt(0, "this") 47 | } 48 | 49 | return nil 50 | } 51 | 52 | func (f function) bind(i *instance) function { 53 | closureEnv := env.New(f.closure) 54 | closureEnv.Define("this", i) 55 | return function{ 56 | declaration: f.declaration, 57 | closure: closureEnv, 58 | isInitializer: f.isInitializer, 59 | } 60 | } 61 | 62 | type functionExpr struct { 63 | declaration ast.FunctionExpr 64 | closure *env.Environment 65 | } 66 | 67 | func (f functionExpr) arity() int { 68 | return len(f.declaration.Params) 69 | } 70 | 71 | func (f functionExpr) call(in *Interpreter, args []interface{}) (returnVal interface{}) { 72 | defer func() { 73 | if err := recover(); err != nil { 74 | if v, ok := err.(Return); ok { 75 | returnVal = v.Value 76 | return 77 | } 78 | panic(err) 79 | } 80 | }() 81 | 82 | callEnv := env.New(f.closure) 83 | for i, v := range f.declaration.Params { 84 | callEnv.Define(v.Token.Lexeme, args[i]) 85 | } 86 | in.executeBlock(f.declaration.Body, callEnv) 87 | return nil 88 | } 89 | -------------------------------------------------------------------------------- /interpret/interpreter.go: -------------------------------------------------------------------------------- 1 | package interpret 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | 7 | "github.com/chidiwilliams/glox/ast" 8 | "github.com/chidiwilliams/glox/env" 9 | ) 10 | 11 | type runtimeError struct { 12 | token ast.Token 13 | msg string 14 | } 15 | 16 | func (r runtimeError) Error() string { 17 | return fmt.Sprintf("%s\n[line %d]", r.msg, r.token.Line) 18 | } 19 | 20 | // Interpreter holds the globals and current execution 21 | // environment for a program to be executed 22 | type Interpreter struct { 23 | // current execution environment 24 | environment *env.Environment 25 | // global variables 26 | globals *env.Environment 27 | // standard output 28 | stdOut io.Writer 29 | // standard error 30 | stdErr io.Writer 31 | // map of "stringified" expression to depth where to find the local. 32 | // we cannot use the ast.Expr as the key directly, because CallExpr 33 | // is an unhashable type (it has a field that is a slice). 34 | locals map[string]int 35 | } 36 | 37 | // NewInterpreter sets up a new interpreter with its environment and config 38 | func NewInterpreter(stdOut io.Writer, stdErr io.Writer) *Interpreter { 39 | globals := env.New(nil) 40 | globals.Define("clock", clock{}) 41 | 42 | return &Interpreter{ 43 | globals: globals, 44 | environment: globals, 45 | stdOut: stdOut, 46 | stdErr: stdErr, 47 | locals: make(map[string]int), 48 | } 49 | } 50 | 51 | // Interpret interprets a list of statements within the interpreter's environment 52 | func (in *Interpreter) Interpret(stmts []ast.Stmt) (result interface{}, hadRuntimeError bool) { 53 | defer func() { 54 | if err := recover(); err != nil { 55 | if e, ok := err.(runtimeError); ok { 56 | _, _ = in.stdErr.Write([]byte(e.Error() + "\n")) 57 | hadRuntimeError = true 58 | } else { 59 | fmt.Printf("Error: %s\n", err) 60 | } 61 | } 62 | }() 63 | 64 | for _, statement := range stmts { 65 | result = in.execute(statement) 66 | } 67 | 68 | return 69 | } 70 | 71 | func (in Interpreter) error(token ast.Token, message string) { 72 | panic(runtimeError{token: token, msg: message}) 73 | } 74 | 75 | func (in *Interpreter) execute(stmt ast.Stmt) interface{} { 76 | return stmt.Accept(in) 77 | } 78 | 79 | // Resolve sets the depth of a local variable access 80 | func (in *Interpreter) Resolve(expr ast.Expr, depth int) { 81 | in.locals[in.asString(expr)] = depth 82 | } 83 | 84 | // GetLocalDistance returns the depth of a resolved local variable access 85 | func (in *Interpreter) GetLocalDistance(expr ast.Expr) (int, bool) { 86 | distance, ok := in.locals[in.asString(expr)] 87 | return distance, ok 88 | } 89 | 90 | func (in *Interpreter) evaluate(expr ast.Expr) interface{} { 91 | return expr.Accept(in) 92 | } 93 | 94 | func (in *Interpreter) VisitBlockStmt(stmt ast.BlockStmt) interface{} { 95 | in.executeBlock(stmt.Statements, env.New(in.environment)) 96 | return nil 97 | } 98 | 99 | func (in *Interpreter) VisitClassStmt(stmt ast.ClassStmt) interface{} { 100 | var superclass *class 101 | if stmt.Superclass != nil { 102 | superclassValue, ok := in.evaluate(stmt.Superclass).(class) 103 | if !ok { 104 | in.error(stmt.Superclass.Name, "Superclass must be a class.") 105 | } 106 | superclass = &superclassValue 107 | } 108 | 109 | in.environment.Define(stmt.Name.Lexeme, nil) 110 | 111 | if superclass != nil { 112 | in.environment = env.New(in.environment) 113 | in.environment.Define("super", superclass) 114 | } 115 | 116 | var initializer *function 117 | if stmt.Init != nil { 118 | initializer = &function{ 119 | declaration: *stmt.Init, 120 | closure: in.environment, 121 | isInitializer: true, 122 | isGetter: false, 123 | } 124 | } 125 | 126 | methods := make(map[string]function, len(stmt.Methods)) 127 | for _, method := range stmt.Methods { 128 | fn := function{ 129 | declaration: method, 130 | closure: in.environment, 131 | isInitializer: false, 132 | isGetter: method.Params == nil, // is this the best way to know it's a getter? 133 | } 134 | methods[method.Name.Lexeme] = fn 135 | } 136 | 137 | class := class{ 138 | name: stmt.Name.Lexeme, 139 | methods: methods, 140 | superclass: superclass, 141 | fields: stmt.Fields, 142 | env: in.environment, 143 | initializer: initializer, 144 | } 145 | 146 | if superclass != nil { 147 | in.environment = in.environment.Enclosing 148 | } 149 | 150 | err := in.environment.Assign(stmt.Name.Lexeme, class) 151 | if err != nil { 152 | panic(err) 153 | } 154 | return nil 155 | } 156 | 157 | func (in *Interpreter) VisitVarStmt(stmt ast.VarStmt) interface{} { 158 | var val interface{} 159 | if stmt.Initializer != nil { 160 | val = in.evaluate(stmt.Initializer) 161 | } 162 | in.environment.Define(stmt.Name.Lexeme, val) 163 | return nil 164 | } 165 | 166 | func (in *Interpreter) VisitIfStmt(stmt ast.IfStmt) interface{} { 167 | if in.isTruthy(in.evaluate(stmt.Condition)) { 168 | in.execute(stmt.ThenBranch) 169 | } else if stmt.ElseBranch != nil { 170 | in.execute(stmt.ElseBranch) 171 | } 172 | return nil 173 | } 174 | 175 | type Break struct{} 176 | 177 | func (in *Interpreter) VisitWhileStmt(stmt ast.WhileStmt) interface{} { 178 | // Exit while stmt if a break is called 179 | defer func() { 180 | if err := recover(); err != nil { 181 | if _, ok := err.(Break); !ok { 182 | panic(err) 183 | } 184 | } 185 | }() 186 | 187 | for in.isTruthy(in.evaluate(stmt.Condition)) { 188 | in.executeLoopBody(stmt.Body) 189 | } 190 | return nil 191 | } 192 | 193 | type Continue struct{} 194 | 195 | func (in *Interpreter) executeLoopBody(body ast.Stmt) interface{} { 196 | // Exit current body if continue panic is found 197 | defer func() { 198 | if err := recover(); err != nil { 199 | if _, ok := err.(Continue); !ok { 200 | panic(err) 201 | } 202 | } 203 | }() 204 | 205 | in.execute(body) 206 | return nil 207 | } 208 | 209 | func (in *Interpreter) VisitContinueStmt(_ ast.ContinueStmt) interface{} { 210 | panic(Continue{}) 211 | } 212 | 213 | func (in *Interpreter) VisitBreakStmt(_ ast.BreakStmt) interface{} { 214 | panic(Break{}) 215 | } 216 | 217 | func (in *Interpreter) VisitLogicalExpr(expr ast.LogicalExpr) interface{} { 218 | left := in.evaluate(expr.Left) 219 | if expr.Operator.TokenType == ast.TokenOr { 220 | if in.isTruthy(left) { 221 | return left 222 | } 223 | } else { // and 224 | if !in.isTruthy(left) { 225 | return left 226 | } 227 | } 228 | return in.evaluate(expr.Right) 229 | } 230 | 231 | func (in *Interpreter) VisitExpressionStmt(stmt ast.ExpressionStmt) interface{} { 232 | return in.evaluate(stmt.Expr) 233 | } 234 | 235 | // VisitFunctionStmt creates a new function from a function statement and 236 | // the current environment and defines the function in the current environment 237 | func (in *Interpreter) VisitFunctionStmt(stmt ast.FunctionStmt) interface{} { 238 | fn := function{declaration: stmt, closure: in.environment} 239 | in.environment.Define(stmt.Name.Lexeme, fn) 240 | return nil 241 | } 242 | 243 | // VisitPrintStmt evaluates the statement's expression and prints 244 | // the result to the interpreter's standard output 245 | func (in *Interpreter) VisitPrintStmt(stmt ast.PrintStmt) interface{} { 246 | value := in.evaluate(stmt.Expr) 247 | _, _ = in.stdOut.Write([]byte(in.stringify(value) + "\n")) 248 | return nil 249 | } 250 | 251 | type Return struct { 252 | Value interface{} 253 | } 254 | 255 | func (in *Interpreter) VisitTypeDeclStmt(stmt ast.TypeDeclStmt) interface{} { 256 | return nil 257 | } 258 | 259 | func (in *Interpreter) VisitReturnStmt(stmt ast.ReturnStmt) interface{} { 260 | var value interface{} 261 | if stmt.Value != nil { 262 | value = in.evaluate(stmt.Value) 263 | } 264 | panic(Return{Value: value}) 265 | } 266 | 267 | func (in *Interpreter) VisitAssignExpr(expr ast.AssignExpr) interface{} { 268 | value := in.evaluate(expr.Value) 269 | 270 | distance, ok := in.GetLocalDistance(expr) 271 | if ok { 272 | in.environment.AssignAt(distance, expr.Name.Lexeme, value) 273 | } else { 274 | if err := in.globals.Assign(expr.Name.Lexeme, value); err != nil { 275 | panic(err) 276 | } 277 | } 278 | 279 | return value 280 | } 281 | 282 | func (in *Interpreter) VisitCallExpr(expr ast.CallExpr) interface{} { 283 | callee := in.evaluate(expr.Callee) 284 | 285 | args := make([]interface{}, len(expr.Arguments)) 286 | for i, arg := range expr.Arguments { 287 | args[i] = in.evaluate(arg) 288 | } 289 | 290 | fn, ok := (callee).(callable) 291 | if !ok { 292 | in.error(expr.Paren, "Can only call functions and classes.") 293 | } 294 | 295 | if len(args) != fn.arity() { 296 | in.error(expr.Paren, 297 | fmt.Sprintf("Expected %d arguments but got %d.", fn.arity(), len(args))) 298 | } 299 | 300 | return fn.call(in, args) 301 | } 302 | 303 | func (in *Interpreter) VisitGetExpr(expr ast.GetExpr) interface{} { 304 | object := in.evaluate(expr.Object) 305 | if instance, ok := object.(Instance); ok { 306 | val, err := instance.Get(in, expr.Name) 307 | if err != nil { 308 | panic(err) 309 | } 310 | return val 311 | } 312 | in.error(expr.Name, "Only instances have properties.") 313 | return nil 314 | } 315 | 316 | func (in *Interpreter) VisitVariableExpr(expr ast.VariableExpr) interface{} { 317 | val, err := in.lookupVariable(expr.Name, expr) 318 | if err != nil { 319 | panic(err) 320 | } 321 | return val 322 | } 323 | 324 | // lookupVariable returns the value of a variable 325 | func (in *Interpreter) lookupVariable(name ast.Token, expr ast.Expr) (interface{}, error) { 326 | // If the variable is a local variable, find it in the resolved enclosing scope 327 | if distance, ok := in.GetLocalDistance(expr); ok { 328 | return in.environment.GetAt(distance, name.Lexeme), nil 329 | } 330 | return in.globals.Get(name.Lexeme) 331 | } 332 | 333 | func (in *Interpreter) VisitBinaryExpr(expr ast.BinaryExpr) interface{} { 334 | left := in.evaluate(expr.Left) 335 | right := in.evaluate(expr.Right) 336 | 337 | switch expr.Operator.TokenType { 338 | case ast.TokenPlus: 339 | _, leftIsFloat := left.(float64) 340 | _, rightIsFloat := right.(float64) 341 | if leftIsFloat && rightIsFloat { 342 | return left.(float64) + right.(float64) 343 | } 344 | _, leftIsString := left.(string) 345 | _, rightIsString := right.(string) 346 | if leftIsString && rightIsString { 347 | return left.(string) + right.(string) 348 | } 349 | in.error(expr.Operator, "Operands must be two numbers or two strings") 350 | case ast.TokenMinus: 351 | in.checkNumberOperands(expr.Operator, left, right) 352 | return left.(float64) - right.(float64) 353 | case ast.TokenSlash: 354 | in.checkNumberOperands(expr.Operator, left, right) 355 | return left.(float64) / right.(float64) 356 | case ast.TokenStar: 357 | in.checkNumberOperands(expr.Operator, left, right) 358 | return left.(float64) * right.(float64) 359 | // comparison 360 | case ast.TokenGreater: 361 | in.checkNumberOperands(expr.Operator, left, right) 362 | return left.(float64) > right.(float64) 363 | case ast.TokenGreaterEqual: 364 | in.checkNumberOperands(expr.Operator, left, right) 365 | return left.(float64) >= right.(float64) 366 | case ast.TokenLess: 367 | in.checkNumberOperands(expr.Operator, left, right) 368 | return left.(float64) < right.(float64) 369 | case ast.TokenLessEqual: 370 | in.checkNumberOperands(expr.Operator, left, right) 371 | return left.(float64) <= right.(float64) 372 | case ast.TokenEqualEqual: 373 | return left == right 374 | case ast.TokenBangEqual: 375 | return left != right 376 | case ast.TokenComma: 377 | return right 378 | } 379 | return nil 380 | } 381 | 382 | // VisitFunctionExpr creates a new function from the function expression and the 383 | // current environment. The name of the function expression is defined within its block. 384 | func (in *Interpreter) VisitFunctionExpr(expr ast.FunctionExpr) interface{} { 385 | closureEnv := env.New(in.environment) 386 | fn := functionExpr{declaration: expr, closure: closureEnv} 387 | if expr.Name != nil { 388 | fn.closure.Define(expr.Name.Lexeme, fn) 389 | } 390 | 391 | return fn 392 | } 393 | 394 | func (in *Interpreter) VisitGroupingExpr(expr ast.GroupingExpr) interface{} { 395 | return in.evaluate(expr.Expression) 396 | } 397 | 398 | func (in *Interpreter) VisitLiteralExpr(expr ast.LiteralExpr) interface{} { 399 | return expr.Value 400 | } 401 | 402 | func (in *Interpreter) VisitSetExpr(expr ast.SetExpr) interface{} { 403 | object := in.evaluate(expr.Object) 404 | 405 | inst, ok := object.(*instance) 406 | if !ok { 407 | in.error(expr.Name, "Only instances have fields.") 408 | } 409 | 410 | value := in.evaluate(expr.Value) 411 | inst.set(expr.Name, value) 412 | return nil 413 | } 414 | 415 | func (in *Interpreter) VisitSuperExpr(expr ast.SuperExpr) interface{} { 416 | distance, _ := in.GetLocalDistance(expr) 417 | superclass := in.environment.GetAt(distance, "super").(*class) 418 | object := in.environment.GetAt(distance-1, "this").(*instance) 419 | method := superclass.findMethod(expr.Method.Lexeme) 420 | if method == nil { 421 | in.error(expr.Method, fmt.Sprintf("Undefined property '%s'.", expr.Method.Lexeme)) 422 | } 423 | return method.bind(object) 424 | } 425 | 426 | func (in *Interpreter) VisitThisExpr(expr ast.ThisExpr) interface{} { 427 | val, err := in.lookupVariable(expr.Keyword, expr) 428 | if err != nil { 429 | panic(err) 430 | } 431 | return val 432 | } 433 | 434 | func (in *Interpreter) VisitUnaryExpr(expr ast.UnaryExpr) interface{} { 435 | right := in.evaluate(expr.Right) 436 | switch expr.Operator.TokenType { 437 | case ast.TokenBang: 438 | return !in.isTruthy(right) 439 | case ast.TokenMinus: 440 | in.checkNumberOperand(expr.Operator, right) 441 | return -right.(float64) 442 | } 443 | return nil 444 | } 445 | 446 | func (in *Interpreter) VisitTernaryExpr(expr ast.TernaryExpr) interface{} { 447 | cond := in.evaluate(expr.Cond) 448 | if in.isTruthy(cond) { 449 | return in.evaluate(expr.Consequent) 450 | } 451 | return in.evaluate(expr.Alternate) 452 | } 453 | 454 | func (in *Interpreter) executeBlock(statements []ast.Stmt, env *env.Environment) { 455 | // Restore the current environment after executing the block 456 | previous := in.environment 457 | defer func() { 458 | in.environment = previous 459 | }() 460 | 461 | in.environment = env 462 | for _, statement := range statements { 463 | in.execute(statement) 464 | } 465 | } 466 | 467 | func (in *Interpreter) isTruthy(val interface{}) bool { 468 | if val == nil { 469 | return false 470 | } 471 | if v, ok := val.(bool); ok { 472 | return v 473 | } 474 | return true 475 | } 476 | 477 | func (in *Interpreter) checkNumberOperand(operator ast.Token, operand interface{}) { 478 | if _, ok := operand.(float64); ok { 479 | return 480 | } 481 | panic(runtimeError{operator, "Operand must be a number"}) 482 | } 483 | 484 | func (in *Interpreter) checkNumberOperands(operator ast.Token, left interface{}, right interface{}) { 485 | if _, ok := left.(float64); ok { 486 | if _, ok = right.(float64); ok { 487 | return 488 | } 489 | } 490 | panic(runtimeError{operator, "Operands must be number"}) 491 | } 492 | 493 | func (in *Interpreter) stringify(value interface{}) string { 494 | if value == nil { 495 | return "nil" 496 | } 497 | return fmt.Sprint(value) 498 | } 499 | 500 | func (in *Interpreter) asString(expr ast.Expr) string { 501 | return fmt.Sprintf("%#v", expr) 502 | } 503 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | //go:generate go run cmd/ast.go 2 | package main 3 | 4 | import ( 5 | "bufio" 6 | "flag" 7 | "fmt" 8 | "io" 9 | "io/ioutil" 10 | "os" 11 | 12 | "github.com/chidiwilliams/glox/ast" 13 | "github.com/chidiwilliams/glox/interpret" 14 | "github.com/chidiwilliams/glox/parse" 15 | "github.com/chidiwilliams/glox/resolve" 16 | "github.com/chidiwilliams/glox/scan" 17 | ) 18 | 19 | var ( 20 | hadError bool 21 | hadRuntimeError bool 22 | 23 | r = newRunner(os.Stdout, os.Stderr) 24 | ) 25 | 26 | func main() { 27 | var filePath string 28 | 29 | flag.StringVar(&filePath, "filePath", "", "File path") 30 | flag.Parse() 31 | 32 | if filePath == "" { 33 | runPrompt() 34 | } else { 35 | runFile(filePath) 36 | } 37 | } 38 | 39 | func runPrompt() { 40 | inputScanner := bufio.NewScanner(os.Stdin) 41 | for { 42 | fmt.Print("> ") 43 | if !inputScanner.Scan() { 44 | break 45 | } 46 | 47 | line := inputScanner.Text() 48 | fmt.Println(r.run(line)) 49 | hadError = false 50 | } 51 | } 52 | 53 | func runFile(path string) { 54 | file, err := ioutil.ReadFile(path) 55 | if err != nil { 56 | panic(err) 57 | } 58 | 59 | r.run(string(file)) 60 | if hadError { 61 | os.Exit(65) 62 | } 63 | if hadRuntimeError { 64 | os.Exit(70) 65 | } 66 | } 67 | 68 | func newRunner(stdOut io.Writer, stdErr io.Writer) runner { 69 | return runner{interpreter: interpret.NewInterpreter(stdOut, stdErr), stdErr: stdErr} 70 | } 71 | 72 | type runner struct { 73 | interpreter *interpret.Interpreter 74 | stdErr io.Writer 75 | } 76 | 77 | func (r *runner) run(source string) interface{} { 78 | scanner := scan.NewScanner(source, r.stdErr) 79 | tokens := scanner.ScanTokens() 80 | 81 | parser := parse.NewParser(tokens, r.stdErr) 82 | var statements []ast.Stmt 83 | statements, hadError = parser.Parse() 84 | 85 | if hadError { 86 | return nil 87 | } 88 | 89 | resolver := resolve.NewResolver(r.interpreter, r.stdErr) 90 | hadError = resolver.ResolveStmts(statements) 91 | 92 | if hadError { 93 | return nil 94 | } 95 | 96 | var result interface{} 97 | result, hadRuntimeError = r.interpreter.Interpret(statements) 98 | return result 99 | } 100 | -------------------------------------------------------------------------------- /main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "os" 6 | "path/filepath" 7 | "strconv" 8 | "strings" 9 | "testing" 10 | ) 11 | 12 | func Test_Run(t *testing.T) { 13 | tests := []struct { 14 | name string 15 | source string 16 | stdOut string 17 | stdErr string 18 | }{ 19 | // atoms 20 | {"string", "print \"hello world\";", "hello world\n", ""}, 21 | {"multi-line string", "print \"hello\nworld\";", "hello\nworld\n", ""}, 22 | {"number", "print 342.32461932591235;", "342.32461932591235\n", ""}, 23 | {"string as boolean", "print \"\" and 34;", "34\n", ""}, 24 | {"nil as boolean", "print nil and 34;", "nil\n", ""}, 25 | {"line ending without semi-colon", "print", "", "[line 1] Error at end: Expect expression.\n"}, 26 | 27 | // comments 28 | {"single-line comment after source", "print 1 + 1; // hello", "2\n", ""}, 29 | {"single-line comment", `// hello 30 | print 1 + 1;`, "2\n", ""}, 31 | 32 | // unary, binary, and ternary operations 33 | {"arithmetic operations", "print -1 + 2 * 3 - 4 / 5;", "4.2\n", ""}, 34 | {"decimal arithmetic", "print 1.234 / 5.678;", "0.2173300457907714\n", ""}, 35 | {"logical operations", "print (!true or false) and false;", "false\n", ""}, 36 | {"ternary", "print 3 < 4 ? 2 > 5 ? \"no\" : \"yes\" : \"also no\";", "yes\n", ""}, 37 | {"string concatenation", "print \"hello\" + \" \" + \"world\";", "hello world\n", ""}, 38 | {"greater than or equal to", "print 4 >= 3 ? 3 >= 3 ? 2 >= 3 : true : true;", "false\n", ""}, 39 | {"less than or equal to", "print 4 <= 5 ? 5 <= 5 ? 6 <= 5 : true : true;", "false\n", ""}, 40 | {"equal to", "print 5 == 5 ? 4 == 5 : true;", "false\n", ""}, 41 | {"not equal to", "print 4 != 5 ? 5 != 5 : true;", "false\n", ""}, 42 | {"comma", "print (1, 2);", "2\n", ""}, 43 | 44 | // variables 45 | {"variable declaration", "var a = 10; print a*2;", "20\n", ""}, 46 | {"variable assignment after declaration", "var a; a = 20; print a*2;", "40\n", ""}, 47 | {"variable re-assignment", "var a = 10; print a; a = 20; print a*2;", "10\n40\n", ""}, 48 | 49 | // block scoping 50 | {"block scoping", `var a = "global a"; 51 | var b = "global b"; 52 | var c = "global c"; 53 | { 54 | var a = "outer a"; 55 | var b = "outer b"; 56 | { 57 | var a = "inner a"; 58 | print a; 59 | print b; 60 | print c; 61 | } 62 | print a; 63 | print b; 64 | print c; 65 | } 66 | print a; 67 | print b; 68 | print c;`, "inner a\nouter b\nglobal c\nouter a\nouter b\nglobal c\nglobal a\nglobal b\nglobal c\n", ""}, 69 | 70 | // conditionals 71 | {"if block", "if (true) { if (false) { print \"hello\"; } else { print \"world\"; } }", "world\n", ""}, 72 | 73 | // loops 74 | {"for loop", `var a = 0; 75 | var temp; 76 | 77 | for (var b = 1; a < 10; b = temp + b) { 78 | print a; 79 | temp = a; 80 | a = b; 81 | }`, "0\n1\n1\n2\n3\n5\n8\n", ""}, 82 | {"while loop", `var a = 0; 83 | var temp; 84 | var b = 1; 85 | 86 | while (a < 10) { 87 | print a; 88 | temp = a; 89 | a = b; 90 | b = temp + b; 91 | }`, "0\n1\n1\n2\n3\n5\n8\n", ""}, 92 | {"break statement", `var a = 1; 93 | while (true) { 94 | a = a + 1; 95 | print a; 96 | if (a == 4) break; 97 | }`, "2\n3\n4\n", ""}, 98 | {"continue statement", `var a = 1; 99 | while (a < 10) { 100 | a = a * 2; 101 | print a; 102 | if (a > 4) { 103 | continue; 104 | } else { 105 | a = a + 1; 106 | } 107 | }`, "2\n6\n12\n", ""}, 108 | 109 | // functions 110 | {"function", `fun sayHi(first, last) { 111 | print "Hello, " + first + " " + last; 112 | } 113 | 114 | sayHi("Dear", "Reader");`, "Hello, Dear Reader\n", ""}, 115 | {"return statement", `fun sayHi(first, last) { 116 | return "Hello, " + first + " " + last; 117 | } 118 | 119 | print sayHi("Dear", "Reader");`, "Hello, Dear Reader\n", ""}, 120 | {"closure", `fun makeCounter() { 121 | var i = 0; 122 | fun count() { 123 | i = i + 1; 124 | print i; 125 | } 126 | return count; 127 | } 128 | 129 | var counter = makeCounter(); 130 | counter(); 131 | counter();`, "1\n2\n", ""}, 132 | {"anonymous function", `fun makeCounter() { 133 | var i = 0; 134 | return fun () { 135 | i = i + 1; 136 | print i; 137 | }; 138 | } 139 | 140 | var counter = makeCounter(); 141 | counter(); 142 | counter();`, "1\n2\n", ""}, 143 | {"iife", `(fun count(next) { 144 | print next; 145 | if (next < 5) return count(next + 1); 146 | return; 147 | })(1);`, "1\n2\n3\n4\n5\n", ""}, 148 | {"calling function with wrong arity", `fun sayHello(a, b) { 149 | print a + b; 150 | } 151 | sayHello("only first");`, "", "Expected 2 arguments but got 1.\n[line 3]\n"}, 152 | 153 | // Variable scoping 154 | {"scoping", `var a = "global"; 155 | { 156 | fun showA() { 157 | print a; 158 | } 159 | 160 | showA(); 161 | var a = "block"; 162 | showA(); 163 | a; // mutes error about the local variable not being used 164 | }`, "global\nglobal\n", ""}, 165 | {"re-declaring variables in same scope", `{ 166 | var a = "global"; 167 | var a = "global2"; 168 | }`, "", "[line 2] Error at 'a': Already a variable with this name in this scope\n[line 2] Error at 'a': Variable 'a' declared but not used.\n"}, 169 | {"unused local variable", `{ 170 | var a = "global"; 171 | }`, "", "[line 1] Error at 'a': Variable 'a' declared but not used.\n"}, 172 | 173 | // Classes 174 | {"class method", `class Bacon { 175 | eat() { 176 | print "Crunch crunch"; 177 | } 178 | } 179 | Bacon().eat(); 180 | `, "Crunch crunch\n", ""}, 181 | {"call and assign", `class Person { 182 | init(age) { 183 | this.age = age; 184 | } 185 | 186 | getAge() { 187 | return this.age; 188 | } 189 | } 190 | 191 | var person = Person(0); 192 | var sum = 0; 193 | sum = sum + person.getAge(); 194 | print sum;`, "0\n", ""}, 195 | {"this", `class Cake { 196 | taste() { 197 | var adjective = "delicious"; 198 | print "The " + this.flavor + " cake is " + adjective + "!"; 199 | } 200 | } 201 | 202 | var cake = Cake(); 203 | cake.flavor = "German chocolate"; 204 | cake.taste(); 205 | `, "The German chocolate cake is delicious!\n", ""}, 206 | {"init class", `class Circle { 207 | init(radius) { 208 | this.radius = radius; 209 | } 210 | 211 | area() { 212 | return 3.141592653 * this.radius * this.radius; 213 | } 214 | } 215 | 216 | var circle = Circle(7); 217 | print circle.area();`, "153.938039997\n", ""}, 218 | {"class with static methods", `class Math { 219 | add(x, y) { 220 | return x + y; 221 | } 222 | } 223 | 224 | print Math.add(1, 2);`, "3\n", ""}, 225 | {"getter", `class Circle { 226 | init(radius) { 227 | this.radius = radius; 228 | } 229 | 230 | area { 231 | return 3.141592653 * this.radius * this.radius; 232 | } 233 | } 234 | 235 | var circle = Circle(7); 236 | print circle.area;`, "153.938039997\n", ""}, 237 | {"inheritance", `class Doughnut { 238 | cook() { 239 | print "Fry until golden brown."; 240 | } 241 | } 242 | 243 | class BostonCream < Doughnut {} 244 | 245 | BostonCream.cook();`, "Fry until golden brown.\n", ""}, 246 | {"calling super", `class Doughnut { 247 | cook() { 248 | print "Fry until golden brown."; 249 | } 250 | } 251 | 252 | class BostonCream < Doughnut { 253 | cook() { 254 | super.cook(); 255 | print "Pipe full of custard and coat with chocolate."; 256 | } 257 | } 258 | 259 | BostonCream().cook();`, "Fry until golden brown.\nPipe full of custard and coat with chocolate.\n", ""}, 260 | {"calling super outside class", `super.hello();`, "", "[line 0] Error at 'super': Can't use 'super' outside of a class.\n"}, 261 | {"calling super in class with no superclass", `class Doughnut { 262 | cook() { 263 | super.cook(); 264 | } 265 | }`, "", "[line 2] Error at 'super': Can't use 'super' in a class with no superclass.\n"}, 266 | } 267 | 268 | for _, tt := range tests { 269 | t.Run(tt.name, func(t *testing.T) { 270 | stdOut := &bytes.Buffer{} 271 | stdErr := &bytes.Buffer{} 272 | r := newRunner(stdOut, stdErr) 273 | r.run(tt.source) 274 | 275 | if stdOut.String() != tt.stdOut { 276 | t.Errorf("stdOut: got %s, expected %s", strconv.Quote(stdOut.String()), strconv.Quote(tt.stdOut)) 277 | } 278 | 279 | if stdErr.String() != tt.stdErr { 280 | t.Errorf("stdErr: got %s, expected %s", strconv.Quote(stdErr.String()), strconv.Quote(tt.stdErr)) 281 | } 282 | }) 283 | } 284 | } 285 | 286 | func TestRunFromFile(t *testing.T) { 287 | paths, err := filepath.Glob(filepath.Join("testdata", "*.input")) 288 | if err != nil { 289 | t.Fatal(err) 290 | } 291 | 292 | for _, path := range paths { 293 | _, filename := filepath.Split(path) 294 | testName := filename[:len(filename)-len(filepath.Ext(path))] 295 | 296 | t.Run(testName, func(t *testing.T) { 297 | source, err := os.ReadFile(path) 298 | if err != nil { 299 | t.Fatal("error reading test source file:", err) 300 | } 301 | 302 | goldenFile := filepath.Join("testdata", testName+".golden") 303 | want, err := os.ReadFile(goldenFile) 304 | if err != nil { 305 | t.Fatal("error reading golden file", err) 306 | } 307 | wantOutput := strings.Trim(string(want), "\n") 308 | 309 | wantStdOut, wantStdErr := splitWantOutput(wantOutput) 310 | 311 | stdOut := &bytes.Buffer{} 312 | stdErr := &bytes.Buffer{} 313 | r := newRunner(stdOut, stdErr) 314 | r.run(string(source)) 315 | 316 | if stdOut.String() != wantStdOut { 317 | t.Errorf("stdOut: got %s, expected %s", strconv.Quote(stdOut.String()), strconv.Quote(wantStdOut)) 318 | } 319 | 320 | if stdErr.String() != wantStdErr { 321 | t.Errorf("stdErr: got %s, expected %s", strconv.Quote(stdErr.String()), strconv.Quote(wantStdErr)) 322 | } 323 | }) 324 | } 325 | } 326 | 327 | func splitWantOutput(output string) (stdOut string, stdErr string) { 328 | lines := strings.Split(output, "\n") 329 | for _, line := range lines { 330 | if len(line) >= 5 && line[:5] == "err: " { 331 | stdErr += line + "\n" 332 | } else { 333 | stdOut += line + "\n" 334 | } 335 | } 336 | 337 | return 338 | } 339 | -------------------------------------------------------------------------------- /parse/parser.go: -------------------------------------------------------------------------------- 1 | package parse 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | 7 | "github.com/chidiwilliams/glox/ast" 8 | ) 9 | 10 | type parseError struct { 11 | msg string 12 | } 13 | 14 | func (p parseError) Error() string { 15 | return p.msg 16 | } 17 | 18 | // Parser parses a flat list of tokens into 19 | // an AST representation of the source program 20 | type Parser struct { 21 | tokens []ast.Token 22 | current int 23 | loop int 24 | hadError bool 25 | stdErr io.Writer 26 | } 27 | 28 | // NewParser returns a new Parser that reads a list of tokens 29 | func NewParser(tokens []ast.Token, stdErr io.Writer) *Parser { 30 | return &Parser{tokens: tokens, stdErr: stdErr} 31 | } 32 | 33 | /** 34 | Parser grammar: 35 | 36 | program => declaration* EOF 37 | declaration => classDecl | funcDecl | varDecl | typeDecl | statement 38 | classDecl => "class" IDENTIFIER ( "<" IDENTIFIER )? "{" ( method | field )* "}" 39 | method => IDENTIFIER parameterList? ( ":" type )? block 40 | field => IDENTIFIER "=" expression ";" 41 | funDecl => "fun" function 42 | function => IDENTIFIER parameterList ( ":" type )? block 43 | parameters => IDENTIFIER ( ":" type )? ( "," IDENTIFIER ( ":" type )? )* 44 | varDecl => "var" IDENTIFIER ( ":" type )? ( "=" expression )? ";" 45 | type => ( "[" type ( "," type )* "]" 46 | | IDENTIFIER ( "<" type ( "," type )* ">" )? ) 47 | ( "|" type )* 48 | typeDecl => "type" IDENTIFIER "=" type ";" 49 | statement => exprStmt | ifStmt | forStmt | printStmt | returnStmt | whileStmt 50 | | breakStmt | continueStmt | block 51 | exprStmt => expression ";" 52 | ifStmt => "if" "(" expression ")" statement ( "else" statement )? 53 | forStmt => "for" "(" ( varDecl | exprStmt | ";" ) expression? ";" expression? ")" statement 54 | printStmt => "print" expression ";" 55 | returnStmt => "return" expression? ";" 56 | whileStmt => "while" "(" expression ")" statement 57 | block => "{" declaration* "}" ; 58 | expression => series 59 | series => assignment ( "," assignment )* 60 | assignment => ( call "." )? IDENTIFIER "=" assignment | ternary 61 | ternary => logic_or ( "?" ternary ":" ternary )* 62 | logic_or => logic_and ( "or" logic_and )* 63 | logic_and => equality ( "and" equality )* 64 | equality => comparison ( ( "!=" | "==" ) comparison ) 65 | comparison => term ( ( ">" | ">=" | "<" | "<=" ) term )* 66 | term => factor ( ( "+" | "-" ) factor )* 67 | factor => unary ( ( "/" | "*" ) unary )* 68 | unary => ( "!" | "-" ) unary | call 69 | call => primary ( "(" arguments? ")" | "." IDENTIFIER )* 70 | arguments => expression ( "," expression )* 71 | primary => NUMBER | STRING | "true" | "false" | "nil" | "(" expression ")" 72 | | IDENTIFIER | functionExpr | "super" . IDENTIFIER 73 | functionExpr => "fun" IDENTIFIER? parameterList ( ":" type )? block 74 | 75 | Reference: C Operator Precedence https://en.cppreference.com/w/c/language/operator_precedence 76 | 77 | */ 78 | 79 | // Parse reads the list of tokens and returns a 80 | // list of statements representing the source program 81 | func (p *Parser) Parse() ([]ast.Stmt, bool) { 82 | var statements []ast.Stmt 83 | for !p.isAtEnd() { 84 | stmt := p.declaration() 85 | statements = append(statements, stmt) 86 | } 87 | return statements, p.hadError 88 | } 89 | 90 | // declaration parses declaration statements. A declaration statement is 91 | // a variable declaration or a regular statement. If the statement contains 92 | // a parse error, it skips to the start of the next statement and returns nil. 93 | func (p *Parser) declaration() ast.Stmt { 94 | defer func() { 95 | if err := recover(); err != nil { 96 | // If the error is a parseError, synchronize to 97 | // the next statement. If not, propagate the panic. 98 | if _, ok := err.(parseError); ok { 99 | p.hadError = true 100 | p.synchronize() 101 | } else { 102 | panic(err) 103 | } 104 | } 105 | }() 106 | 107 | if p.match(ast.TokenClass) { 108 | return p.classDeclaration() 109 | } 110 | if p.match(ast.TokenFun) { 111 | return p.function("function") 112 | } 113 | if p.match(ast.TokenVar) { 114 | return p.varDeclaration() 115 | } 116 | if p.match(ast.TokenTypeType) { 117 | return p.typeDeclaration() 118 | } 119 | return p.statement() 120 | } 121 | 122 | func (p *Parser) classDeclaration() ast.Stmt { 123 | lineStart := p.previous().Line 124 | name := p.consume(ast.TokenIdentifier, "Expect class name.") 125 | 126 | var superclass *ast.VariableExpr 127 | if p.match(ast.TokenLess) { 128 | p.consume(ast.TokenIdentifier, "Expect superclass name.") 129 | superclass = &ast.VariableExpr{Name: p.previous()} 130 | } 131 | 132 | p.consume(ast.TokenLeftBrace, "Expect '{' before class body.") 133 | 134 | var initMethod *ast.FunctionStmt 135 | methods := make([]ast.FunctionStmt, 0) 136 | fields := make([]ast.Field, 0) 137 | for !p.check(ast.TokenRightBrace) && !p.isAtEnd() { 138 | // Is there another way to do this besides peeking next? 139 | if p.peekNext().TokenType == ast.TokenEqual || p.peekNext().TokenType == ast.TokenColon { 140 | field := p.field() 141 | fields = append(fields, field) 142 | } else { 143 | method := p.function("method") 144 | if method.Name.Lexeme == "init" { 145 | initMethod = &method 146 | } else { 147 | methods = append(methods, method) 148 | } 149 | } 150 | } 151 | 152 | p.consume(ast.TokenRightBrace, "Expect '}' after class body.") 153 | 154 | return ast.ClassStmt{ 155 | Name: name, 156 | Fields: fields, 157 | Init: initMethod, 158 | Methods: methods, 159 | Superclass: superclass, 160 | LineStart: lineStart, 161 | LineEnd: p.previous().Line, 162 | } 163 | } 164 | 165 | func (p *Parser) varDeclaration() ast.Stmt { 166 | name := p.consume(ast.TokenIdentifier, "Expect variable name") 167 | var typeDecl ast.Type 168 | if p.match(ast.TokenColon) { 169 | typeDecl = p.typeAnnotation() 170 | } 171 | var initializer ast.Expr 172 | if p.match(ast.TokenEqual) { 173 | initializer = p.expression() 174 | } 175 | p.consume(ast.TokenSemicolon, "Expect ';' after variable declaration") 176 | return ast.VarStmt{Name: name, Initializer: initializer, TypeDecl: typeDecl} 177 | } 178 | 179 | func (p *Parser) typeDeclaration() ast.Stmt { 180 | name := p.consume(ast.TokenIdentifier, "Expect type name") 181 | p.consume(ast.TokenEqual, "Expect '=' after type name") 182 | base := p.typeAnnotation() 183 | p.consume(ast.TokenSemicolon, "Expect ';' after type declaration") 184 | return ast.TypeDeclStmt{Name: name, Base: base} 185 | } 186 | 187 | func (p *Parser) typeAnnotation() ast.Type { 188 | var parsedType ast.Type 189 | 190 | // Array type 191 | if p.match(ast.TokenLeftBracket) { 192 | types := make([]ast.Type, 0) 193 | 194 | for !p.check(ast.TokenRightBracket) { 195 | nextType := p.typeAnnotation() 196 | types = append(types, nextType) 197 | 198 | if !p.match(ast.TokenComma) { 199 | break 200 | } 201 | } 202 | 203 | p.consume(ast.TokenRightBracket, "Expect ']' after type list.") 204 | 205 | parsedType = ast.ArrayType{Types: types} 206 | } else if p.match(ast.TokenIdentifier) { 207 | typeName := p.previous().Lexeme 208 | 209 | var genericArgs []ast.Type 210 | if p.match(ast.TokenLess) { 211 | genericArgs = make([]ast.Type, 0) 212 | 213 | for !p.check(ast.TokenGreater) { 214 | nextType := p.typeAnnotation() 215 | genericArgs = append(genericArgs, nextType) 216 | 217 | if !p.match(ast.TokenComma) { 218 | break 219 | } 220 | } 221 | 222 | p.consume(ast.TokenGreater, "Expect '>' after generic arguments.") 223 | } 224 | 225 | parsedType = ast.SingleType{Name: typeName, GenericArgs: genericArgs} 226 | } else { 227 | p.error(p.previous(), "Could not parse type annotation.") 228 | } 229 | 230 | for p.match(ast.TokenPipe) { 231 | nextType := p.typeAnnotation() 232 | parsedType = ast.UnionType{Left: parsedType, Right: nextType} 233 | } 234 | 235 | return parsedType 236 | } 237 | 238 | // statement parses statements. A statement can be a print, 239 | // if, while, block or expression statement. 240 | func (p *Parser) statement() ast.Stmt { 241 | if p.match(ast.TokenPrint) { 242 | return p.printStatement() 243 | } 244 | if p.match(ast.TokenLeftBrace) { 245 | stmt := p.block() 246 | return ast.BlockStmt{Statements: stmt} 247 | } 248 | if p.match(ast.TokenIf) { 249 | return p.ifStatement() 250 | } 251 | if p.match(ast.TokenWhile) { 252 | p.loop++ 253 | defer func() { p.loop-- }() 254 | return p.whileStatement() 255 | } 256 | if p.match(ast.TokenFor) { 257 | p.loop++ 258 | defer func() { p.loop-- }() 259 | return p.forStatement() 260 | } 261 | if p.match(ast.TokenBreak) { 262 | if p.loop == 0 { 263 | p.error(p.previous(), "Break outside loop") 264 | } 265 | p.consume(ast.TokenSemicolon, "Expect ';' after break") 266 | return ast.BreakStmt{} 267 | } 268 | if p.match(ast.TokenContinue) { 269 | if p.loop == 0 { 270 | p.error(p.previous(), "Continue outside loop") 271 | } 272 | p.consume(ast.TokenSemicolon, "Expect ';' after continue") 273 | return ast.ContinueStmt{} 274 | } 275 | if p.match(ast.TokenReturn) { 276 | return p.returnStatement() 277 | } 278 | return p.expressionStatement() 279 | } 280 | 281 | func (p *Parser) forStatement() ast.Stmt { 282 | p.consume(ast.TokenLeftParen, "Expect '(' after 'for'.") 283 | 284 | var initializer ast.Stmt 285 | if p.match(ast.TokenSemicolon) { 286 | initializer = nil 287 | } else if p.match(ast.TokenVar) { 288 | initializer = p.varDeclaration() 289 | } else { 290 | initializer = p.expressionStatement() 291 | } 292 | 293 | var condition ast.Expr 294 | if !p.check(ast.TokenSemicolon) { 295 | condition = p.expression() 296 | 297 | } 298 | p.consume(ast.TokenSemicolon, "Expect ';' after loop condition.") 299 | 300 | var increment ast.Expr 301 | if !p.check(ast.TokenRightParen) { 302 | increment = p.expression() 303 | 304 | } 305 | p.consume(ast.TokenRightParen, "Expect ')' after for clauses.") 306 | body := p.statement() 307 | 308 | if increment != nil { 309 | body = ast.BlockStmt{Statements: []ast.Stmt{body, ast.ExpressionStmt{Expr: increment}}} 310 | } 311 | 312 | if condition == nil { 313 | condition = ast.LiteralExpr{Value: true} 314 | } 315 | body = ast.WhileStmt{Body: body, Condition: condition} 316 | 317 | if initializer != nil { 318 | body = ast.BlockStmt{Statements: []ast.Stmt{initializer, body}} 319 | } 320 | 321 | return body 322 | } 323 | 324 | func (p *Parser) printStatement() ast.Stmt { 325 | expr := p.expression() 326 | p.consume(ast.TokenSemicolon, "Expect ';' after value") 327 | return ast.PrintStmt{Expr: expr} 328 | } 329 | 330 | func (p *Parser) returnStatement() ast.Stmt { 331 | keyword := p.previous() 332 | var value ast.Expr 333 | if !p.check(ast.TokenSemicolon) { 334 | value = p.expression() 335 | } 336 | p.consume(ast.TokenSemicolon, "Expect ';' after return value.") 337 | return ast.ReturnStmt{Keyword: keyword, Value: value} 338 | } 339 | 340 | func (p *Parser) block() []ast.Stmt { 341 | var statements []ast.Stmt 342 | for !p.check(ast.TokenRightBrace) && !p.isAtEnd() { 343 | stmt := p.declaration() 344 | statements = append(statements, stmt) 345 | } 346 | p.consume(ast.TokenRightBrace, "Expect '}' after block.") 347 | return statements 348 | } 349 | 350 | func (p *Parser) ifStatement() ast.Stmt { 351 | p.consume(ast.TokenLeftParen, "Expect '(' after 'if'.") 352 | condition := p.expression() 353 | p.consume(ast.TokenRightParen, "Expect ')' after if condition.") 354 | 355 | thenBranch := p.statement() 356 | var elseBranch ast.Stmt 357 | if p.match(ast.TokenElse) { 358 | elseBranch = p.statement() 359 | } 360 | 361 | return ast.IfStmt{Condition: condition, ThenBranch: thenBranch, ElseBranch: elseBranch} 362 | } 363 | 364 | func (p *Parser) whileStatement() ast.Stmt { 365 | p.consume(ast.TokenLeftParen, "Expect '(' after 'while'.") 366 | condition := p.expression() 367 | p.consume(ast.TokenRightParen, "Expect ')' after while condition.") 368 | body := p.statement() 369 | return ast.WhileStmt{Condition: condition, Body: body} 370 | } 371 | 372 | // expressionStatement parses expression statements 373 | func (p *Parser) expressionStatement() ast.Stmt { 374 | // parse the next expression 375 | expr := p.expression() 376 | // panic if the next token is not a semicolon 377 | p.consume(ast.TokenSemicolon, "Expect ';' after value") 378 | return ast.ExpressionStmt{Expr: expr} 379 | } 380 | 381 | func (p *Parser) field() ast.Field { 382 | name := p.consume(ast.TokenIdentifier, "Expect field name.") 383 | 384 | var fieldType ast.Type 385 | if p.match(ast.TokenColon) { 386 | fieldType = p.typeAnnotation() 387 | } 388 | 389 | var value ast.Expr 390 | if p.match(ast.TokenEqual) { 391 | value = p.expression() 392 | } 393 | 394 | p.consume(ast.TokenSemicolon, "Expect ';' after field.") 395 | 396 | return ast.Field{Name: name, Value: value, Type: fieldType} 397 | } 398 | 399 | func (p *Parser) function(kind string) ast.FunctionStmt { 400 | name := p.consume(ast.TokenIdentifier, "Expect "+kind+" name.") 401 | 402 | // Nil parameters used to check if the method is a getter. Should it use a field of its own? 403 | var parameters []ast.Param 404 | var returnType ast.Type 405 | 406 | if kind != "method" || p.check(ast.TokenLeftParen) { 407 | parameters = p.parameterList(kind) 408 | 409 | if p.match(ast.TokenColon) { 410 | returnType = p.typeAnnotation() 411 | } 412 | } 413 | 414 | p.consume(ast.TokenLeftBrace, "Expect '{' before "+kind+" body.") 415 | 416 | body := p.block() 417 | return ast.FunctionStmt{ 418 | Name: name, 419 | Params: parameters, 420 | Body: body, 421 | ReturnType: returnType, 422 | } 423 | } 424 | 425 | func (p *Parser) expression() ast.Expr { 426 | return p.series() 427 | } 428 | 429 | func (p *Parser) series() ast.Expr { 430 | expr := p.assignment() 431 | 432 | for p.match(ast.TokenComma) { 433 | operator := p.previous() 434 | right := p.assignment() 435 | expr = ast.BinaryExpr{Left: expr, Operator: operator, Right: right} 436 | } 437 | 438 | return expr 439 | } 440 | 441 | func (p *Parser) assignment() ast.Expr { 442 | expr := p.ternary() 443 | 444 | if p.match(ast.TokenEqual) { 445 | equals := p.previous() 446 | value := p.assignment() 447 | 448 | if varExpr, ok := expr.(ast.VariableExpr); ok { 449 | return ast.AssignExpr{Name: varExpr.Name, Value: value} 450 | } else if getExpr, ok := expr.(ast.GetExpr); ok { 451 | return ast.SetExpr{ 452 | Object: getExpr.Object, 453 | Name: getExpr.Name, 454 | Value: value, 455 | } 456 | } 457 | p.error(equals, "Invalid assignment target.") 458 | } 459 | 460 | return expr 461 | } 462 | 463 | func (p *Parser) ternary() ast.Expr { 464 | expr := p.or() 465 | 466 | if p.match(ast.TokenQuestionMark) { 467 | cond1 := p.ternary() 468 | p.consume(ast.TokenColon, "Expect ':' after conditional.") 469 | cond2 := p.ternary() 470 | expr = ast.TernaryExpr{Cond: expr, Consequent: cond1, Alternate: cond2} 471 | } 472 | 473 | return expr 474 | } 475 | 476 | func (p *Parser) or() ast.Expr { 477 | expr := p.and() 478 | 479 | for p.match(ast.TokenOr) { 480 | operator := p.previous() 481 | right := p.and() 482 | expr = ast.LogicalExpr{Left: expr, Operator: operator, Right: right} 483 | } 484 | return expr 485 | } 486 | 487 | func (p *Parser) and() ast.Expr { 488 | expr := p.equality() 489 | 490 | for p.match(ast.TokenAnd) { 491 | operator := p.previous() 492 | right := p.equality() 493 | expr = ast.LogicalExpr{Left: expr, Operator: operator, Right: right} 494 | } 495 | return expr 496 | } 497 | 498 | func (p *Parser) equality() ast.Expr { 499 | expr := p.comparison() 500 | 501 | for p.match(ast.TokenBangEqual, ast.TokenEqualEqual) { 502 | operator := p.previous() 503 | right := p.comparison() 504 | expr = ast.BinaryExpr{Left: expr, Operator: operator, Right: right} 505 | } 506 | 507 | return expr 508 | } 509 | 510 | func (p *Parser) comparison() ast.Expr { 511 | expr := p.term() 512 | 513 | for p.match(ast.TokenGreater, ast.TokenGreaterEqual, ast.TokenLess, ast.TokenLessEqual) { 514 | operator := p.previous() 515 | right := p.term() 516 | expr = ast.BinaryExpr{Left: expr, Operator: operator, Right: right} 517 | } 518 | 519 | return expr 520 | } 521 | 522 | func (p *Parser) term() ast.Expr { 523 | expr := p.factor() 524 | 525 | for p.match(ast.TokenMinus, ast.TokenPlus) { 526 | operator := p.previous() 527 | right := p.factor() 528 | expr = ast.BinaryExpr{Left: expr, Operator: operator, Right: right} 529 | } 530 | 531 | return expr 532 | } 533 | 534 | func (p *Parser) factor() ast.Expr { 535 | expr := p.unary() 536 | 537 | for p.match(ast.TokenSlash, ast.TokenStar) { 538 | operator := p.previous() 539 | right := p.unary() 540 | expr = ast.BinaryExpr{Left: expr, Operator: operator, Right: right} 541 | } 542 | 543 | return expr 544 | } 545 | 546 | func (p *Parser) unary() ast.Expr { 547 | if p.match(ast.TokenBang, ast.TokenMinus) { 548 | operator := p.previous() 549 | right := p.unary() 550 | return ast.UnaryExpr{Operator: operator, Right: right} 551 | } 552 | 553 | return p.call() 554 | } 555 | 556 | func (p *Parser) call() ast.Expr { 557 | expr := p.primary() 558 | 559 | for { 560 | if p.match(ast.TokenLeftParen) { 561 | expr = p.finishCall(expr) 562 | } else if p.match(ast.TokenDot) { 563 | name := p.consume(ast.TokenIdentifier, "Expect property name after '.'.") 564 | expr = ast.GetExpr{Object: expr, Name: name} 565 | } else { 566 | break 567 | } 568 | } 569 | 570 | return expr 571 | } 572 | 573 | func (p *Parser) finishCall(callee ast.Expr) ast.Expr { 574 | args := make([]ast.Expr, 0) 575 | if !p.check(ast.TokenRightParen) { 576 | for { 577 | if len(args) >= 255 { 578 | p.error(p.peek(), "Can't have more than 255 arguments.") 579 | } 580 | expr := p.assignment() 581 | args = append(args, expr) // Didn't use p.expression() because an expression can be a series! 582 | if !p.match(ast.TokenComma) { 583 | break 584 | } 585 | } 586 | } 587 | paren := p.consume(ast.TokenRightParen, "Expect ')' after arguments.") 588 | return ast.CallExpr{Callee: callee, Paren: paren, Arguments: args} 589 | } 590 | 591 | func (p *Parser) primary() ast.Expr { 592 | switch { 593 | case p.match(ast.TokenFalse): 594 | return p.literalAtCurrentLine(false) 595 | case p.match(ast.TokenTrue): 596 | return p.literalAtCurrentLine(true) 597 | case p.match(ast.TokenNil): 598 | return p.literalAtCurrentLine(nil) 599 | case p.match(ast.TokenNumber, ast.TokenString): 600 | return p.literalAtCurrentLine(p.previous().Literal) 601 | case p.match(ast.TokenLeftParen): 602 | expr := p.expression() 603 | p.consume(ast.TokenRightParen, "Expect ')' after expression.") 604 | return ast.GroupingExpr{Expression: expr} 605 | case p.match(ast.TokenIdentifier): 606 | return ast.VariableExpr{Name: p.previous()} 607 | case p.match(ast.TokenFun): 608 | return p.functionExpression() 609 | case p.match(ast.TokenThis): 610 | return ast.ThisExpr{Keyword: p.previous()} 611 | case p.match(ast.TokenSuper): 612 | keyword := p.previous() 613 | p.consume(ast.TokenDot, "Expect '.' after 'super'.") 614 | method := p.consume(ast.TokenIdentifier, "Expect superclass method name.") 615 | return ast.SuperExpr{Keyword: keyword, Method: method} 616 | } 617 | 618 | p.error(p.peek(), "Expect expression.") 619 | return nil 620 | } 621 | 622 | func (p *Parser) literalAtCurrentLine(value interface{}) ast.LiteralExpr { 623 | // TODO: How about multi-line strings? Is that supported? If yes, get the correct value for LineEnd 624 | return ast.LiteralExpr{Value: value, LineStart: p.previous().Line, LineEnd: p.previous().Line} 625 | } 626 | 627 | // functionExpression parses a function expression. 628 | // A function expression may be a named or anonymous function. 629 | func (p *Parser) functionExpression() ast.Expr { 630 | var name *ast.Token 631 | if !p.check(ast.TokenLeftParen) { 632 | fnName := p.consume(ast.TokenIdentifier, "Expect function name.") 633 | name = &fnName 634 | } 635 | 636 | parameters := p.parameterList("function") 637 | 638 | var returnType ast.Type 639 | if p.match(ast.TokenColon) { 640 | returnType = p.typeAnnotation() 641 | } 642 | 643 | p.consume(ast.TokenLeftBrace, "Expect '{' before function body.") 644 | 645 | body := p.block() 646 | return ast.FunctionExpr{Name: name, Params: parameters, Body: body, ReturnType: returnType} 647 | } 648 | 649 | func (p *Parser) parameterList(kind string) []ast.Param { 650 | p.consume(ast.TokenLeftParen, "Expect '(' after "+kind+" name.") 651 | 652 | parameters := make([]ast.Param, 0) 653 | if !p.check(ast.TokenRightParen) { 654 | for { 655 | if len(parameters) >= 255 { 656 | p.error(p.peek(), "Can't have more than 255 parameters.") 657 | } 658 | paramToken := p.consume(ast.TokenIdentifier, "Expect parameter name.") 659 | var paramType ast.Type 660 | if p.match(ast.TokenColon) { 661 | paramType = p.typeAnnotation() 662 | } 663 | parameters = append(parameters, ast.Param{Token: paramToken, Type: paramType}) 664 | if !p.match(ast.TokenComma) { 665 | break 666 | } 667 | } 668 | } 669 | 670 | p.consume(ast.TokenRightParen, "Expect ')' after parameters.") 671 | return parameters 672 | } 673 | 674 | // consume checks that the next ast.Token is of the given ast.TokenType and then 675 | // advances to the next token. If the check fails, it panics with the given message. 676 | func (p *Parser) consume(tokenType ast.TokenType, message string) ast.Token { 677 | if p.check(tokenType) { 678 | return p.advance() 679 | } 680 | p.error(p.peek(), message) 681 | return ast.Token{} 682 | } 683 | 684 | func (p *Parser) error(token ast.Token, message string) { 685 | var where string 686 | if token.TokenType == ast.TokenEof { 687 | where = " at end" 688 | } else { 689 | where = " at '" + token.Lexeme + "'" 690 | } 691 | 692 | err := parseError{msg: fmt.Sprintf("[line %d] Error%s: %s\n", token.Line+1, where, message)} 693 | _, _ = p.stdErr.Write([]byte(err.Error())) 694 | panic(err) 695 | } 696 | 697 | func (p *Parser) synchronize() { 698 | p.advance() 699 | for !p.isAtEnd() { 700 | if p.previous().TokenType == ast.TokenSemicolon { 701 | return 702 | } 703 | 704 | switch p.peek().TokenType { 705 | case ast.TokenClass, ast.TokenFor, ast.TokenFun, ast.TokenIf, 706 | ast.TokenPrint, ast.TokenReturn, ast.TokenVar, ast.TokenWhile, 707 | ast.TokenBreak, ast.TokenContinue: 708 | return 709 | } 710 | 711 | p.advance() 712 | } 713 | } 714 | 715 | func (p *Parser) match(types ...ast.TokenType) bool { 716 | for _, tokenType := range types { 717 | if p.check(tokenType) { 718 | p.advance() 719 | return true 720 | } 721 | } 722 | 723 | return false 724 | } 725 | 726 | func (p *Parser) check(tokenType ast.TokenType) bool { 727 | if p.isAtEnd() { 728 | return false 729 | } 730 | 731 | return p.peek().TokenType == tokenType 732 | } 733 | 734 | func (p *Parser) advance() ast.Token { 735 | if !p.isAtEnd() { 736 | p.current++ 737 | } 738 | return p.previous() 739 | } 740 | 741 | func (p *Parser) isAtEnd() bool { 742 | return p.peek().TokenType == ast.TokenEof 743 | } 744 | 745 | func (p *Parser) peek() ast.Token { 746 | return p.tokens[p.current] 747 | } 748 | 749 | func (p *Parser) previous() ast.Token { 750 | return p.tokens[p.current-1] 751 | } 752 | 753 | func (p *Parser) peekNext() ast.Token { 754 | return p.tokens[p.current+1] 755 | } 756 | -------------------------------------------------------------------------------- /resolve/resolver.go: -------------------------------------------------------------------------------- 1 | package resolve 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | 7 | "github.com/chidiwilliams/glox/ast" 8 | "github.com/chidiwilliams/glox/interpret" 9 | ) 10 | 11 | type functionType int 12 | 13 | const ( 14 | functionTypeNone functionType = iota 15 | functionTypeFunction 16 | functionTypeMethod 17 | functionTypeInitializer 18 | ) 19 | 20 | type classType int 21 | 22 | const ( 23 | classTypeNone classType = iota 24 | classTypeClass 25 | classTypeSubClass 26 | ) 27 | 28 | type scopeVar struct { 29 | token ast.Token 30 | defined bool 31 | used bool 32 | } 33 | 34 | // scope describes all the local variables 35 | // declared and defined in the current scope 36 | type scope map[string]*scopeVar 37 | 38 | // declare a variable with the given name in this scope 39 | func (s scope) declare(name string, token ast.Token) { 40 | s[name] = &scopeVar{token: token} 41 | } 42 | 43 | // define a variable with the given name in this scope 44 | func (s scope) define(name string) { 45 | s[name].defined = true 46 | } 47 | 48 | // has returns whether a variable with the given 49 | // name is declared and defined in this scope 50 | func (s scope) has(name string) (declared, defined bool) { 51 | v, ok := s[name] 52 | if !ok { 53 | return false, false 54 | } 55 | return true, v.defined 56 | } 57 | 58 | // use sets a variable as used in this scope 59 | func (s scope) use(name string) { 60 | s[name].used = true 61 | } 62 | 63 | func (s scope) set(name string) { 64 | s[name] = &scopeVar{defined: true, used: true} 65 | } 66 | 67 | type scopes []scope 68 | 69 | func (s *scopes) peek() scope { 70 | return (*s)[len(*s)-1] 71 | } 72 | 73 | func (s *scopes) push(scope scope) { 74 | *s = append(*s, scope) 75 | } 76 | 77 | func (s *scopes) pop() { 78 | *s = (*s)[:len(*s)-1] 79 | } 80 | 81 | // Resolver resolves local variables in a program. It reports 82 | // to the interpreter the variable to use each time a local 83 | // variable is accessed in the program. 84 | type Resolver struct { 85 | // the program Interpreter 86 | interpreter *interpret.Interpreter 87 | // scopes is a stack of scope-s 88 | scopes scopes 89 | // currentFunction is the functionType of the 90 | // current enclosing function. The Resolver uses 91 | // the field to report an error when a return 92 | // statement appears outside a function 93 | currentFunction functionType 94 | // the classType of the current enclosing class, used 95 | // to report an error when "this" appears outside a class 96 | currentClass classType 97 | stdErr io.Writer 98 | hadError bool 99 | } 100 | 101 | // NewResolver returns a new Resolver 102 | func NewResolver(interpreter *interpret.Interpreter, stdErr io.Writer) *Resolver { 103 | return &Resolver{interpreter: interpreter, stdErr: stdErr} 104 | } 105 | 106 | // ResolveStmts resolves all the local variables in a list of statements 107 | func (r *Resolver) ResolveStmts(statements []ast.Stmt) (hadError bool) { 108 | for _, statement := range statements { 109 | r.resolveStmt(statement) 110 | } 111 | return r.hadError 112 | } 113 | 114 | // resolveExpr resolves an expression 115 | func (r *Resolver) resolveExpr(expr ast.Expr) { 116 | expr.Accept(r) 117 | } 118 | 119 | // resolveStmt resolves a statement 120 | func (r *Resolver) resolveStmt(stmt ast.Stmt) { 121 | stmt.Accept(r) 122 | } 123 | 124 | // resolveFunction resolves a function statement. It begins a 125 | // new scope and resolves the function body within the scope. 126 | func (r *Resolver) resolveFunction(function ast.FunctionStmt, fnType functionType) { 127 | // change the current function type and save it back 128 | enclosingFunction := r.currentFunction 129 | r.currentFunction = fnType 130 | defer func() { r.currentFunction = enclosingFunction }() 131 | 132 | r.beginScope() 133 | for _, param := range function.Params { 134 | r.declare(param.Token) 135 | r.define(param.Token) 136 | } 137 | r.ResolveStmts(function.Body) 138 | r.endScope() 139 | } 140 | 141 | // beginScope pushes a new scope to the stack 142 | func (r *Resolver) beginScope() { 143 | r.scopes.push(make(scope)) 144 | } 145 | 146 | // endScope pops the current scope. Before removing the scope, 147 | // it reports an error if any local variable in the scope was unused. 148 | func (r *Resolver) endScope() { 149 | for name, v := range r.scopes.peek() { 150 | if !v.used { 151 | r.error(v.token, fmt.Sprintf("Variable '%s' declared but not used.", name)) 152 | } 153 | } 154 | 155 | r.scopes.pop() 156 | } 157 | 158 | func (r *Resolver) VisitAssignExpr(expr ast.AssignExpr) interface{} { 159 | r.resolveExpr(expr.Value) 160 | r.resolveLocal(expr, expr.Name) 161 | return nil 162 | } 163 | 164 | func (r *Resolver) VisitBinaryExpr(expr ast.BinaryExpr) interface{} { 165 | r.resolveExpr(expr.Left) 166 | r.resolveExpr(expr.Right) 167 | return nil 168 | } 169 | 170 | func (r *Resolver) VisitCallExpr(expr ast.CallExpr) interface{} { 171 | r.resolveExpr(expr.Callee) 172 | for _, argument := range expr.Arguments { 173 | r.resolveExpr(argument) 174 | } 175 | return nil 176 | } 177 | 178 | // VisitFunctionExpr resolves a function expression. 179 | func (r *Resolver) VisitFunctionExpr(expr ast.FunctionExpr) interface{} { 180 | // change the current function type 181 | // and save it back after the resolution 182 | enclosingFunction := r.currentFunction 183 | r.currentFunction = functionTypeFunction 184 | defer func() { r.currentFunction = enclosingFunction }() 185 | 186 | // function expression scope 187 | r.beginScope() 188 | if expr.Name != nil { 189 | r.declare(*expr.Name) 190 | r.define(*expr.Name) 191 | } 192 | 193 | // function call scope 194 | r.beginScope() 195 | for _, param := range expr.Params { 196 | r.declare(param.Token) 197 | r.define(param.Token) 198 | } 199 | r.ResolveStmts(expr.Body) 200 | r.endScope() 201 | 202 | r.endScope() 203 | return nil 204 | } 205 | 206 | func (r *Resolver) VisitGetExpr(expr ast.GetExpr) interface{} { 207 | r.resolveExpr(expr.Object) 208 | return nil 209 | } 210 | 211 | func (r *Resolver) VisitGroupingExpr(expr ast.GroupingExpr) interface{} { 212 | r.resolveExpr(expr.Expression) 213 | return nil 214 | } 215 | 216 | func (r *Resolver) VisitLiteralExpr(_ ast.LiteralExpr) interface{} { 217 | return nil 218 | } 219 | 220 | func (r *Resolver) VisitLogicalExpr(expr ast.LogicalExpr) interface{} { 221 | r.resolveExpr(expr.Left) 222 | r.resolveExpr(expr.Right) 223 | return nil 224 | } 225 | 226 | func (r *Resolver) VisitSetExpr(expr ast.SetExpr) interface{} { 227 | r.resolveExpr(expr.Value) 228 | r.resolveExpr(expr.Object) 229 | return nil 230 | } 231 | 232 | func (r *Resolver) VisitSuperExpr(expr ast.SuperExpr) interface{} { 233 | if r.currentClass == classTypeNone { 234 | r.error(expr.Keyword, "Can't use 'super' outside of a class.") 235 | } else if r.currentClass != classTypeSubClass { 236 | r.error(expr.Keyword, "Can't use 'super' in a class with no superclass.") 237 | } 238 | 239 | r.resolveLocal(expr, expr.Keyword) 240 | return nil 241 | } 242 | 243 | func (r *Resolver) VisitThisExpr(expr ast.ThisExpr) interface{} { 244 | if r.currentClass == classTypeNone { 245 | r.error(expr.Keyword, "Can't use 'this' outside of a class.") 246 | } 247 | 248 | r.resolveLocal(expr, expr.Keyword) 249 | return nil 250 | } 251 | 252 | func (r *Resolver) VisitTernaryExpr(expr ast.TernaryExpr) interface{} { 253 | r.resolveExpr(expr.Cond) 254 | r.resolveExpr(expr.Consequent) 255 | r.resolveExpr(expr.Alternate) 256 | return nil 257 | } 258 | 259 | func (r *Resolver) VisitUnaryExpr(expr ast.UnaryExpr) interface{} { 260 | r.resolveExpr(expr.Right) 261 | return nil 262 | } 263 | 264 | func (r *Resolver) VisitVariableExpr(expr ast.VariableExpr) interface{} { 265 | if len(r.scopes) > 0 { 266 | if declared, defined := r.scopes.peek().has(expr.Name.Lexeme); declared && !defined { // if the variable name is declared but not defined, report error 267 | r.error(expr.Name, "Can't read local variable in its own initializer.") 268 | } 269 | } 270 | 271 | r.resolveLocal(expr, expr.Name) 272 | return nil 273 | } 274 | 275 | func (r *Resolver) VisitBlockStmt(stmt ast.BlockStmt) interface{} { 276 | r.beginScope() 277 | r.ResolveStmts(stmt.Statements) 278 | r.endScope() 279 | return nil 280 | } 281 | 282 | func (r *Resolver) VisitClassStmt(stmt ast.ClassStmt) interface{} { 283 | enclosingClass := r.currentClass 284 | defer func() { r.currentClass = enclosingClass }() 285 | 286 | r.currentClass = classTypeClass 287 | 288 | r.declare(stmt.Name) 289 | r.define(stmt.Name) 290 | 291 | if stmt.Superclass != nil && stmt.Name.Lexeme == stmt.Superclass.Name.Lexeme { 292 | r.error(stmt.Superclass.Name, "A class can't inherit from itself.") 293 | } 294 | 295 | if stmt.Superclass != nil { 296 | r.currentClass = classTypeSubClass 297 | r.resolveExpr(stmt.Superclass) 298 | } 299 | 300 | if stmt.Superclass != nil { 301 | r.beginScope() 302 | defer func() { r.endScope() }() 303 | 304 | r.scopes.peek().set("super") 305 | } 306 | 307 | r.beginScope() 308 | 309 | r.scopes.peek().set("this") 310 | 311 | if stmt.Init != nil { 312 | r.resolveFunction(*stmt.Init, functionTypeInitializer) 313 | } 314 | 315 | for _, method := range stmt.Methods { 316 | r.resolveFunction(method, functionTypeMethod) 317 | } 318 | 319 | r.endScope() 320 | 321 | return nil 322 | } 323 | 324 | func (r *Resolver) VisitExpressionStmt(stmt ast.ExpressionStmt) interface{} { 325 | r.resolveExpr(stmt.Expr) 326 | return nil 327 | } 328 | 329 | func (r *Resolver) VisitFunctionStmt(stmt ast.FunctionStmt) interface{} { 330 | r.declare(stmt.Name) 331 | r.define(stmt.Name) 332 | r.resolveFunction(stmt, functionTypeFunction) 333 | return nil 334 | } 335 | 336 | func (r *Resolver) VisitIfStmt(stmt ast.IfStmt) interface{} { 337 | r.resolveExpr(stmt.Condition) 338 | r.resolveStmt(stmt.ThenBranch) 339 | if stmt.ElseBranch != nil { 340 | r.resolveStmt(stmt.ElseBranch) 341 | } 342 | return nil 343 | } 344 | 345 | func (r *Resolver) VisitPrintStmt(stmt ast.PrintStmt) interface{} { 346 | r.resolveExpr(stmt.Expr) 347 | return nil 348 | } 349 | 350 | func (r *Resolver) VisitTypeDeclStmt(stmt ast.TypeDeclStmt) interface{} { 351 | return nil 352 | } 353 | 354 | func (r *Resolver) VisitReturnStmt(stmt ast.ReturnStmt) interface{} { 355 | if r.currentFunction == functionTypeNone { 356 | r.error(stmt.Keyword, "Can't return from top-level code.") 357 | } 358 | 359 | if stmt.Value != nil { 360 | if r.currentFunction == functionTypeInitializer { 361 | r.error(stmt.Keyword, "Can't return a value from an initializer;") 362 | } 363 | r.resolveExpr(stmt.Value) 364 | } 365 | 366 | return nil 367 | } 368 | 369 | func (r *Resolver) VisitWhileStmt(stmt ast.WhileStmt) interface{} { 370 | r.resolveExpr(stmt.Condition) 371 | r.resolveStmt(stmt.Body) 372 | return nil 373 | } 374 | 375 | func (r *Resolver) VisitContinueStmt(stmt ast.ContinueStmt) interface{} { 376 | return nil 377 | } 378 | 379 | func (r *Resolver) VisitBreakStmt(stmt ast.BreakStmt) interface{} { 380 | return nil 381 | } 382 | 383 | func (r *Resolver) VisitVarStmt(stmt ast.VarStmt) interface{} { 384 | r.declare(stmt.Name) 385 | if stmt.Initializer != nil { 386 | r.resolveExpr(stmt.Initializer) 387 | } 388 | r.define(stmt.Name) 389 | return nil 390 | } 391 | 392 | // declare a variable name within the current scope. 393 | // If a variable with the same name is already declared 394 | // in the current scope, it reports an error. 395 | func (r *Resolver) declare(name ast.Token) { 396 | // if at the global scope, return 397 | if len(r.scopes) == 0 { 398 | return 399 | } 400 | 401 | sc := r.scopes.peek() 402 | if _, defined := sc.has(name.Lexeme); defined { 403 | r.error(name, "Already a variable with this name in this scope") 404 | } 405 | 406 | sc.declare(name.Lexeme, name) 407 | } 408 | 409 | // define a variable name within the current scope 410 | func (r *Resolver) define(name ast.Token) { 411 | // at global scope, no need to do anything 412 | if len(r.scopes) == 0 { 413 | return 414 | } 415 | 416 | r.scopes.peek().define(name.Lexeme) 417 | } 418 | 419 | // resolveLocal resolves a local variable or assignment expression. It 420 | // looks through the scope stack and reports the "depth" of the variable 421 | // to the interpreter. The depth is the number of scopes between the scope 422 | // where the variable is accessed and the scope where the variable was defined. 423 | func (r *Resolver) resolveLocal(expr ast.Expr, name ast.Token) { 424 | for i := len(r.scopes) - 1; i >= 0; i-- { 425 | s := r.scopes[i] 426 | if _, defined := s.has(name.Lexeme); defined { 427 | depth := len(r.scopes) - 1 - i 428 | r.interpreter.Resolve(expr, depth) 429 | s.use(name.Lexeme) 430 | return 431 | } 432 | } 433 | } 434 | 435 | func (r *Resolver) error(token ast.Token, message string) { 436 | var where string 437 | if token.TokenType == ast.TokenEof { 438 | where = " at end" 439 | } else { 440 | where = " at '" + token.Lexeme + "'" 441 | } 442 | 443 | _, _ = r.stdErr.Write([]byte(fmt.Sprintf("[line %d] Error%s: %s\n", token.Line, where, message))) 444 | r.hadError = true 445 | } 446 | -------------------------------------------------------------------------------- /scan/scanner.go: -------------------------------------------------------------------------------- 1 | package scan 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "strconv" 7 | 8 | "github.com/chidiwilliams/glox/ast" 9 | ) 10 | 11 | // Scanner convert a source text 12 | // into a slice of ast.Token-s 13 | type Scanner struct { 14 | start int 15 | current int 16 | line int 17 | source string 18 | tokens []ast.Token 19 | stdErr io.Writer 20 | } 21 | 22 | // NewScanner returns a new Scanner 23 | func NewScanner(source string, stdErr io.Writer) *Scanner { 24 | return &Scanner{source: source, stdErr: stdErr} 25 | } 26 | 27 | // ScanTokens returns a slice of tokens representing the source text 28 | func (s *Scanner) ScanTokens() []ast.Token { 29 | for !s.isAtEnd() { 30 | // we're at the beginning of the next lexeme 31 | s.start = s.current 32 | s.scanToken() 33 | } 34 | 35 | s.tokens = append(s.tokens, ast.Token{TokenType: ast.TokenEof, Line: s.line}) 36 | return s.tokens 37 | } 38 | 39 | func (s *Scanner) scanToken() { 40 | char := s.advance() 41 | switch char { 42 | case '(': 43 | s.addToken(ast.TokenLeftParen) 44 | case ')': 45 | s.addToken(ast.TokenRightParen) 46 | case '{': 47 | s.addToken(ast.TokenLeftBrace) 48 | case '}': 49 | s.addToken(ast.TokenRightBrace) 50 | case '[': 51 | s.addToken(ast.TokenLeftBracket) 52 | case ']': 53 | s.addToken(ast.TokenRightBracket) 54 | case ',': 55 | s.addToken(ast.TokenComma) 56 | case '.': 57 | s.addToken(ast.TokenDot) 58 | case '-': 59 | s.addToken(ast.TokenMinus) 60 | case '+': 61 | s.addToken(ast.TokenPlus) 62 | case ';': 63 | s.addToken(ast.TokenSemicolon) 64 | case ':': 65 | s.addToken(ast.TokenColon) 66 | case '*': 67 | s.addToken(ast.TokenStar) 68 | case '?': 69 | s.addToken(ast.TokenQuestionMark) 70 | case '|': 71 | s.addToken(ast.TokenPipe) 72 | 73 | // with look-ahead 74 | case '!': 75 | var nextToken ast.TokenType 76 | if s.match('=') { 77 | nextToken = ast.TokenBangEqual 78 | } else { 79 | nextToken = ast.TokenBang 80 | } 81 | s.addToken(nextToken) 82 | case '=': 83 | var nextToken ast.TokenType 84 | if s.match('=') { 85 | nextToken = ast.TokenEqualEqual 86 | } else { 87 | nextToken = ast.TokenEqual 88 | } 89 | s.addToken(nextToken) 90 | case '<': 91 | var nextToken ast.TokenType 92 | if s.match('=') { 93 | nextToken = ast.TokenLessEqual 94 | } else { 95 | nextToken = ast.TokenLess 96 | } 97 | s.addToken(nextToken) 98 | case '>': 99 | var nextToken ast.TokenType 100 | if s.match('=') { 101 | nextToken = ast.TokenGreaterEqual 102 | } else { 103 | nextToken = ast.TokenGreater 104 | } 105 | s.addToken(nextToken) 106 | case '/': 107 | if s.match('/') { 108 | for s.peek() != '\n' && !s.isAtEnd() { 109 | s.advance() 110 | } 111 | } else { 112 | s.addToken(ast.TokenSlash) 113 | } 114 | 115 | // whitespace 116 | case ' ': 117 | case '\r': 118 | case '\t': 119 | 120 | case '\n': 121 | s.line++ 122 | 123 | // string 124 | case '"': 125 | s.string() 126 | 127 | default: 128 | if s.isDigit(char) { 129 | s.number() 130 | } else if s.isAlpha(char) { 131 | s.identifier() 132 | } else { 133 | s.error("Unexpected character.") 134 | } 135 | } 136 | } 137 | 138 | func (s *Scanner) isAtEnd() bool { 139 | return s.current >= len(s.source) 140 | } 141 | 142 | func (s *Scanner) advance() rune { 143 | curr := rune(s.source[s.current]) 144 | s.current++ 145 | return curr 146 | } 147 | 148 | func (s *Scanner) addToken(tokenType ast.TokenType) { 149 | s.addTokenWithLiteral(tokenType, nil) 150 | } 151 | 152 | func (s *Scanner) addTokenWithLiteral(tokenType ast.TokenType, literal interface{}) { 153 | text := s.source[s.start:s.current] 154 | token := ast.Token{ 155 | TokenType: tokenType, 156 | Lexeme: text, 157 | Literal: literal, 158 | Line: s.line, 159 | Start: s.start} 160 | s.tokens = append(s.tokens, token) 161 | } 162 | 163 | func (s *Scanner) match(expected rune) bool { 164 | if s.isAtEnd() { 165 | return false 166 | } 167 | 168 | if rune(s.source[s.current]) != expected { 169 | return false 170 | } 171 | 172 | s.current++ 173 | return true 174 | } 175 | 176 | func (s *Scanner) string() { 177 | for s.peek() != '"' && !s.isAtEnd() { 178 | if s.peek() == '\n' { 179 | s.line++ 180 | } 181 | s.advance() 182 | } 183 | 184 | if s.isAtEnd() { 185 | s.error("Unterminated string.") 186 | return 187 | } 188 | 189 | s.advance() // the closing " 190 | 191 | value := s.source[s.start+1 : s.current-1] 192 | s.addTokenWithLiteral(ast.TokenString, value) 193 | } 194 | 195 | func (s *Scanner) isDigit(r rune) bool { 196 | return r >= '0' && r <= '9' 197 | } 198 | 199 | func (s *Scanner) number() { 200 | for s.isDigit(s.peek()) { 201 | s.advance() 202 | } 203 | 204 | // look for a fractional part 205 | if s.peek() == '.' && s.isDigit(s.peekNext()) { 206 | s.advance() 207 | for s.isDigit(s.peek()) { 208 | s.advance() 209 | } 210 | } 211 | 212 | val, _ := strconv.ParseFloat(s.source[s.start:s.current], 64) 213 | s.addTokenWithLiteral(ast.TokenNumber, val) 214 | } 215 | 216 | func (s *Scanner) peek() rune { 217 | if s.isAtEnd() { 218 | return '\000' 219 | } 220 | return rune(s.source[s.current]) 221 | } 222 | 223 | func (s *Scanner) peekNext() rune { 224 | if s.current+1 >= len(s.source) { 225 | return '\000' 226 | } 227 | return rune(s.source[s.current+1]) 228 | } 229 | 230 | func (s *Scanner) isAlpha(char rune) bool { 231 | return (char >= 'a' && char <= 'z') || (char >= 'A' && char <= 'Z') || (char == '_') 232 | } 233 | 234 | var keywords = map[string]ast.TokenType{ 235 | "and": ast.TokenAnd, 236 | "class": ast.TokenClass, 237 | "else": ast.TokenElse, 238 | "false": ast.TokenFalse, 239 | "for": ast.TokenFor, 240 | "fun": ast.TokenFun, 241 | "if": ast.TokenIf, 242 | "nil": ast.TokenNil, 243 | "or": ast.TokenOr, 244 | "print": ast.TokenPrint, 245 | "return": ast.TokenReturn, 246 | "super": ast.TokenSuper, 247 | "this": ast.TokenThis, 248 | "true": ast.TokenTrue, 249 | "var": ast.TokenVar, 250 | "while": ast.TokenWhile, 251 | "break": ast.TokenBreak, 252 | "continue": ast.TokenContinue, 253 | "type": ast.TokenTypeType, 254 | } 255 | 256 | func (s *Scanner) identifier() { 257 | for s.isAlphaNumeric(s.peek()) { 258 | s.advance() 259 | } 260 | 261 | text := s.source[s.start:s.current] 262 | tokenType, found := keywords[text] 263 | if !found { 264 | tokenType = ast.TokenIdentifier 265 | } 266 | s.addToken(tokenType) 267 | } 268 | 269 | func (s *Scanner) isAlphaNumeric(char rune) bool { 270 | return s.isAlpha(char) || s.isDigit(char) 271 | } 272 | 273 | func (s *Scanner) error(message string) { 274 | _, _ = s.stdErr.Write([]byte(fmt.Sprintf("[line %d] Error: %s\n", s.line, message))) 275 | } 276 | -------------------------------------------------------------------------------- /test.lox: -------------------------------------------------------------------------------- 1 | print "hello"; 2 | -------------------------------------------------------------------------------- /testdata/class-with-fields.golden: -------------------------------------------------------------------------------- 1 | 35 2 | -------------------------------------------------------------------------------- /testdata/class-with-fields.input: -------------------------------------------------------------------------------- 1 | class Point { 2 | x = 0; 3 | y = 0; 4 | z = 5; 5 | 6 | init(x, y) { 7 | this.x = x; 8 | this.y = y; 9 | } 10 | 11 | calc() { 12 | return this.x + this.y + this.z; 13 | } 14 | } 15 | 16 | var point = Point(10, 20); 17 | print point.calc(); 18 | -------------------------------------------------------------------------------- /typechecker/testdata/binary-expr-on-string-type.golden: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chidiwilliams/glox/7bae84376ac826fae75e0e67d714ffd1759426c7/typechecker/testdata/binary-expr-on-string-type.golden -------------------------------------------------------------------------------- /typechecker/testdata/binary-expr-on-string-type.input: -------------------------------------------------------------------------------- 1 | var x = 10; 2 | { 3 | var x: string = ""; 4 | x + "hello"; 5 | } 6 | x - 20; 7 | -------------------------------------------------------------------------------- /typechecker/testdata/binary-expr.golden: -------------------------------------------------------------------------------- 1 | type error on line 3: expected 'string' type, but got 'number' 2 | -------------------------------------------------------------------------------- /typechecker/testdata/binary-expr.input: -------------------------------------------------------------------------------- 1 | var x = 10; 2 | var y = 20; 3 | var z: string = x * 10 + y; 4 | z; 5 | -------------------------------------------------------------------------------- /typechecker/testdata/boolean-expr.golden: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chidiwilliams/glox/7bae84376ac826fae75e0e67d714ffd1759426c7/typechecker/testdata/boolean-expr.golden -------------------------------------------------------------------------------- /typechecker/testdata/boolean-expr.input: -------------------------------------------------------------------------------- 1 | var x: bool = 30 > 2; 2 | -------------------------------------------------------------------------------- /typechecker/testdata/call-non-callable.golden: -------------------------------------------------------------------------------- 1 | type error on line 1: can only call functions or classes 2 | -------------------------------------------------------------------------------- /typechecker/testdata/call-non-callable.input: -------------------------------------------------------------------------------- 1 | 3(); 2 | -------------------------------------------------------------------------------- /typechecker/testdata/class-assign-field.golden: -------------------------------------------------------------------------------- 1 | type error on line 3: only instances have fields 2 | -------------------------------------------------------------------------------- /typechecker/testdata/class-assign-field.input: -------------------------------------------------------------------------------- 1 | class Class {} 2 | 3 | Class.hello = "world"; 4 | -------------------------------------------------------------------------------- /typechecker/testdata/class-with-init.golden: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chidiwilliams/glox/7bae84376ac826fae75e0e67d714ffd1759426c7/typechecker/testdata/class-with-init.golden -------------------------------------------------------------------------------- /typechecker/testdata/class-with-init.input: -------------------------------------------------------------------------------- 1 | class Rectangle { 2 | length: number = 0; 3 | width: number = 0; 4 | 5 | init(length: number, width: number) { 6 | this.length = length; 7 | this.width = width; 8 | } 9 | 10 | area(): number { 11 | return this.length * this.width; 12 | } 13 | } 14 | 15 | var rectangle: Rectangle = Rectangle(2, 9); 16 | print rectangle.area(); 17 | -------------------------------------------------------------------------------- /typechecker/testdata/class-with-super.golden: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chidiwilliams/glox/7bae84376ac826fae75e0e67d714ffd1759426c7/typechecker/testdata/class-with-super.golden -------------------------------------------------------------------------------- /typechecker/testdata/class-with-super.input: -------------------------------------------------------------------------------- 1 | class Point { 2 | x: number = 0; 3 | y: number = 0; 4 | 5 | init(x: number, y: number) { 6 | this.x = x; 7 | this.y = y; 8 | } 9 | 10 | calc(): number { 11 | return this.x + this.y; 12 | } 13 | } 14 | 15 | class Point3D < Point { 16 | z: number = 0; 17 | 18 | init(x:number, y: number, z: number) { 19 | super.init(x, y); 20 | this.z = z; 21 | } 22 | 23 | calc(): number { 24 | return super.calc() + this.z; 25 | } 26 | } 27 | 28 | var point3D: Point3D = Point3D(1, 2, 3); 29 | print point3D.calc(); 30 | -------------------------------------------------------------------------------- /typechecker/testdata/class-with-two-supers.golden: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chidiwilliams/glox/7bae84376ac826fae75e0e67d714ffd1759426c7/typechecker/testdata/class-with-two-supers.golden -------------------------------------------------------------------------------- /typechecker/testdata/class-with-two-supers.input: -------------------------------------------------------------------------------- 1 | class A { 2 | say(): string { 3 | return "hello"; 4 | } 5 | } 6 | 7 | class B < A { 8 | } 9 | 10 | class C < B { 11 | say(): string { 12 | return super.say(); 13 | } 14 | } 15 | 16 | print C().say(); 17 | -------------------------------------------------------------------------------- /typechecker/testdata/class-without-init.golden: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chidiwilliams/glox/7bae84376ac826fae75e0e67d714ffd1759426c7/typechecker/testdata/class-without-init.golden -------------------------------------------------------------------------------- /typechecker/testdata/class-without-init.input: -------------------------------------------------------------------------------- 1 | class Greeter { 2 | greet(): string { 3 | return "hello"; 4 | } 5 | } 6 | 7 | var greeter: Greeter = Greeter(); 8 | greeter; 9 | -------------------------------------------------------------------------------- /typechecker/testdata/custom-type.golden: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chidiwilliams/glox/7bae84376ac826fae75e0e67d714ffd1759426c7/typechecker/testdata/custom-type.golden -------------------------------------------------------------------------------- /typechecker/testdata/custom-type.input: -------------------------------------------------------------------------------- 1 | type int = number; 2 | type ID = int; 3 | type Index = ID; 4 | 5 | fun square(x: int): int { 6 | return x * x; 7 | } 8 | 9 | var n: int = square(2); 10 | 11 | fun promote(userID: ID): ID { 12 | return userID + 1; 13 | } 14 | 15 | var p: ID = promote(1); 16 | 17 | var x: Index = 1; 18 | -------------------------------------------------------------------------------- /typechecker/testdata/fn-body.golden: -------------------------------------------------------------------------------- 1 | type error on line 2: unexpected type: string, allowed: number 2 | -------------------------------------------------------------------------------- /typechecker/testdata/fn-body.input: -------------------------------------------------------------------------------- 1 | var fn = fun (firstName: string, lastName: string): string { 2 | return firstName - lastName; 3 | }; 4 | fn; 5 | -------------------------------------------------------------------------------- /typechecker/testdata/fn-call-native-with-correct-arg-count.golden: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chidiwilliams/glox/7bae84376ac826fae75e0e67d714ffd1759426c7/typechecker/testdata/fn-call-native-with-correct-arg-count.golden -------------------------------------------------------------------------------- /typechecker/testdata/fn-call-native-with-correct-arg-count.input: -------------------------------------------------------------------------------- 1 | clock(); 2 | -------------------------------------------------------------------------------- /typechecker/testdata/fn-call-native-with-wrong-arg-count.golden: -------------------------------------------------------------------------------- 1 | type error on line 1: function of type Fn<[], number> expects 0 arguments, got 1 2 | -------------------------------------------------------------------------------- /typechecker/testdata/fn-call-native-with-wrong-arg-count.input: -------------------------------------------------------------------------------- 1 | clock(1); 2 | -------------------------------------------------------------------------------- /typechecker/testdata/fn-call-with-correct-arg-types.golden: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chidiwilliams/glox/7bae84376ac826fae75e0e67d714ffd1759426c7/typechecker/testdata/fn-call-with-correct-arg-types.golden -------------------------------------------------------------------------------- /typechecker/testdata/fn-call-with-correct-arg-types.input: -------------------------------------------------------------------------------- 1 | fun addThree(x: number, y: number, z: number): number { 2 | return x + y + z; 3 | } 4 | 5 | addThree(1, 2, 3); 6 | -------------------------------------------------------------------------------- /typechecker/testdata/fn-call-with-wrong-arg-types.golden: -------------------------------------------------------------------------------- 1 | type error on line 5: expected 'number' type, but got 'string' 2 | -------------------------------------------------------------------------------- /typechecker/testdata/fn-call-with-wrong-arg-types.input: -------------------------------------------------------------------------------- 1 | fun addThree(x: number, y: number, z: number): number { 2 | return x + y + z; 3 | } 4 | 5 | addThree(1, "hello", 3); 6 | -------------------------------------------------------------------------------- /typechecker/testdata/fn-callback.golden: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chidiwilliams/glox/7bae84376ac826fae75e0e67d714ffd1759426c7/typechecker/testdata/fn-callback.golden -------------------------------------------------------------------------------- /typechecker/testdata/fn-callback.input: -------------------------------------------------------------------------------- 1 | fun applyBinary(x: number, y: number, callback: Fn<[number, number], number>): number { 2 | return callback(x, y); 3 | } 4 | 5 | applyBinary( 6 | 2, 7 | 3, 8 | fun (x: number, y: number): number { 9 | return x * y; 10 | } 11 | ); 12 | -------------------------------------------------------------------------------- /typechecker/testdata/fn-iife.golden: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chidiwilliams/glox/7bae84376ac826fae75e0e67d714ffd1759426c7/typechecker/testdata/fn-iife.golden -------------------------------------------------------------------------------- /typechecker/testdata/fn-iife.input: -------------------------------------------------------------------------------- 1 | var num = (fun (x: number): number { 2 | return x * x; 3 | })(2); 4 | print num; 5 | -------------------------------------------------------------------------------- /typechecker/testdata/fn-inferred-type.golden: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chidiwilliams/glox/7bae84376ac826fae75e0e67d714ffd1759426c7/typechecker/testdata/fn-inferred-type.golden -------------------------------------------------------------------------------- /typechecker/testdata/fn-inferred-type.input: -------------------------------------------------------------------------------- 1 | var fn: Fn<[string, string], string> = fun (firstName: string, lastName: string): string { 2 | return firstName + lastName; 3 | }; 4 | fn; 5 | -------------------------------------------------------------------------------- /typechecker/testdata/fn-multi-return.golden: -------------------------------------------------------------------------------- 1 | type error on line 1: expected enclosing function to return 'number', but got 'string' 2 | -------------------------------------------------------------------------------- /typechecker/testdata/fn-multi-return.input: -------------------------------------------------------------------------------- 1 | fun getName(): number { 2 | if (true) return 0; 3 | return "name"; 4 | } 5 | -------------------------------------------------------------------------------- /typechecker/testdata/fn-recursive-factorial.golden: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chidiwilliams/glox/7bae84376ac826fae75e0e67d714ffd1759426c7/typechecker/testdata/fn-recursive-factorial.golden -------------------------------------------------------------------------------- /typechecker/testdata/fn-recursive-factorial.input: -------------------------------------------------------------------------------- 1 | fun factorial(n: number): number { 2 | if (n == 1) { 3 | return 1; 4 | } else { 5 | return n * factorial(n - 1); 6 | } 7 | } 8 | 9 | print factorial(5); 10 | -------------------------------------------------------------------------------- /typechecker/testdata/fn-return-fn.golden: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chidiwilliams/glox/7bae84376ac826fae75e0e67d714ffd1759426c7/typechecker/testdata/fn-return-fn.golden -------------------------------------------------------------------------------- /typechecker/testdata/fn-return-fn.input: -------------------------------------------------------------------------------- 1 | fun getMultiplier(n: number): Fn<[number], number> { 2 | return fun (in: number): number { 3 | return n * in; 4 | }; 5 | } 6 | 7 | getMultiplier(2); 8 | -------------------------------------------------------------------------------- /typechecker/testdata/fn-single-return.golden: -------------------------------------------------------------------------------- 1 | type error on line 1: expected enclosing function to return 'number', but got 'string' 2 | -------------------------------------------------------------------------------- /typechecker/testdata/fn-single-return.input: -------------------------------------------------------------------------------- 1 | var fn = fun (firstName: string, lastName: string): number { 2 | return firstName + lastName; 3 | }; 4 | fn; 5 | -------------------------------------------------------------------------------- /typechecker/testdata/ternary-expr.golden: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chidiwilliams/glox/7bae84376ac826fae75e0e67d714ffd1759426c7/typechecker/testdata/ternary-expr.golden -------------------------------------------------------------------------------- /typechecker/testdata/ternary-expr.input: -------------------------------------------------------------------------------- 1 | var x: number = true ? 2 : 9; 2 | var y: string = 2 < 10 ? "hello" : "world"; 3 | -------------------------------------------------------------------------------- /typechecker/testdata/union-type-allowed-operators.golden: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chidiwilliams/glox/7bae84376ac826fae75e0e67d714ffd1759426c7/typechecker/testdata/union-type-allowed-operators.golden -------------------------------------------------------------------------------- /typechecker/testdata/union-type-allowed-operators.input: -------------------------------------------------------------------------------- 1 | var x: string | number = "hello"; 2 | var y: string | number = 32; 3 | 4 | print x + y; 5 | -------------------------------------------------------------------------------- /typechecker/testdata/union-type-illegal-operators.golden: -------------------------------------------------------------------------------- 1 | type error on line 4: unexpected type: string | number, allowed: number 2 | -------------------------------------------------------------------------------- /typechecker/testdata/union-type-illegal-operators.input: -------------------------------------------------------------------------------- 1 | var x: string | number = "hello"; 2 | var y: string | number = 32; 3 | 4 | print x / y; 5 | -------------------------------------------------------------------------------- /typechecker/testdata/union-type.golden: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chidiwilliams/glox/7bae84376ac826fae75e0e67d714ffd1759426c7/typechecker/testdata/union-type.golden -------------------------------------------------------------------------------- /typechecker/testdata/union-type.input: -------------------------------------------------------------------------------- 1 | var a: string | number = 0; 2 | var b: number | string = "hello"; 3 | 4 | b = a; 5 | 6 | var c: number | string | bool = true; 7 | c = b; 8 | 9 | type alias = number | string | bool; 10 | var d: alias = false; 11 | d = c; 12 | -------------------------------------------------------------------------------- /typechecker/testdata/variable-re-assignment.golden: -------------------------------------------------------------------------------- 1 | type error on line 2: expected 'number' type, but got 'string' 2 | -------------------------------------------------------------------------------- /typechecker/testdata/variable-re-assignment.input: -------------------------------------------------------------------------------- 1 | var x: number = 20; 2 | x = "hello"; 3 | -------------------------------------------------------------------------------- /typechecker/testdata/while-stmt.golden: -------------------------------------------------------------------------------- 1 | type error on line 2: expected 'number' type, but got 'string' 2 | -------------------------------------------------------------------------------- /typechecker/testdata/while-stmt.input: -------------------------------------------------------------------------------- 1 | var x = 5; 2 | while (x >= "hello") { 3 | x = x - 1; 4 | } 5 | -------------------------------------------------------------------------------- /typechecker/typechecker.go: -------------------------------------------------------------------------------- 1 | package typechecker 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/chidiwilliams/glox/ast" 7 | "github.com/chidiwilliams/glox/env" 8 | "github.com/chidiwilliams/glox/interpret" 9 | ) 10 | 11 | func NewTypeChecker(interpreter *interpret.Interpreter) *TypeChecker { 12 | globals := env.New(nil) 13 | globals.Define("clock", newFunctionType("", []loxType{}, typeNumber)) 14 | 15 | types := map[string]loxType{ 16 | "number": typeNumber, 17 | "string": typeString, 18 | "bool": typeBoolean, 19 | "nil": typeNil, 20 | } 21 | 22 | return &TypeChecker{env: globals, globals: globals, interpreter: interpreter, types: types} 23 | } 24 | 25 | type TypeChecker struct { 26 | env *env.Environment 27 | globals *env.Environment 28 | interpreter *interpret.Interpreter 29 | declaredFnReturnType loxType 30 | inferredFnReturnType loxType 31 | types map[string]loxType 32 | } 33 | 34 | func (c *TypeChecker) VisitTypeDeclStmt(stmt ast.TypeDeclStmt) interface{} { 35 | if c.env.Has(stmt.Name.Lexeme) { 36 | c.error(stmt.Name.Line, fmt.Sprintf("type with name %s is already defined.", stmt.Name.Lexeme)) 37 | } 38 | 39 | baseType := c.typeFromParsed(stmt.Base) 40 | if baseType == nil { 41 | c.error(stmt.Name.Line, fmt.Sprintf("type %v is not defined.", stmt.Base)) 42 | } 43 | 44 | alias := newAliasType(stmt.Name.Lexeme, baseType) 45 | c.types[stmt.Name.Lexeme] = alias 46 | return alias 47 | } 48 | 49 | func (c *TypeChecker) VisitBlockStmt(stmt ast.BlockStmt) interface{} { 50 | c.checkBlock(stmt.Statements, env.New(c.env)) 51 | return nil 52 | } 53 | 54 | func (c *TypeChecker) checkBlock(stmts []ast.Stmt, env *env.Environment) { 55 | // Restore the current environment after executing the block 56 | previous := c.env 57 | defer func() { 58 | c.env = previous 59 | }() 60 | 61 | c.env = env 62 | for _, stmt := range stmts { 63 | c.checkStmt(stmt) 64 | } 65 | } 66 | 67 | func (c *TypeChecker) VisitClassStmt(stmt ast.ClassStmt) interface{} { 68 | var superclassType *classType 69 | if stmt.Superclass != nil { 70 | var ok bool 71 | superclass, ok := c.types[stmt.Superclass.Name.Lexeme] 72 | if !ok { 73 | c.error(stmt.Superclass.StartLine(), "superclass is undefined") 74 | } 75 | 76 | superclassType, ok = superclass.(*classType) 77 | if !ok { 78 | c.error(stmt.Superclass.StartLine(), "superclass must be a class") 79 | } 80 | } 81 | 82 | classType := newClassType(stmt.Name.Lexeme, superclassType) 83 | c.types[stmt.Name.Lexeme] = classType 84 | classEnv := c.env 85 | 86 | if stmt.Superclass != nil { 87 | previous := c.env 88 | defer func() { c.env = previous }() 89 | 90 | c.env = env.New(previous) 91 | c.env.Define("super", superclassType) 92 | } 93 | 94 | // TODO: is there a way to make this a function, beginScope and endScope 95 | previous := c.env 96 | defer func() { c.env = previous }() 97 | 98 | c.env = env.New(previous) 99 | 100 | c.env.Define("this", classType) 101 | 102 | for _, field := range stmt.Fields { 103 | fieldType := c.check(field.Value) 104 | 105 | if field.Type != nil { 106 | expectedType := c.typeFromParsed(field.Type) 107 | c.expect(fieldType, expectedType, field.Value, field.Value) 108 | } 109 | 110 | classType.properties.Define(field.Name.Lexeme, fieldType) 111 | } 112 | 113 | // Get signature of the init method. We do this before checking the rest of the 114 | // methods, just in case the methods also reference the class in their body 115 | var initMethodParams []loxType 116 | if stmt.Init != nil { 117 | initMethodParams = make([]loxType, len(stmt.Init.Params)) 118 | for i, param := range stmt.Init.Params { 119 | initMethodParams[i] = c.typeFromParsed(param.Type) 120 | } 121 | } 122 | 123 | classEnv.Define(stmt.Name.Lexeme, newFunctionType("", initMethodParams, classType)) 124 | 125 | for _, method := range stmt.Methods { 126 | c.checkMethod(classType, method) 127 | } 128 | 129 | return nil 130 | } 131 | 132 | func (c *TypeChecker) checkMethod(class *classType, stmt ast.FunctionStmt) functionType { 133 | // Check the method within the current environment 134 | // Save the name and return type in the class's properties 135 | 136 | params := stmt.Params 137 | parsedReturnType := stmt.ReturnType 138 | name := &stmt.Name 139 | enclosingEnv := c.env 140 | body := stmt.Body 141 | 142 | // Predefine function type from signature for recursive calls 143 | paramTypes := make([]loxType, len(params)) 144 | for i, param := range params { 145 | paramTypes[i] = c.typeFromParsed(param.Type) 146 | } 147 | 148 | var returnType loxType 149 | if name != nil && (*name).Lexeme == "init" { 150 | returnType = class 151 | } else { 152 | returnType = c.typeFromParsed(parsedReturnType) 153 | } 154 | 155 | if name != nil { 156 | class.properties.Define(name.Lexeme, newFunctionType("", paramTypes, returnType)) 157 | } 158 | 159 | fnEnv := env.New(enclosingEnv) 160 | for i, param := range params { 161 | fnEnv.Define(param.Token.Lexeme, paramTypes[i]) 162 | } 163 | 164 | inferredReturnType := c.checkFunctionBody(body, fnEnv, returnType) 165 | 166 | if parsedReturnType != nil && !returnType.contains(inferredReturnType) { 167 | panic(typeError{message: fmt.Sprintf("expected function %s to return %s, but got %s", body, parsedReturnType, inferredReturnType)}) 168 | } 169 | 170 | return functionType{paramTypes: paramTypes, returnType: inferredReturnType} 171 | } 172 | 173 | func (c *TypeChecker) VisitExpressionStmt(stmt ast.ExpressionStmt) interface{} { 174 | c.check(stmt.Expr) 175 | return nil 176 | } 177 | 178 | func (c *TypeChecker) VisitFunctionStmt(stmt ast.FunctionStmt) interface{} { 179 | return c.checkFunction(&stmt.Name, stmt.Params, stmt.Body, stmt.ReturnType, c.env) 180 | } 181 | 182 | func (c *TypeChecker) VisitIfStmt(stmt ast.IfStmt) interface{} { 183 | conditionType := c.check(stmt.Condition) 184 | c.expect(conditionType, typeBoolean, stmt.Condition, stmt.Condition) 185 | 186 | c.checkStmt(stmt.ThenBranch) 187 | if stmt.ElseBranch != nil { 188 | c.checkStmt(stmt.ElseBranch) 189 | } 190 | return nil 191 | } 192 | 193 | func (c *TypeChecker) VisitPrintStmt(stmt ast.PrintStmt) interface{} { 194 | c.check(stmt.Expr) 195 | return nil 196 | } 197 | 198 | func (c *TypeChecker) VisitReturnStmt(stmt ast.ReturnStmt) interface{} { 199 | returnType := typeNil 200 | if stmt.Value != nil { 201 | returnType = c.check(stmt.Value) 202 | } 203 | 204 | if c.declaredFnReturnType != nil && !c.declaredFnReturnType.contains(returnType) { 205 | panic(typeError{message: fmt.Sprintf("expected enclosing function to return '%s', but got '%s'", c.declaredFnReturnType, returnType)}) 206 | } 207 | 208 | // For now there's no union type, so without the declared annotation, the function's return type 209 | // is just the type of the value returned by the last traversed return statement. Ideally, the 210 | // inferred function type should be some kind of union of all its return values. Or at least, 211 | // reduced to "any" if there's no union. 212 | c.inferredFnReturnType = returnType 213 | 214 | return nil 215 | } 216 | 217 | func (c *TypeChecker) VisitWhileStmt(stmt ast.WhileStmt) interface{} { 218 | conditionType := c.check(stmt.Condition) 219 | c.expect(conditionType, typeBoolean, stmt.Condition, stmt.Condition) 220 | c.checkStmt(stmt.Body) 221 | return nil 222 | } 223 | 224 | func (c *TypeChecker) VisitContinueStmt(stmt ast.ContinueStmt) interface{} { 225 | return nil 226 | } 227 | 228 | func (c *TypeChecker) VisitBreakStmt(stmt ast.BreakStmt) interface{} { 229 | return nil 230 | } 231 | 232 | func (c *TypeChecker) VisitVarStmt(stmt ast.VarStmt) interface{} { 233 | if stmt.Initializer != nil { 234 | // Infer actual type 235 | valueType := c.check(stmt.Initializer) 236 | 237 | if stmt.TypeDecl == nil { 238 | c.env.Define(stmt.Name.Lexeme, valueType) 239 | } else { // With type check 240 | expectedType := c.typeFromParsed(stmt.TypeDecl) 241 | c.expect(valueType, expectedType, stmt.Initializer, stmt.Initializer) 242 | c.env.Define(stmt.Name.Lexeme, expectedType) 243 | } 244 | } 245 | return nil 246 | } 247 | 248 | func (c *TypeChecker) CheckStmts(stmts []ast.Stmt) (err error) { 249 | defer func() { 250 | if recovered := recover(); recovered != nil { 251 | if typeErr, ok := recovered.(typeError); ok { 252 | err = typeErr 253 | } else { 254 | panic(recovered) 255 | } 256 | } 257 | }() 258 | 259 | for _, stmt := range stmts { 260 | c.checkStmt(stmt) 261 | } 262 | 263 | return nil 264 | } 265 | 266 | func (c *TypeChecker) check(expr ast.Expr) loxType { 267 | return expr.Accept(c).(loxType) 268 | } 269 | 270 | func (c *TypeChecker) checkStmt(stmt ast.Stmt) { 271 | stmt.Accept(c) 272 | } 273 | 274 | func (c *TypeChecker) VisitAssignExpr(expr ast.AssignExpr) interface{} { 275 | valueType := c.check(expr.Value) 276 | varType, err := c.lookupType(expr.Name, expr) 277 | if err != nil { 278 | panic(typeError{message: err.Error()}) 279 | } 280 | 281 | c.expect(valueType, varType, expr.Value, expr) 282 | return valueType 283 | } 284 | 285 | func (c *TypeChecker) VisitBinaryExpr(expr ast.BinaryExpr) interface{} { 286 | if c.isBooleanBinary(expr.Operator) { 287 | return c.checkBooleanBinaryExpr(expr) 288 | } 289 | 290 | leftType := c.check(expr.Left) 291 | rightType := c.check(expr.Right) 292 | 293 | allowedTypes := c.getOperandTypesForOperator(expr.Operator) 294 | 295 | c.expectOperatorType(leftType, allowedTypes, expr) 296 | c.expectOperatorType(rightType, allowedTypes, expr) 297 | 298 | return c.expect(leftType, rightType, expr.Right, expr) 299 | } 300 | 301 | func (c *TypeChecker) VisitCallExpr(expr ast.CallExpr) interface{} { 302 | calleeType, ok := c.check(expr.Callee).(functionType) 303 | if !ok { 304 | c.error(expr.Callee.StartLine(), "can only call functions or classes") 305 | } 306 | return c.checkFunctionCall(calleeType, expr) 307 | } 308 | 309 | func (c *TypeChecker) checkFunctionCall(calleeType functionType, expr ast.CallExpr) loxType { 310 | if len(calleeType.paramTypes) != len(expr.Arguments) { 311 | panic(typeError{message: fmt.Sprintf("function of type %s expects %d arguments, got %d", calleeType, len(calleeType.paramTypes), len(expr.Arguments))}) 312 | } 313 | 314 | for i, arg := range expr.Arguments { 315 | argType := c.check(arg) 316 | c.expect(argType, calleeType.paramTypes[i], arg, expr) 317 | } 318 | 319 | return calleeType.returnType 320 | } 321 | 322 | func (c *TypeChecker) VisitFunctionExpr(expr ast.FunctionExpr) interface{} { 323 | return c.checkFunction(expr.Name, expr.Params, expr.Body, expr.ReturnType, env.New(c.env)) 324 | } 325 | 326 | func (c *TypeChecker) checkFunction(name *ast.Token, params []ast.Param, body []ast.Stmt, parsedReturnType ast.Type, enclosingEnv *env.Environment) functionType { 327 | // Predefine function type from signature for recursive calls 328 | paramTypes := make([]loxType, len(params)) 329 | for i, param := range params { 330 | paramTypes[i] = c.typeFromParsed(param.Type) 331 | } 332 | 333 | returnType := c.typeFromParsed(parsedReturnType) 334 | if name != nil { 335 | c.env.Define(name.Lexeme, newFunctionType("", paramTypes, returnType)) 336 | } 337 | 338 | fnEnv := env.New(enclosingEnv) 339 | for i, param := range params { 340 | fnEnv.Define(param.Token.Lexeme, paramTypes[i]) 341 | } 342 | 343 | inferredReturnType := c.checkFunctionBody(body, fnEnv, returnType) 344 | 345 | if parsedReturnType != nil && !returnType.contains(inferredReturnType) { 346 | panic(typeError{message: fmt.Sprintf("expected function %s to return %s, but got %s", body, parsedReturnType, inferredReturnType)}) 347 | } 348 | 349 | return functionType{paramTypes: paramTypes, returnType: inferredReturnType} 350 | } 351 | 352 | func (c *TypeChecker) checkFunctionBody(fnBody []ast.Stmt, fnEnv *env.Environment, declaredReturnType loxType) loxType { 353 | previousEnclosingFnReturnType := c.declaredFnReturnType 354 | defer func() { c.declaredFnReturnType = previousEnclosingFnReturnType }() 355 | 356 | c.declaredFnReturnType = declaredReturnType 357 | 358 | c.checkBlock(fnBody, fnEnv) 359 | 360 | returnType := c.inferredFnReturnType 361 | c.inferredFnReturnType = nil 362 | return returnType 363 | } 364 | 365 | func (c *TypeChecker) VisitGetExpr(expr ast.GetExpr) interface{} { 366 | object := c.check(expr.Object) 367 | 368 | objectClassType, ok := object.(*classType) 369 | if !ok { 370 | c.error(expr.Object.StartLine(), "object must be an instance of a class") 371 | } 372 | 373 | field, err := objectClassType.getField(expr.Name.Lexeme) 374 | if err != nil { 375 | c.error(expr.Name.Line, err.Error()) 376 | } 377 | 378 | return field 379 | } 380 | 381 | func (c *TypeChecker) VisitGroupingExpr(expr ast.GroupingExpr) interface{} { 382 | return c.check(expr.Expression) 383 | } 384 | 385 | func (c *TypeChecker) VisitLiteralExpr(expr ast.LiteralExpr) interface{} { 386 | switch expr.Value.(type) { 387 | case string: 388 | return typeString 389 | case float64: 390 | return typeNumber 391 | case bool: 392 | return typeBoolean 393 | } 394 | panic(typeError{message: "unable to determine expression type"}) 395 | } 396 | 397 | func (c *TypeChecker) VisitLogicalExpr(expr ast.LogicalExpr) interface{} { 398 | // TODO implement me 399 | panic("implement me") 400 | } 401 | 402 | func (c *TypeChecker) VisitSetExpr(expr ast.SetExpr) interface{} { 403 | object := c.check(expr.Object) 404 | 405 | objectAsClassType, ok := object.(*classType) 406 | if !ok { 407 | c.error(expr.StartLine(), "only instances have fields") 408 | } 409 | 410 | property, err := objectAsClassType.properties.Get(expr.Name.Lexeme) 411 | if err != nil { 412 | c.error(expr.Name.Line, "property does not exist on class") 413 | } 414 | 415 | valueType := c.check(expr.Value) 416 | c.expect(valueType, property.(loxType), expr.Value, expr) 417 | return valueType 418 | } 419 | 420 | func (c *TypeChecker) VisitSuperExpr(expr ast.SuperExpr) interface{} { 421 | distance, _ := c.interpreter.GetLocalDistance(expr) 422 | superclass := c.env.GetAt(distance, "super").(*classType) 423 | 424 | method, err := superclass.properties.Get(expr.Method.Lexeme) 425 | if err != nil { 426 | c.error(expr.EndLine(), err.Error()) 427 | } 428 | 429 | return method 430 | } 431 | 432 | func (c *TypeChecker) VisitThisExpr(expr ast.ThisExpr) interface{} { 433 | distance, _ := c.interpreter.GetLocalDistance(expr) 434 | return c.env.GetAt(distance, "this") 435 | } 436 | 437 | func (c *TypeChecker) VisitTernaryExpr(expr ast.TernaryExpr) interface{} { 438 | conditionType := c.check(expr.Cond) 439 | c.expect(conditionType, typeBoolean, expr.Cond, expr) 440 | 441 | consequentType := c.check(expr.Consequent) 442 | alternateType := c.check(expr.Alternate) 443 | 444 | c.expect(alternateType, consequentType, expr, expr) 445 | return alternateType 446 | } 447 | 448 | func (c *TypeChecker) VisitUnaryExpr(expr ast.UnaryExpr) interface{} { 449 | // TODO implement me 450 | panic("implement me") 451 | } 452 | 453 | func (c *TypeChecker) VisitVariableExpr(expr ast.VariableExpr) interface{} { 454 | exprType, err := c.lookupType(expr.Name, expr) 455 | if err != nil { 456 | panic(typeError{message: err.Error()}) 457 | } 458 | return exprType 459 | } 460 | 461 | func (c *TypeChecker) lookupType(name ast.Token, expr ast.Expr) (loxType, error) { 462 | // If the variable is a local variable, find it in the resolved enclosing scope 463 | if distance, ok := c.interpreter.GetLocalDistance(expr); ok { 464 | return c.env.GetAt(distance, name.Lexeme).(loxType), nil 465 | } 466 | nameType, err := c.globals.Get(name.Lexeme) 467 | if err != nil { 468 | return nil, err 469 | } 470 | return nameType.(loxType), nil 471 | } 472 | 473 | func (c *TypeChecker) typeFromParsed(parsedType ast.Type) loxType { 474 | switch parsed := parsedType.(type) { 475 | case ast.SingleType: 476 | switch parsed.Name { 477 | case "Fn": 478 | // TODO: So many possible bugs here. Should assert on length of generic arguments. 479 | // Should there actually be an ast.ArrayType? 480 | parsedParamTypes := parsed.GenericArgs[0].(ast.ArrayType).Types 481 | 482 | paramTypes := make([]loxType, len(parsedParamTypes)) 483 | for i, parsedParamType := range parsedParamTypes { 484 | paramTypes[i] = c.typeFromParsed(parsedParamType) 485 | } 486 | 487 | parsedReturnType := parsed.GenericArgs[1].(ast.SingleType) 488 | returnType := c.typeFromParsed(parsedReturnType) 489 | 490 | return newFunctionType("", paramTypes, returnType) 491 | default: 492 | t, ok := c.types[parsed.Name] 493 | if ok { 494 | return t 495 | } 496 | } 497 | case ast.UnionType: 498 | left := c.typeFromParsed(parsed.Left) 499 | right := c.typeFromParsed(parsed.Right) 500 | return unionType{left: left, right: right} 501 | } 502 | return nil 503 | } 504 | 505 | func (c *TypeChecker) expect(actual loxType, expected loxType, value ast.Expr, expr ast.Expr) loxType { 506 | if !expected.contains(actual) { 507 | c.error(value.StartLine(), fmt.Sprintf("expected '%s' type, but got '%s'", expected.String(), actual.String())) 508 | } 509 | return actual 510 | } 511 | 512 | func (c *TypeChecker) getOperandTypesForOperator(operator ast.Token) loxType { 513 | switch operator.Lexeme { 514 | case "+": 515 | return unionType{typeNumber, typeString} 516 | default: 517 | return typeNumber 518 | } 519 | } 520 | 521 | func (c *TypeChecker) expectOperatorType(inputType loxType, allowedTypes loxType, expr ast.Expr) { 522 | if allowedTypes.contains(inputType) { 523 | return 524 | } 525 | 526 | c.error(expr.StartLine(), fmt.Sprintf("unexpected type: %v, allowed: %v", inputType, allowedTypes)) 527 | } 528 | 529 | func (c *TypeChecker) error(line int, message string) { 530 | panic(typeError{line: line, message: message}) 531 | } 532 | 533 | func (c *TypeChecker) isBooleanBinary(operator ast.Token) bool { 534 | switch operator.TokenType { 535 | case ast.TokenBangEqual, ast.TokenEqualEqual, 536 | ast.TokenGreater, ast.TokenGreaterEqual, 537 | ast.TokenLess, ast.TokenLessEqual: 538 | return true 539 | default: 540 | return false 541 | } 542 | } 543 | 544 | func (c *TypeChecker) checkBooleanBinaryExpr(expr ast.BinaryExpr) interface{} { 545 | leftType := c.check(expr.Left) 546 | rightType := c.check(expr.Right) 547 | 548 | c.expect(rightType, leftType, expr.Right, expr) 549 | return typeBoolean 550 | } 551 | -------------------------------------------------------------------------------- /typechecker/typechecker_test.go: -------------------------------------------------------------------------------- 1 | package typechecker 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | "strconv" 7 | "strings" 8 | "testing" 9 | 10 | "github.com/chidiwilliams/glox/ast" 11 | "github.com/chidiwilliams/glox/interpret" 12 | "github.com/chidiwilliams/glox/parse" 13 | "github.com/chidiwilliams/glox/resolve" 14 | "github.com/chidiwilliams/glox/scan" 15 | ) 16 | 17 | func TestTypeChecker_CheckStmts(t *testing.T) { 18 | paths, err := filepath.Glob(filepath.Join("testdata", "*.input")) 19 | if err != nil { 20 | t.Fatal(err) 21 | } 22 | 23 | for _, path := range paths { 24 | _, filename := filepath.Split(path) 25 | testName := filename[:len(filename)-len(filepath.Ext(path))] 26 | 27 | t.Run(testName, func(t *testing.T) { 28 | source, err := os.ReadFile(path) 29 | if err != nil { 30 | t.Fatal("error reading test source file:", err) 31 | } 32 | 33 | statements, interpreter := runSource(t, string(source)) 34 | 35 | typeChecker := NewTypeChecker(interpreter) 36 | typeErr := typeChecker.CheckStmts(statements) 37 | 38 | goldenFile := filepath.Join("testdata", testName+".golden") 39 | want, err := os.ReadFile(goldenFile) 40 | if err != nil { 41 | t.Fatal("error reading golden file", err) 42 | } 43 | wantErr := strings.Trim(string(want), "\n") 44 | 45 | if typeErr != nil && wantErr == "" || 46 | typeErr != nil && typeErr.Error() != wantErr { 47 | t.Errorf("Check() error = %v, want %v", strconv.Quote(typeErr.Error()), strconv.Quote(wantErr)) 48 | } 49 | if typeErr == nil && wantErr != "" { 50 | t.Errorf("Check() error = nil, want %v", strconv.Quote(wantErr)) 51 | } 52 | }) 53 | } 54 | } 55 | 56 | func runSource(t *testing.T, source string) ([]ast.Stmt, *interpret.Interpreter) { 57 | scanner := scan.NewScanner(source, os.Stderr) 58 | tokens := scanner.ScanTokens() 59 | 60 | parser := parse.NewParser(tokens, os.Stderr) 61 | var statements []ast.Stmt 62 | statements, hadError := parser.Parse() 63 | if hadError { 64 | t.Fatal("parsing error") 65 | } 66 | 67 | interpreter := interpret.NewInterpreter(os.Stdout, os.Stderr) 68 | 69 | resolver := resolve.NewResolver(interpreter, os.Stderr) 70 | hadError = resolver.ResolveStmts(statements) 71 | 72 | if hadError { 73 | t.Fatal("resolving error") 74 | } 75 | 76 | return statements, interpreter 77 | } 78 | -------------------------------------------------------------------------------- /typechecker/types.go: -------------------------------------------------------------------------------- 1 | package typechecker 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/chidiwilliams/glox/env" 7 | ) 8 | 9 | type typeError struct { 10 | message string 11 | line int 12 | } 13 | 14 | func (e typeError) Error() string { 15 | return fmt.Sprintf("type error on line %d: %s", e.line+1, e.message) 16 | } 17 | 18 | type loxType interface { 19 | String() string 20 | contains(other loxType) bool 21 | } 22 | 23 | type primitiveType struct { 24 | name string 25 | } 26 | 27 | func newPrimitiveType(name string) loxType { 28 | return primitiveType{name: name} 29 | } 30 | 31 | func (t primitiveType) String() string { 32 | return t.name 33 | } 34 | 35 | func (t primitiveType) contains(other loxType) bool { 36 | if other, ok := other.(aliasType); ok { 37 | return t.contains(other.parent) 38 | } 39 | 40 | return t == other 41 | } 42 | 43 | var ( 44 | typeNumber = newPrimitiveType("number") 45 | typeString = newPrimitiveType("string") 46 | typeBoolean = newPrimitiveType("boolean") 47 | typeNil = newPrimitiveType("nil") 48 | ) 49 | 50 | func newFunctionType(name string, paramTypes []loxType, returnType loxType) functionType { 51 | return functionType{name: name, paramTypes: paramTypes, returnType: returnType} 52 | } 53 | 54 | type functionType struct { 55 | name string 56 | paramTypes []loxType 57 | returnType loxType 58 | } 59 | 60 | func (t functionType) contains(other loxType) bool { 61 | if other, ok := other.(aliasType); ok { 62 | return t.contains(other.parent) 63 | } 64 | 65 | fnType, ok := other.(functionType) 66 | if !ok { 67 | return false 68 | } 69 | 70 | if !t.returnType.contains(fnType.returnType) { 71 | return false 72 | } 73 | 74 | if len(fnType.paramTypes) != len(t.paramTypes) { 75 | return false 76 | } 77 | 78 | for i, paramType := range fnType.paramTypes { 79 | if !t.paramTypes[i].contains(paramType) { 80 | return false 81 | } 82 | } 83 | 84 | return true 85 | } 86 | 87 | func (t functionType) String() string { 88 | if t.name != "" { 89 | return t.name 90 | } 91 | 92 | name := "Fn<" 93 | 94 | name += "[" 95 | for i, paramType := range t.paramTypes { 96 | if i > 0 { 97 | name += "," 98 | } 99 | name += paramType.String() 100 | } 101 | name += "], " 102 | name += t.returnType.String() 103 | name += ">" 104 | return name 105 | } 106 | 107 | func newAliasType(name string, parent loxType) loxType { 108 | return aliasType{name: name, parent: parent} 109 | } 110 | 111 | type aliasType struct { 112 | name string 113 | parent loxType 114 | } 115 | 116 | func (t aliasType) String() string { 117 | return t.name 118 | } 119 | 120 | func (t aliasType) contains(other loxType) bool { 121 | if other, ok := other.(aliasType); ok && t.name == other.name { 122 | return true 123 | } 124 | 125 | return t.parent.contains(other) 126 | } 127 | 128 | type classType struct { 129 | name string 130 | superClass loxType 131 | properties *env.Environment 132 | } 133 | 134 | func (t *classType) String() string { 135 | return t.name 136 | } 137 | 138 | func (t *classType) contains(other loxType) bool { 139 | if t == other { 140 | return true 141 | } 142 | 143 | if alias, ok := other.(aliasType); ok { 144 | return alias.contains(t) 145 | } 146 | 147 | if t.superClass != nil { 148 | return t.superClass.contains(other) 149 | } 150 | 151 | return false 152 | } 153 | 154 | func (t *classType) getField(name string) (loxType, error) { 155 | fieldType, err := t.properties.Get(name) 156 | if err != nil { 157 | return nil, err 158 | } 159 | return fieldType.(loxType), nil 160 | } 161 | 162 | func newClassType(name string, superClass *classType) *classType { 163 | var enclosingEnv *env.Environment 164 | if superClass != nil { 165 | enclosingEnv = superClass.properties 166 | } 167 | 168 | properties := env.New(enclosingEnv) 169 | return &classType{ 170 | name: name, 171 | superClass: superClass, 172 | properties: properties, 173 | } 174 | } 175 | 176 | type unionType struct { 177 | left loxType 178 | right loxType 179 | } 180 | 181 | func (t unionType) String() string { 182 | return fmt.Sprintf("%s | %s", t.left.String(), t.right.String()) 183 | } 184 | 185 | func (t unionType) contains(other loxType) bool { 186 | if other == t { 187 | return true 188 | } 189 | 190 | if other, ok := other.(unionType); ok { 191 | return t.contains(other.left) && t.contains(other.right) 192 | } 193 | 194 | return t.left.contains(other) || t.right.contains(other) 195 | } 196 | --------------------------------------------------------------------------------