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