commit bf1df56370215b914d5a0e9ff416da6eb50b1500
parent 65428752a41df1171c0ba4735b8f6f7c4766dc88
Author: bsandro <email@bsandro.tech>
Date:   Sun, 19 Jun 2022 00:12:58 +0300
parser support for if statements
Diffstat:
3 files changed, 107 insertions(+), 0 deletions(-)
diff --git a/ast/ast.go b/ast/ast.go
@@ -176,3 +176,42 @@ func (il *IntegerLiteral) TokenLiteral() string {
 func (il *IntegerLiteral) String() string {
 	return il.Token.Literal
 }
+
+type IfExpression struct {
+	Token token.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()
+}
+
+type BlockStatement struct {
+	Token token.Token
+	Statements []Statement
+}
+func (bs *BlockStatement) 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()
+}
diff --git a/parser/parser.go b/parser/parser.go
@@ -72,6 +72,8 @@ func New(l *lexer.Lexer) *Parser {
 
 	p.registerPrefix(token.LPAREN, p.parseGroupedExpression)
 
+	p.registerPrefix(token.IF, p.parseIfExpression)
+
 	return p
 }
 
@@ -264,3 +266,34 @@ func (p *Parser) parseGroupedExpression() ast.Expression {
 	}
 	return expr
 }
+
+func (p *Parser) parseIfExpression() ast.Expression {
+	expr := &ast.IfExpression{Token: p.curToken}
+	if !p.expectPeek(token.LPAREN) {
+		return nil
+	}
+	p.nextToken()
+	expr.Condition = p.parseExpression(LOWEST)
+	if !p.expectPeek(token.RPAREN) {
+		return nil
+	}
+	if !p.expectPeek(token.LCURLY) {
+		return nil
+	}
+	expr.Consequence = p.parseBlockStatement()
+	return expr
+}
+
+func (p *Parser) parseBlockStatement() *ast.BlockStatement {
+	block := &ast.BlockStatement{Token: p.curToken}
+	block.Statements = []ast.Statement{}
+	p.nextToken()
+	for !p.curTokenIs(token.RCURLY) && !p.curTokenIs(token.EOF) {
+		statement := p.parseStatement()
+		if  statement != nil {
+			block.Statements = append(block.Statements, statement)
+		}
+		p.nextToken()
+	}
+	return block
+}
diff --git a/parser/parser_test.go b/parser/parser_test.go
@@ -400,3 +400,38 @@ func TestBooleanExpression(t *testing.T) {
 		}
 	}
 }
+
+func TestIfExpression(t *testing.T) {
+	input := "if (x<y) { x }"
+	l := lexer.New(input)
+	p := New(l)
+	prg := p.ParseProgram()
+	checkParserErrors(t, p)
+	if len(prg.Statements) != 1 {
+		t.Fatalf("len(prg.Statements) is not 1 but %d", len(prg.Statements))
+	}
+	statement, ok := prg.Statements[0].(*ast.ExpressionStatement)
+	if !ok {
+		t.Fatalf("prg.Statements[0] is not ExpressionStatement but %T", prg.Statements[0])
+	}
+	expr, ok := statement.Expression.(*ast.IfExpression)
+	if !ok {
+		t.Fatalf("statement.Expression is not IfExpression but %T", statement.Expression)
+	}
+	if !testInfixExpression(t, expr.Condition, "x", "<", "y") {
+		return
+	}
+	if len(expr.Consequence.Statements) != 1 {
+		t.Fatalf("len(expr.Consequence.Statements) is not 1 but %d", len(expr.Consequence.Statements))
+	}
+	cons, ok := expr.Consequence.Statements[0].(*ast.ExpressionStatement)
+	if !ok {
+		t.Fatalf("consequence statement is not Statement but %T", expr.Consequence.Statements[0])
+	}
+	if !testIdentifier(t, cons.Expression, "x") {
+		return
+	}
+	if expr.Alternative != nil {
+		t.Fatalf("expr alternative is not nil")
+	}
+}