Skip to content

Commit

Permalink
feat: parse continue and break
Browse files Browse the repository at this point in the history
  • Loading branch information
silverhairs committed Aug 13, 2023
1 parent bcb8425 commit 7ee1375
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 3 deletions.
27 changes: 24 additions & 3 deletions glox/parser/parser.go
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -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()
Expand Down
46 changes: 46 additions & 0 deletions glox/parser/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand All @@ -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
}

0 comments on commit 7ee1375

Please sign in to comment.