From fee3e38896b2e9412b2626c7c81739fd9776bf0f Mon Sep 17 00:00:00 2001 From: Chuck Smith Date: Thu, 18 Jan 2024 12:08:25 -0500 Subject: [PATCH] boolean and if/else --- ast/ast.go | 59 +++++++++++++++++++ parser/parser.go | 73 +++++++++++++++++++++++ parser/parser_test.go | 131 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 263 insertions(+) diff --git a/ast/ast.go b/ast/ast.go index 015c3ea..7852327 100644 --- a/ast/ast.go +++ b/ast/ast.go @@ -81,6 +81,23 @@ type InfixExpression struct { Right Expression } +type Boolean struct { + Token token.Token + Value bool +} + +type BlockStatement struct { + Token token.Token // the { token + Statements []Statement +} + +type IfExpression struct { + Token token.Token // The 'if' token + Condition Expression + Consequence *BlockStatement + Alternative *BlockStatement +} + func (ls *LetStatement) statementNode() { } func (ls *LetStatement) TokenLiteral() string { @@ -180,3 +197,45 @@ func (ie *InfixExpression) String() string { return out.String() } + +func (b Boolean) TokenLiteral() string { + return b.Token.Literal +} +func (b Boolean) String() string { + return b.Token.Literal +} +func (b Boolean) expressionNode() {} + +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() +} +func (bs BlockStatement) statementNode() {} + +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() +} +func (ie IfExpression) expressionNode() {} diff --git a/parser/parser.go b/parser/parser.go index 2dbe71e..de7e971 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -62,6 +62,10 @@ func New(l *lexer.Lexer) *Parser { p.registerPrefix(token.INT, p.parseIntegerLiteral) p.registerPrefix(token.BANG, p.parsePrefixExpression) p.registerPrefix(token.MINUS, p.parsePrefixExpression) + p.registerPrefix(token.TRUE, p.parseBoolean) + p.registerPrefix(token.FALSE, p.parseBoolean) + p.registerPrefix(token.LPAREN, p.parseGroupedExpression) + p.registerPrefix(token.IF, p.parseIfExpression) p.infixParseFns = make(map[token.TokenType]infixParseFn) p.registerInfix(token.PLUS, p.parseInfixExpression) @@ -282,3 +286,72 @@ func (p *Parser) parseInfixExpression(left ast.Expression) ast.Expression { return expression } + +func (p *Parser) parseBoolean() ast.Expression { + return &ast.Boolean{ + Token: p.curToken, + Value: p.curTokenIs(token.TRUE), + } +} + +func (p *Parser) parseGroupedExpression() ast.Expression { + p.nextToken() + + exp := p.parseExpression(LOWEST) + + if p.expectPeek(token.RPAREN) { + return nil + } + + return exp +} + +func (p *Parser) parseIfExpression() ast.Expression { + expression := &ast.IfExpression{Token: p.curToken} + + if !p.expectPeek(token.LPAREN) { + return nil + } + + p.nextToken() + expression.Condition = p.parseExpression(LOWEST) + + if !p.expectPeek(token.RPAREN) { + return nil + } + + if !p.expectPeek(token.LBRACE) { + return nil + } + + expression.Consequence = p.parseBlockExpression() + + if p.peekTokenIs(token.ELSE) { + p.nextToken() + + if !p.expectPeek(token.LBRACE) { + return nil + } + + expression.Alternative = p.parseBlockExpression() + } + + return expression +} + +func (p *Parser) parseBlockExpression() *ast.BlockStatement { + block := &ast.BlockStatement{Token: p.curToken} + block.Statements = []ast.Statement{} + + p.nextToken() + + for !p.curTokenIs(token.RBRACE) && !p.curTokenIs(token.EOF) { + stmt := p.parseStatement() + if stmt != nil { + block.Statements = append(block.Statements, stmt) + } + p.nextToken() + } + + return block +} diff --git a/parser/parser_test.go b/parser/parser_test.go index 7c9ac89..acdac3f 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -356,6 +356,114 @@ func TestOperatorPrecedenceParsing(t *testing.T) { } } +func TestIfExpression(t *testing.T) { + input := `if (x < y) { x }` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain %d statements. got=%d\n", + 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\n", + 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 }` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain %d statements. got=%d\n", + 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\n", + 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("exp.Alternative.Statements does not contain 1 statements. got=%d\n", + 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 + } +} + func testLetStatement(t *testing.T, s ast.Statement, name string) bool { if s.TokenLiteral() != "let" { t.Errorf("s.TokenLiteral not 'let'. got=%q", s.TokenLiteral()) @@ -419,11 +527,34 @@ func testLiteralExpression( return testIntegerLiteral(t, exp, v) case string: return testIdentifier(t, exp, v) + case bool: + return testBooleanLiteral(t, exp, v) } t.Errorf("type of exp not handled. got=%T", exp) return false } +func testBooleanLiteral(t *testing.T, exp ast.Expression, value bool) bool { + bo, ok := exp.(*ast.Boolean) + if !ok { + t.Errorf("exp *ast.Boolean. got=%T", exp) + return false + } + + if bo.Value != value { + t.Errorf("bo.Value not %t. got=%t", value, bo.Value) + return false + } + + if bo.TokenLiteral() != fmt.Sprintf("%t", value) { + t.Errorf("intboeg.TokenLiteral not %t. got=%s", value, + bo.TokenLiteral()) + return false + } + + return true +} + func testIntegerLiteral(t *testing.T, il ast.Expression, value int64) bool { integ, ok := il.(*ast.IntegerLiteral) if !ok {