Skip to content

Commit

Permalink
Merge pull request #3 from kamilturek/if-expressions
Browse files Browse the repository at this point in the history
If Expressions
  • Loading branch information
kamilturek authored Jun 15, 2024
2 parents b4daf99 + db0289d commit 77cb3c0
Show file tree
Hide file tree
Showing 4 changed files with 246 additions and 4 deletions.
48 changes: 48 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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`.
50 changes: 50 additions & 0 deletions ast/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
55 changes: 51 additions & 4 deletions parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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()

Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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
}
97 changes: 97 additions & 0 deletions parser/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}

0 comments on commit 77cb3c0

Please sign in to comment.