diff --git a/README.md b/README.md index e808b52..94a792e 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,19 @@ A Monkey programming language interpreter from the book [Writing An Interpreter In Go](https://interpreterbook.com/) by Thorsten Ball. +## Table of Contents + +- [Standard Types](#standard-types) + - [Integer](#integer) + - [Boolean](#boolean) +- [Operators](#operators) + - [Basic Arithmetic Operators](#basic-arithemtic-operators) + - [Comparison Operators](#comparison-operators) + - [Operator Precedence](#operator-precedence) + - [Grouped Expressions](#grouped-expressions) +- [Flow Control](#flow-control) + - [If Expressions](#if-expressions) + ## Standard Types Monkey supports several basic data types. @@ -66,3 +79,38 @@ You can use parentheses to influence the order of executing arithmetic operation ``` let x = (2 / (5 + 5)); ``` + +## Flow Control + +### If Expressions + +Monkey supports `if` expressions for flow control. An `if` expression evaluates a condition and executes the corresponding block of code. + +The syntax for an `if` expression is as follows: + +``` +if (condition) { + // block of code +} else { + // optional else block +} +``` + +- The `else` block is optional. +- Each block can contain multiple expressions or statements. +- The value of the `if` expression is the value of the last expression in the executed block. + +#### Example + +``` +let x = 10; +let y = 20; + +let max = if (x > y) { + x +} else { + y +}; +``` + +In this example, max will be set to `20` because `y` is greater than `x`. diff --git a/ast/ast.go b/ast/ast.go index 03e1c92..8d9b1ea 100644 --- a/ast/ast.go +++ b/ast/ast.go @@ -190,6 +190,56 @@ func (ie *InfixExpression) String() string { return fmt.Sprintf("(%s %s %s)", ie.Left.String(), ie.Operator, ie.Right.String()) } +// If Expression + +type IfExpression struct { + Token token.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() +} + +// Block Statement + +type BlockStatement struct { + Token token.Token // the `{`` 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() +} + // Boolean type BooleanLiteral struct { diff --git a/parser/parser.go b/parser/parser.go index db910fe..34a0d28 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -61,6 +61,7 @@ func NewParser(l *lexer.Lexer) *Parser { p.registerPrefix(token.BANG, p.parsePrefixExpression) p.registerPrefix(token.MINUS, p.parsePrefixExpression) p.registerPrefix(token.LPAREN, p.parseGroupedExpression) + p.registerPrefix(token.IF, p.parseIfExpression) p.registerInfix(token.PLUS, p.parseInfixExpression) p.registerInfix(token.MINUS, p.parseInfixExpression) @@ -115,7 +116,7 @@ func (p *Parser) peekTokenIs(t token.TokenType) bool { return p.peekToken.Type == t } -func (p *Parser) exceptPeek(t token.TokenType) bool { +func (p *Parser) expectPeek(t token.TokenType) bool { if p.peekTokenIs(t) { p.nextToken() @@ -167,13 +168,13 @@ func (p *Parser) parseStatement() ast.Statement { func (p *Parser) parseLetStatement() *ast.LetStatement { stmt := &ast.LetStatement{Token: p.curToken} - if !p.exceptPeek(token.IDENT) { + if !p.expectPeek(token.IDENT) { return nil } stmt.Name = &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} - if !p.exceptPeek(token.ASSIGN) { + if !p.expectPeek(token.ASSIGN) { return nil } @@ -265,7 +266,7 @@ func (p *Parser) parseGroupedExpression() ast.Expression { exp := p.parseExpression(LOWEST) - if !p.exceptPeek(token.RPAREN) { + if !p.expectPeek(token.RPAREN) { return nil } @@ -294,3 +295,49 @@ func (p *Parser) parseBooleanLiteral() ast.Expression { Value: p.curTokenIs(token.TRUE), } } + +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.parseBlockStatement() + + if p.peekTokenIs(token.ELSE) { + p.nextToken() + + if !p.expectPeek(token.LBRACE) { + return nil + } + + expression.Alternative = p.parseBlockStatement() + } + + return expression +} + +func (p *Parser) parseBlockStatement() *ast.BlockStatement { + block := &ast.BlockStatement{} + + p.nextToken() + + for !p.curTokenIs(token.RBRACE) && !p.curTokenIs(token.EOF) { + block.Statements = append(block.Statements, p.parseStatement()) + p.nextToken() + } + + return block +} diff --git a/parser/parser_test.go b/parser/parser_test.go index 4310c14..4989f8b 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -499,3 +499,100 @@ func TestBoolean(t *testing.T) { testLiteralExpression(t, stmt.Expression, tt.expected) } } + +func TestIfExpression(t *testing.T) { + t.Parallel() + + input := "if (x < y) { x }" + + l := lexer.NewLexer(input) + p := parser.NewParser(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain 1 statement. got=%d", len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] not *ast.ExpressionStatement. got=%T", program.Statements[0]) + } + + exp, ok := stmt.Expression.(*ast.IfExpression) + if !ok { + t.Fatalf("stmt.Expression not *ast.IfExpression. got=%T", stmt.Expression) + } + + if !testInfixExpression(t, exp.Condition, "x", "<", "y") { + return + } + + if len(exp.Consequence.Statements) != 1 { + t.Errorf("exp.Consequence.Statements is not 1 statements. got=%d", len(exp.Consequence.Statements)) + } + + consequence, ok := exp.Consequence.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("exp.Consequence.Statements[0] not an *ast.ExpressionStatement. got=%T", exp.Consequence.Statements[0]) + } + + if !testIdentifier(t, consequence.Expression, "x") { + return + } + + if exp.Alternative != nil { + t.Errorf("exp.Alternative was not nil. got=%+v", exp.Alternative) + } +} + +func TestIfElseExpression(t *testing.T) { + t.Parallel() + + input := "if (x < y) { x } else { y }" + + l := lexer.NewLexer(input) + p := parser.NewParser(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain 1 statement. got=%d", len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] not *ast.ExpressionStatement. got=%T", program.Statements[0]) + } + + exp, ok := stmt.Expression.(*ast.IfExpression) + if !ok { + t.Fatalf("stmt.Expression not *ast.IfExpression. got=%T", stmt.Expression) + } + + if !testInfixExpression(t, exp.Condition, "x", "<", "y") { + return + } + + if len(exp.Consequence.Statements) != 1 { + t.Errorf("exp.Consequence.Statements is not 1 statements. got=%d", len(exp.Consequence.Statements)) + } + + consequence, ok := exp.Consequence.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("exp.Consequence.Statements[0] not an *ast.ExpressionStatement. got=%T", exp.Consequence.Statements[0]) + } + + if !testIdentifier(t, consequence.Expression, "x") { + return + } + + alternative, ok := exp.Alternative.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("exp.alternative.Statements[0] not an *ast.ExpressionStatement. got=%T", exp.Consequence.Statements[0]) + } + + if !testIdentifier(t, alternative.Expression, "y") { + return + } +}