Browse Source

§2.8 If/else expressions.

Frederic G. MARAND 5 years ago
parent
commit
c262d2bded
5 changed files with 249 additions and 5 deletions
  1. 3 0
      .idea/runConfigurations/Test_All.xml
  2. 64 4
      ast/ast.go
  3. 62 0
      parser/parser.go
  4. 116 0
      parser/parser_test.go
  5. 4 1
      parser/parser_tracing.go

+ 3 - 0
.idea/runConfigurations/Test_All.xml

@@ -3,6 +3,9 @@
     <module name="waiig15" />
     <working_directory value="$PROJECT_DIR$" />
     <go_parameters value="-i" />
+    <envs>
+      <env name="VERBOSE" value="" />
+    </envs>
     <framework value="gotest" />
     <kind value="DIRECTORY" />
     <package value="fgm/waiig15" />

+ 64 - 4
ast/ast.go

@@ -141,6 +141,31 @@ func (es *ExpressionStatement) String() string {
 	return ""
 }
 
+// BlockStatement fulfills the Node and Statement interfaces.
+// It represents a suite of statements.
+type BlockStatement struct {
+	Token      token.Token // the { token
+	Statements []Statement
+}
+
+func (bs *BlockStatement) statementNode() {}
+
+// TokenLiteral satisfies the Node interface.
+func (bs *BlockStatement) TokenLiteral() string {
+	return bs.Token.Literal
+}
+
+// String satisfies the Node and fmt.Stringer interfaces.
+func (bs *BlockStatement) String() string {
+	var out bytes.Buffer
+
+	for _, s := range bs.Statements {
+		out.WriteString(s.String())
+	}
+
+	return out.String()
+}
+
 // Identifier is the Node type for identifiers.
 type Identifier struct {
 	Token token.Token // the token.IDENT token. Why do we need it ?
@@ -188,7 +213,7 @@ func (il *IntegerLiteral) String() string {
 	return il.Token.Literal
 }
 
-// PrefixExpression fulfills the Node and Statement interfaces.
+// PrefixExpression fulfills the Node and Expression interfaces.
 // It represents a prefixed expression like:
 // "-5;"
 type PrefixExpression struct {
@@ -216,9 +241,9 @@ func (pe *PrefixExpression) String() string {
 	return out.String()
 }
 
-// InfixExpression fulfills the Node and Statement interfaces.
-// It represents a prefixed expression like:
-// "-5;"
+// InfixExpression fulfills the Node and Expression interfaces.
+// It represents an infix expression like:
+// "5 + 6;"
 type InfixExpression struct {
 	Token    token.Token // The operator token, e.g. +
 	Left     Expression
@@ -245,3 +270,38 @@ func (ie *InfixExpression) String() string {
 
 	return out.String()
 }
+
+// IfExpression fulfills the Node and Expression interfaces.
+// It represents a condition expression like:
+// "if true 5 else 6;"
+// Note that if/else are expressions, not just statements.
+type IfExpression struct {
+	Token       token.Token // The "if" token
+	Condition   Expression
+	Consequence *BlockStatement
+	Alternative *BlockStatement
+}
+
+func (ie *IfExpression) expressionNode() {}
+
+// TokenLiteral satisfies the Node interface.
+func (ie *IfExpression) TokenLiteral() string {
+	return ie.Token.Literal
+}
+
+// String satisfies the Node and fmt.Stringer interfaces.
+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()
+}

+ 62 - 0
parser/parser.go

@@ -66,6 +66,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)
 	for _, tok := range []token.TokenType{
@@ -340,6 +341,67 @@ func (p *Parser) parseBoolean() ast.Expression {
 	return expression
 }
 
+func (p *Parser) parseIfExpression() ast.Expression {
+	expression := &ast.IfExpression{
+		Token: p.curToken,
+	}
+
+	// If expressions need parentheses around the condition. Opening one.
+	if !p.expectPeek(token.LPAREN) {
+		return nil
+	}
+
+	p.nextToken()
+	expression.Condition = p.parseExpression(LOWEST)
+
+	// If expressions need parentheses around the condition. Closing one.
+	if !p.expectPeek(token.RPAREN) {
+		return nil
+	}
+
+	// Consequences are blocks, no omitted braces as in C/JS/PHP.
+	if !p.expectPeek(token.LBRACE) {
+		return nil
+	}
+
+	expression.Consequence = p.parseBlockStatement()
+
+	if p.peekTokenIs(token.ELSE) {
+		// We know it's an ELSE since we just checked, so no expectPeek().
+		p.nextToken()
+
+		// Alternatives are blocks, no omitted braces as in C/JS/PHP.
+		if !p.expectPeek(token.LBRACE) {
+			return nil
+		}
+
+		expression.Alternative = p.parseBlockStatement()
+	}
+
+	return expression
+}
+
+func (p *Parser) parseBlockStatement() *ast.BlockStatement {
+	block := &ast.BlockStatement{
+		Token: p.curToken,
+	}
+	block.Statements = []ast.Statement{}
+
+	// Consume LBRACE.
+	p.nextToken()
+
+	// Parse until RBRACE or EOF.
+	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) parseGroupedExpression() ast.Expression {
 	p.nextToken()
 

+ 116 - 0
parser/parser_test.go

@@ -385,6 +385,122 @@ func TestBooleanExpression(t *testing.T) {
 	}
 }
 
+func TestIfExpression(t *testing.T) {
+	// Notice: no semicolon, no return.
+	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))
+	}
+
+	// This is the external statement (if) not the one in Consequence (block).
+	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") {
+		// No error message, they have already been generated in testInfixExpression.
+		return
+	}
+
+	if len(exp.Consequence.Statements) != 1 {
+		t.Fatalf("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") {
+		// No error message, they have already been generated in testIdentifier.
+		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))
+	}
+
+	// This is the external statement (if) not the one in Consequence (block).
+	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") {
+		// No error message, they have already been generated in testInfixExpression.
+		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())

+ 4 - 1
parser/parser_tracing.go

@@ -2,6 +2,7 @@ package parser
 
 import (
 	"fmt"
+	"os"
 	"strings"
 )
 
@@ -14,7 +15,9 @@ func indentLevel() string {
 }
 
 func tracePrint(fs string) {
-	fmt.Printf("%s%s\n", indentLevel(), fs)
+	if os.Getenv("VERBOSE") != "" {
+		fmt.Printf("%s%s\n", indentLevel(), fs)
+	}
 }
 
 func indentInc() {