diff --git a/glox/parser/parser.go b/glox/parser/parser.go index 6dbb4e9..a081eeb 100644 --- a/glox/parser/parser.go +++ b/glox/parser/parser.go @@ -1,14 +1,21 @@ package parser import ( + "fmt" "glox/ast" "glox/exception" "glox/token" ) +// statement -> whileStmt +// whileStmt -> "while" "(" expression ")" statement +// statement -> branch +// branch -> expression? break | continue + type Parser struct { - tokens []token.Token - position int + tokens []token.Token + position int + loopLevel int } func New(tokens []token.Token) *Parser { @@ -94,11 +101,25 @@ func (p *Parser) letDeclaration() (ast.Statement, error) { } func (p *Parser) statement() (ast.Statement, error) { - if p.match(token.FOR) { + if p.match(token.BREAK) || p.match(token.CONTINUE) { + tok := p.previous() + if p.loopLevel == 0 { + return nil, exception.Runtime(p.previous(), fmt.Sprintf("'%s' cannot be used outside of a loop.", tok.Lexeme)) + } + if _, err := p.consume(token.SEMICOLON, fmt.Sprintf("expect ';' after '%s'.", tok.Lexeme)); err != nil { + return nil, err + } + return ast.NewBranch(tok), nil + + } else if p.match(token.FOR) { + p.loopLevel++ + defer func() { p.loopLevel-- }() return p.forStatement() } else if p.match(token.PRINT) { return p.printStatement() } else if p.match(token.WHILE) { + p.loopLevel++ + defer func() { p.loopLevel-- }() return p.while() } else if p.match(token.L_BRACE) { block, err := p.block() diff --git a/glox/parser/parser_test.go b/glox/parser/parser_test.go index 9238155..190820c 100644 --- a/glox/parser/parser_test.go +++ b/glox/parser/parser_test.go @@ -838,6 +838,36 @@ func TestParseWhile(t *testing.T) { ast.NewPrintStmt(ast.NewLiteralExpression("yes")), ), }, + { + code: `while(0>1 or false) break;`, + want: ast.NewWhileStmt( + ast.NewLogical( + ast.NewBinaryExpression( + ast.NewLiteralExpression(0), + token.Token{Type: token.GREATER, Lexeme: ">", Line: 1}, + ast.NewLiteralExpression(1), + ), + token.Token{Type: token.OR, Lexeme: "or", Line: 1}, + ast.NewLiteralExpression(false), + ), + ast.NewBranch(token.Token{Type: token.BREAK, Lexeme: "break", Line: 1}), + ), + }, + { + code: `while(0>1 or false) continue;`, + want: ast.NewWhileStmt( + ast.NewLogical( + ast.NewBinaryExpression( + ast.NewLiteralExpression(0), + token.Token{Type: token.GREATER, Lexeme: ">", Line: 1}, + ast.NewLiteralExpression(1), + ), + token.Token{Type: token.OR, Lexeme: "or", Line: 1}, + ast.NewLiteralExpression(false), + ), + ast.NewBranch(token.Token{Type: token.CONTINUE, Lexeme: "continue", Line: 1}), + ), + }, } for _, test := range tests { @@ -1104,6 +1134,8 @@ func testStmt(stmt ast.Statement, want ast.Statement, t *testing.T) bool { return testIfStmt(stmt, want, t) case *ast.WhileStmt: return testWhile(stmt, want, t) + case *ast.BranchStmt: + return testBranch(stmt, want, t) default: t.Errorf("statement %T does not have a testing function. consider adding one", want) return false @@ -1119,3 +1151,17 @@ func testWhile(got ast.Statement, want *ast.WhileStmt, t *testing.T) bool { } return testExpression(want.Condition, want.Condition, t) && testStmt(while.Body, want.Body, t) } + +func testBranch(got ast.Statement, want *ast.BranchStmt, t *testing.T) bool { + branch, isOk := got.(*ast.BranchStmt) + if !isOk { + t.Errorf("got='%T' want a *ast.BranchStmt", got) + return false + } + if branch.Token.Type != want.Token.Type { + t.Errorf("branch stmt has wrong token. got='%v' expected='%v'", branch.Token.Type, want.Token.Type) + return false + } + + return true +}