Add for loops

main
oabrivard 1 year ago
parent e8b34eed57
commit 41bf68b685

@ -83,8 +83,11 @@ func (p *Parser) varDeclaration() ast.Stmt {
return &ast.VarStmt{Name: name, Initializer: initializer} return &ast.VarStmt{Name: name, Initializer: initializer}
} }
// statement → exprStmt | ifStmt | printStmt | whileStmt | block ; // statement → exprStmt | forStmt | ifStmt | printStmt | whileStmt | block ;
func (p *Parser) statement() ast.Stmt { func (p *Parser) statement() ast.Stmt {
if p.match(token.FOR) {
return p.forStatement()
}
if p.match(token.IF) { if p.match(token.IF) {
return p.ifStatement() return p.ifStatement()
} }
@ -101,6 +104,58 @@ func (p *Parser) statement() ast.Stmt {
return p.expressionStatement() return p.expressionStatement()
} }
// forStmt → "for" "(" ( varDecl | exprStmt | ";" ) expression? ";" expression? ")" statement ;
func (p *Parser) forStatement() ast.Stmt {
err := p.consume(token.LEFT_PAREN, "Expect '(' after 'for'.")
if err != nil {
return p.fromErrorExpr(err)
}
var initializer ast.Stmt
if p.match(token.SEMICOLON) {
initializer = nil
} else if p.match(token.VAR) {
initializer = p.varDeclaration()
} else {
initializer = p.expressionStatement()
}
var condition ast.Expr
if !p.check(token.SEMICOLON) {
condition = p.expression()
}
err = p.consume(token.SEMICOLON, "Expect ';' after loop condition.")
if err != nil {
return p.fromErrorExpr(err)
}
var increment ast.Expr
if !p.check(token.RIGHT_PAREN) {
increment = p.expression()
}
err = p.consume(token.RIGHT_PAREN, "Expect ')' after for clauses.")
if err != nil {
return p.fromErrorExpr(err)
}
body := p.statement()
if increment != nil {
body = &ast.BlockStmt{Statements: []ast.Stmt{body, &ast.ExpressionStmt{Expression: increment}}}
}
if condition == nil {
condition = &ast.LiteralExpr{Value: true}
}
body = &ast.WhileStmt{Condition: condition, Body: body}
if initializer != nil {
body = &ast.BlockStmt{Statements: []ast.Stmt{initializer, body}}
}
return body
}
// ifStmt → "if" "(" expression ")" statement ( "else" statement )? ; // ifStmt → "if" "(" expression ")" statement ( "else" statement )? ;
func (p *Parser) ifStatement() ast.Stmt { func (p *Parser) ifStatement() ast.Stmt {
err := p.consume(token.LEFT_PAREN, "Expect '(' after 'if'.") err := p.consume(token.LEFT_PAREN, "Expect '(' after 'if'.")

@ -430,3 +430,55 @@ func TestParseWhileStatement(t *testing.T) {
}) })
} }
} }
func TestParseForStatement(t *testing.T) {
tests := []struct {
name string
tokens []token.Token
expected string
}{
{
name: "simple for statement",
tokens: []token.Token{
{Type: token.FOR, Lexeme: "for"},
{Type: token.LEFT_PAREN, Lexeme: "("},
{Type: token.VAR, Lexeme: "var"},
{Type: token.IDENTIFIER, Lexeme: "i"},
{Type: token.EQUAL, Lexeme: "="},
{Type: token.NUMBER, Lexeme: "0", Literal: 0.0},
{Type: token.SEMICOLON, Lexeme: ";"},
{Type: token.IDENTIFIER, Lexeme: "i"},
{Type: token.LESS, Lexeme: "<"},
{Type: token.NUMBER, Lexeme: "10", Literal: 10.0},
{Type: token.SEMICOLON, Lexeme: ";"},
{Type: token.IDENTIFIER, Lexeme: "i"},
{Type: token.EQUAL, Lexeme: "="},
{Type: token.IDENTIFIER, Lexeme: "i"},
{Type: token.PLUS, Lexeme: "+"},
{Type: token.NUMBER, Lexeme: "1", Literal: 1.0},
{Type: token.RIGHT_PAREN, Lexeme: ")"},
{Type: token.PRINT, Lexeme: "print"},
{Type: token.IDENTIFIER, Lexeme: "i"},
{Type: token.SEMICOLON, Lexeme: ";"},
{Type: token.EOF},
},
expected: "{\n (var i 0)\n (while (< i 10) {\n {\n (print i)\n (= i (+ i 1))\n}\n})\n}\n",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
parser := New(tt.tokens, errors.NewMockErrorLogger())
stmts := parser.Parse()
if len(stmts) != 1 {
t.Fatalf("expected 1 statement, got %d", len(stmts))
}
ap := ast.NewPrinter()
s := ap.PrintStmts(stmts)
if s != tt.expected {
t.Errorf("expected %v, got %v", tt.expected, s)
}
})
}
}

8
testdata/fibo.lox vendored

@ -0,0 +1,8 @@
var a = 0;
var temp;
for (var b = 1; a < 10000; b = temp + b) {
print a;
temp = a;
a = b;
}
Loading…
Cancel
Save