Browse Source

§2.6 Parsing prefix expressions.

Frederic G. MARAND 5 years ago
parent
commit
e405e6bda2

+ 3 - 1
ast/expression.go

@@ -1,6 +1,8 @@
 package ast
 
-import "code.osinet.fr/fgm/waiig15/token"
+import (
+	"code.osinet.fr/fgm/waiig15/token"
+)
 
 // ExpressionStatement fulfills the Node and Statement interfaces.
 // It represents a statement made of a bare expression like:

+ 35 - 0
ast/prefix_expression.go

@@ -0,0 +1,35 @@
+package ast
+
+import (
+	"bytes"
+
+	"code.osinet.fr/fgm/waiig15/token"
+)
+
+// ExpressionStatement fulfills the Node and Statement interfaces.
+// It represents a prefixed expression like:
+// "-5;"
+type PrefixExpression struct {
+	Token    token.Token
+	Operator string
+	Right    Expression
+}
+
+// String satisfies the Node and fmt.Stringer interfaces.
+func (pe *PrefixExpression) String() string {
+	var out bytes.Buffer
+
+	out.WriteString("(")
+	out.WriteString(pe.Operator)
+	out.WriteString(pe.Right.String())
+	out.WriteString(")")
+
+	return out.String()
+}
+
+func (pe *PrefixExpression) expressionNode() {}
+
+// TokenLiteral satisfies the Node interface.
+func (pe *PrefixExpression) TokenLiteral() string {
+	return pe.Token.Literal
+}

+ 2 - 0
parser/parser.go

@@ -46,8 +46,10 @@ func New(l *lexer.Lexer) *Parser {
 	}
 
 	p.prefixParseFns = make(map[token.TokenType]prefixParseFn)
+	p.registerPrefix(token.BANG, p.parsePrefixExpression)
 	p.registerPrefix(token.IDENT, p.parseIdentifier)
 	p.registerPrefix(token.INT, p.parseIntegerLiteral)
+	p.registerPrefix(token.MINUS, p.parsePrefixExpression)
 
 	// Read two tokens, so curToken and peeToken are both set.
 	p.nextToken()

+ 8 - 0
parser/parser_expression.go

@@ -1,13 +1,21 @@
 package parser
 
 import (
+	"fmt"
+
 	"code.osinet.fr/fgm/waiig15/ast"
 	"code.osinet.fr/fgm/waiig15/token"
 )
 
+func (p *Parser) noPrefixParseFnError(t token.TokenType) {
+	msg := fmt.Sprintf("no prefix parse function for %s found", t)
+	p.errors = append(p.errors, msg)
+}
+
 func (p *Parser) parseExpression(precedence int) ast.Expression {
 	prefix := p.prefixParseFns[p.curToken.Type]
 	if prefix == nil {
+		p.noPrefixParseFnError(p.curToken.Type)
 		return nil
 	}
 

+ 20 - 0
parser/parser_prefix.go

@@ -0,0 +1,20 @@
+package parser
+
+import (
+	"code.osinet.fr/fgm/waiig15/ast"
+)
+
+func (p *Parser) parsePrefixExpression() ast.Expression {
+	expression := &ast.PrefixExpression{
+		Token:    p.curToken,
+		Operator: p.curToken.Literal,
+	}
+
+	// Consume the operator token to progress to the prefixed expression.
+	p.nextToken()
+
+	// The precedence is now that of the prefix operator instead of the lowest.
+	expression.Right = p.parseExpression(PREFIX)
+
+	return expression
+}

+ 74 - 0
parser/parser_prefix_test.go

@@ -0,0 +1,74 @@
+package parser
+
+import (
+	"fmt"
+	"testing"
+
+	"code.osinet.fr/fgm/waiig15/ast"
+	"code.osinet.fr/fgm/waiig15/lexer"
+)
+
+func testIntegerLiteral(t *testing.T, il ast.Expression, value int64) bool {
+	integ, ok := il.(*ast.IntegerLiteral)
+	if !ok {
+		t.Errorf("il not *ast.IntegerLiteral. got=%T", il)
+		return false
+	}
+
+	if integ.Value != value {
+		t.Errorf("integ.Value not %d. got =%d", value, integ.Value)
+		return false
+	}
+
+	if integ.TokenLiteral() != fmt.Sprintf("%d", value) {
+		t.Errorf("integ.TokenLiteral not %d. got=%s",
+			value, integ.TokenLiteral())
+		return false
+	}
+
+	return true
+}
+
+func TestParsingPrefixExpressions(t *testing.T) {
+	prefixTests := []struct {
+		input        string
+		operator     string
+		integerValue int64
+	}{
+		{"!5", "!", 5},
+		{"-15", "-", 15},
+	}
+
+	for _, tt := range prefixTests {
+		l := lexer.New(tt.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.PrefixExpression)
+		if !ok {
+			t.Fatalf("stms is not ast.PrefixExpression. got=%T",
+				stmt.Expression)
+		}
+
+		if exp.Operator != tt.operator {
+			t.Fatalf("exp.Operator is not '%s'. got=%s",
+				tt.operator, exp.Operator)
+		}
+
+		if !testIntegerLiteral(t, exp.Right, tt.integerValue) {
+			return
+		}
+	}
+}