From c07b5f8fd0cf8bd1654825dcf7401d670fda4c0a Mon Sep 17 00:00:00 2001 From: Bobby Date: Wed, 24 Jan 2024 21:03:26 +0000 Subject: If-Else Parsing --- ast/ast.go | 43 ++++++++++++++++++++ parser/parser.go | 44 +++++++++++++++++++++ parser/parser_test.go | 106 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 193 insertions(+) diff --git a/ast/ast.go b/ast/ast.go index b2634d4..31b1be1 100644 --- a/ast/ast.go +++ b/ast/ast.go @@ -88,6 +88,24 @@ func (i *Identifier) String() string { return i.Value } +// BlockStatement represents a block statement. +type BlockStatement struct { + Token tokens.Token // the token.LBRACE token + Statements []Statement +} + +func (bs *BlockStatement) statementNode() {} +func (bs *BlockStatement) TokenLiteral() string { return bs.Token.Literal } +func (bs *BlockStatement) String() string { + var out bytes.Buffer + + for _, s := range bs.Statements { + out.WriteString(s.String()) + } + + return out.String() +} + // ReturnStatement represents a return statement. type ReturnStatement struct { Token tokens.Token // the token.RETURN token @@ -204,3 +222,28 @@ func (b *Boolean) TokenLiteral() string { func (b *Boolean) String() string { return b.Token.Literal } + +type IfExpression struct { + Token tokens.Token // the 'if' token + Condition Expression + Consequence *BlockStatement + Alternative *BlockStatement +} + +func (ie *IfExpression) expressionNode() {} +func (ie *IfExpression) TokenLiteral() string { return ie.Token.Literal } +func (ie *IfExpression) String() string { + var out bytes.Buffer + + out.WriteString("if") + out.WriteString(ie.Condition.String()) + out.WriteString(" ") + out.WriteString(ie.Consequence.String()) + + if ie.Alternative != nil { + out.WriteString("else ") + out.WriteString(ie.Alternative.String()) + } + + return out.String() +} diff --git a/parser/parser.go b/parser/parser.go index 310acee..e38e54c 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -67,6 +67,7 @@ func New(l *lexer.Lexer) *Parser { p.registerPrefix(tokens.MINUS, p.parsePrefixExpression) p.registerPrefix(tokens.TRUE, p.parseBoolean) p.registerPrefix(tokens.FALSE, p.parseBoolean) + p.registerPrefix(tokens.IF, p.parseIfExpression) // Initialize the infix parse functions. p.infixParseFns = make(map[tokens.TokenType]infixParseFn) @@ -286,6 +287,49 @@ func (p *Parser) parseInfixExpression(left ast.Expression) ast.Expression { return expression } +func (p *Parser) parseIfExpression() ast.Expression { + expression := &ast.IfExpression{Token: p.curToken} + + if !p.expectPeek(tokens.LPAREN) { return nil } + + p.nextToken() + expression.Condition = p.parseExpression(LOWEST) + + if !p.expectPeek(tokens.RPAREN) { return nil } + + if !p.expectPeek(tokens.LBRACE) { return nil } + + expression.Consequence = p.parseBlockStatement() + + if p.peekTokenIs(tokens.ELSE) { + p.nextToken() + + if !p.expectPeek(tokens.LBRACE) { return nil } + + expression.Alternative = p.parseBlockStatement() + } + + return expression +} + +func (p *Parser) parseBlockStatement() *ast.BlockStatement { + block := &ast.BlockStatement{Token: p.curToken} + block.Statements = []ast.Statement{} + + p.nextToken() + + for !p.curTokenIs(tokens.RBRACE) && !p.curTokenIs(tokens.EOF) { + stmt := p.parseStatement() + if stmt != nil { + block.Statements = append(block.Statements, stmt) + } + + p.nextToken() + } + + return block +} + // curTokenIs returns true if the current token is of the given type. func (p *Parser) curTokenIs(t tokens.TokenType) bool { return p.curToken.Type == t diff --git a/parser/parser_test.go b/parser/parser_test.go index d2a8c8f..f26fa1e 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -546,3 +546,109 @@ func testBooleanLiteral(t *testing.T, exp ast.Expression, value bool) bool { return true } + +// If expression tests. + +func TestIfExpression(t *testing.T) { + input := "if (x < y) { x }" + + var l *lexer.Lexer = lexer.New(input) + var p *Parser = New(l) + var program *ast.Program = p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain %d statements. got=%d", 1, len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", program.Statements[0]) + } + + exp, ok := stmt.Expression.(*ast.IfExpression) + + if !ok { + t.Fatalf("stmt.Expression is not ast.IfExpression. got=%T", stmt.Expression) + } + + if !testInfixExpression(t, exp.Condition, "x", "<", "y") { + return + } + + if len(exp.Consequence.Statements) != 1 { + t.Errorf("consequence is not 1 statements. got=%d", len(exp.Consequence.Statements)) + } + + consequence, ok := exp.Consequence.Statements[0].(*ast.ExpressionStatement) + + if !ok { + t.Fatalf("Statements[0] is not ast.ExpressionStatement. got=%T", exp.Consequence.Statements[0]) + } + + if !testIdentifier(t, consequence.Expression, "x") { + return + } + + if exp.Alternative != nil { + t.Errorf("exp.Alternative.Statements was not nil. got=%+v", exp.Alternative) + } +} + +func TestIfElseExpression(t *testing.T) { + input := "if (x < y) { x } else { y }" + + var l *lexer.Lexer = lexer.New(input) + var p *Parser = New(l) + var program *ast.Program = p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain %d statements. got=%d", 1, len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", program.Statements[0]) + } + + exp, ok := stmt.Expression.(*ast.IfExpression) + + if !ok { + t.Fatalf("stmt.Expression is not ast.IfExpression. got=%T", stmt.Expression) + } + + if !testInfixExpression(t, exp.Condition, "x", "<", "y") { + return + } + + if len(exp.Consequence.Statements) != 1 { + t.Errorf("consequence is not 1 statements. got=%d", len(exp.Consequence.Statements)) + } + + consequence, ok := exp.Consequence.Statements[0].(*ast.ExpressionStatement) + + if !ok { + t.Fatalf("Statements[0] is not ast.ExpressionStatement. got=%T", exp.Consequence.Statements[0]) + } + + if !testIdentifier(t, consequence.Expression, "x") { + return + } + + if len(exp.Alternative.Statements) != 1 { + t.Errorf("consequence is not 1 statements. got=%d", len(exp.Alternative.Statements)) + } + + alternative, ok := exp.Alternative.Statements[0].(*ast.ExpressionStatement) + + if !ok { + t.Fatalf("Statements[0] is not ast.ExpressionStatement. got=%T", exp.Alternative.Statements[0]) + } + + if !testIdentifier(t, alternative.Expression, "y") { + return + } +} -- cgit v1.2.3