Skip to content

Commit

Permalink
2.8: Add if-else
Browse files Browse the repository at this point in the history
  • Loading branch information
nibral committed Jan 13, 2019
1 parent e80c585 commit d9c439f
Show file tree
Hide file tree
Showing 3 changed files with 202 additions and 0 deletions.
42 changes: 42 additions & 0 deletions ast/ast.go
Expand Up @@ -159,3 +159,45 @@ type Boolean struct {
func (b *Boolean) expressionNode() {}
func (b *Boolean) TokenLiteral() string { return b.Token.Literal }
func (b *Boolean) String() string { return b.Token.Literal }

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()
}

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()
}
51 changes: 51 additions & 0 deletions parser/parser.go
Expand Up @@ -65,6 +65,7 @@ func New(l *lexer.Lexer) *Parser {
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)
Expand Down Expand Up @@ -302,3 +303,53 @@ func (p *Parser) parseGroupedExpression() ast.Expression {

return exp
}

func (p *Parser) parseBlockStatement() *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
}

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
}
109 changes: 109 additions & 0 deletions parser/parser_test.go
Expand Up @@ -498,3 +498,112 @@ func testBooleanLiteral(t *testing.T, exp ast.Expression, value bool) bool {

return true
}

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("alternative is not 1 statements. got=%d\n",
len(exp.Consequence.Statements))
}

alternative, ok := exp.Alternative.Statements[0].(*ast.ExpressionStatement)
if !ok {
t.Fatalf("Statements[0] is not ast.ExpressionStatement. got=%T",
exp.Consequence.Statements[0])
}

if !testIdentifier(t, alternative.Expression, "y") {
return
}
}

0 comments on commit d9c439f

Please sign in to comment.