commit 734ab8682a5f8bcff89e9bf5f344852d9e849929
parent 800aaef02c811fab39454b02cdb7589b45c61e66
Author: bsandro <email@bsandro.tech>
Date:   Mon, 13 Jun 2022 17:57:43 +0300
identifier expressions support
Diffstat:
4 files changed, 161 insertions(+), 1 deletion(-)
diff --git a/ast/ast.go b/ast/ast.go
@@ -1,11 +1,13 @@
 package ast
 
 import (
+	"bytes"
 	"interp/token"
 )
 
 type Node interface {
 	TokenLiteral() string
+	String() string
 }
 
 type Statement interface {
@@ -27,6 +29,9 @@ func (i *Identifier) expressionNode() {}
 func (i *Identifier) TokenLiteral() string {
 	return i.Token.Literal
 }
+func (i *Identifier) String() string {
+	return i.Value
+}
 
 type LetStatement struct {
 	Token token.Token
@@ -38,6 +43,17 @@ func (l *LetStatement) statementNode() {}
 func (l *LetStatement) TokenLiteral() string {
 	return l.Token.Literal
 }
+func (l *LetStatement) String() string {
+	var out bytes.Buffer
+	out.WriteString(l.TokenLiteral() + " ")
+	out.WriteString(l.Name.String())
+	out.WriteString(" = ")
+	if l.Value != nil {
+		out.WriteString(l.Value.String())
+	}
+	out.WriteString(";")
+	return out.String()
+}
 
 type ReturnStatement struct {
 	Token       token.Token
@@ -48,6 +64,31 @@ func (rs *ReturnStatement) statementNode() {}
 func (rs *ReturnStatement) TokenLiteral() string {
 	return rs.Token.Literal
 }
+func (rs *ReturnStatement) String() string {
+	var out bytes.Buffer
+	out.WriteString(rs.TokenLiteral() + " ")
+	if rs.ReturnValue != nil {
+		out.WriteString(rs.ReturnValue.String())
+	}
+	out.WriteString(";")
+	return out.String()
+}
+
+type ExpressionStatement struct {
+	Token      token.Token
+	Expression Expression
+}
+
+func (ec *ExpressionStatement) statementNode() {}
+func (ec *ExpressionStatement) TokenLiteral() string {
+	return ec.Token.Literal
+}
+func (ec *ExpressionStatement) String() string {
+	if ec.Expression != nil {
+		return ec.Expression.String()
+	}
+	return ""
+}
 
 type Program struct {
 	Statements []Statement
@@ -60,3 +101,11 @@ func (p *Program) TokenLiteral() string {
 		return ""
 	}
 }
+
+func (p *Program) String() string {
+	var out bytes.Buffer
+	for _, s := range p.Statements {
+		out.WriteString(s.String())
+	}
+	return out.String()
+}
diff --git a/ast/ast_test.go b/ast/ast_test.go
@@ -0,0 +1,28 @@
+package ast
+
+import (
+	"interp/token"
+	"testing"
+)
+
+func TestString(t *testing.T) {
+	program := &Program{
+		Statements: []Statement{
+			&LetStatement{
+				Token: token.Token{Type: token.LET, Literal: "let"},
+				Name: &Identifier{
+					Token: token.Token{Type: token.IDENT, Literal: "myVar"},
+					Value: "myVar",
+				},
+				Value: &Identifier{
+					Token: token.Token{Type: token.IDENT, Literal: "anotherVar"},
+					Value: "anotherVar",
+				},
+			},
+		},
+	}
+
+	if program.String() != "let myVar = anotherVar;" {
+		t.Errorf("program.String() is wrong: '%s'", program.String())
+	}
+}
diff --git a/parser/parser.go b/parser/parser.go
@@ -7,11 +7,28 @@ import (
 	"interp/token"
 )
 
+const (
+	_ int = iota
+	LOWEST
+	EQUALS      // ==
+	LESSGREATER // > or <
+	SUM         // +
+	PRODUCT     // *
+	PREFIX      // -X or !X
+	CALL        // myFunction(x)
+)
+
+type prefixParseFn func() ast.Expression
+type infixParseFn func(ast.Expression) ast.Expression
+
 type Parser struct {
 	l         *lexer.Lexer
 	curToken  token.Token
 	peekToken token.Token
 	errors    []string
+
+	prefixParseFns map[token.TokenType]prefixParseFn
+	infixParseFns  map[token.TokenType]infixParseFn
 }
 
 func New(l *lexer.Lexer) *Parser {
@@ -21,6 +38,10 @@ func New(l *lexer.Lexer) *Parser {
 	}
 	p.nextToken()
 	p.nextToken()
+
+	p.prefixParseFns = make(map[token.TokenType]prefixParseFn)
+	p.registerPrefix(token.IDENT, p.parseIdentifier)
+
 	return p
 }
 
@@ -53,7 +74,7 @@ func (p *Parser) parseStatement() ast.Statement {
 	case token.RETURN:
 		return p.parseReturnStatement()
 	default:
-		return nil
+		return p.parseExpressionStatement()
 	}
 }
 
@@ -85,6 +106,29 @@ func (p *Parser) parseReturnStatement() *ast.ReturnStatement {
 	return statement
 }
 
+func (p *Parser) parseExpressionStatement() *ast.ExpressionStatement {
+	statement := &ast.ExpressionStatement{Token: p.curToken}
+	statement.Expression = p.parseExpression(LOWEST)
+	if p.peekTokenIs(token.SEMICOLON) {
+		p.nextToken()
+	}
+	return statement
+}
+
+func (p *Parser) parseExpression(precedence int) ast.Expression {
+	prefix := p.prefixParseFns[p.curToken.Type]
+	if prefix == nil {
+		return nil
+	} else {
+		leftExp := prefix()
+		return leftExp
+	}
+}
+
+func (p *Parser) parseIdentifier() ast.Expression {
+	return &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal}
+}
+
 func (p *Parser) curTokenIs(t token.TokenType) bool {
 	return p.curToken.Type == t
 }
@@ -107,3 +151,11 @@ func (p *Parser) peekError(t token.TokenType) {
 	msg := fmt.Sprintf("token expected: %s, got: %s", t, p.peekToken.Type)
 	p.errors = append(p.errors, msg)
 }
+
+func (p *Parser) registerPrefix(tokenType token.TokenType, fn prefixParseFn) {
+	p.prefixParseFns[tokenType] = fn
+}
+
+func (p *Parser) registerInfix(tokenType token.TokenType, fn infixParseFn) {
+	p.infixParseFns[tokenType] = fn
+}
diff --git a/parser/parser_test.go b/parser/parser_test.go
@@ -104,3 +104,34 @@ func checkParserErrors(t *testing.T, p *Parser) {
 	}
 	t.FailNow()
 }
+
+func TestIdentifierExpression(t *testing.T) {
+	input := "foo;"
+
+	l := lexer.New(input)
+	p := New(l)
+	prg := p.ParseProgram()
+	checkParserErrors(t, p)
+
+	if len(prg.Statements) != 1 {
+		t.Fatalf("prg.Statements has more than 1 element (%d)", len(prg.Statements))
+	}
+
+	statement, ok := prg.Statements[0].(*ast.ExpressionStatement)
+	if !ok {
+		t.Fatalf("prg.Statements[0] is not ast.ExpressionStatement but %T", prg.Statements[0])
+	}
+
+	ident, ok := statement.Expression.(*ast.Identifier)
+	if !ok {
+		t.Fatalf("expression is not *ast.Identifier but %T", statement.Expression)
+	}
+
+	if ident.Value != "foo" {
+		t.Fatalf("ident.Value is not valid: %s", ident.Value)
+	}
+
+	if ident.TokenLiteral() != "foo" {
+		t.Errorf("ident.TokenLiteral() is not 'foo' but %s", ident.TokenLiteral())
+	}
+}