diff --git a/ast/ast.go b/ast/ast.go index cca8408..63787ab 100644 --- a/ast/ast.go +++ b/ast/ast.go @@ -1,9 +1,13 @@ package ast -import "monkey_interpreter/token" +import ( + "bytes" + "monkey_interpreter/token" +) type Node interface { TokenLiteral() string + String() string } type Statement interface { @@ -28,6 +32,25 @@ func (p *Program) TokenLiteral() string { } } +func (p *Program) String() string { + var out bytes.Buffer + + for _, s := range p.Statements { + out.WriteString(s.String()) + } + + return out.String() +} + +type Identifier struct { + Token token.Token // the token.IDENT token + Value string +} + +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 // the token.LET token Name *Identifier @@ -36,14 +59,15 @@ type LetStatement struct { func (ls *LetStatement) statementNode() {} func (ls *LetStatement) TokenLiteral() string { return ls.Token.Literal } +func (ls *LetStatement) String() string { + var out bytes.Buffer -type Identifier struct { - Token token.Token // the token.IDENT token - Value string -} + out.WriteString(ls.TokenLiteral() + " ") + out.WriteString(ls.Name.String()) + out.WriteString(" = ") -func (i *Identifier) statementNode() {} -func (i *Identifier) TokenLiteral() string { return i.Token.Literal } + return out.String() +} type ReturnStatement struct { Token token.Token // the token.RETURN token @@ -52,3 +76,28 @@ type ReturnStatement struct { 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()) + } + + return out.String() +} + +type ExpressionStatement struct { + Token token.Token // the first token of the expression + Expression Expression +} + +func (es *ExpressionStatement) statementNode() {} +func (es *ExpressionStatement) TokenLiteral() string { return es.Token.Literal } +func (es *ExpressionStatement) String() string { + if es.Expression != nil { + return es.Expression.String() + } + return "" +} diff --git a/ast/ast_test.go b/ast/ast_test.go new file mode 100644 index 0000000..6637a17 --- /dev/null +++ b/ast/ast_test.go @@ -0,0 +1,35 @@ +package ast + +import ( + "monkey_interpreter/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: &Identifier{ + Token: token.Token{ + Type: token.IDENT, + Literal: "anotherVar", + }, + }, + }, + }, + } + + if program.String() != "lat myVar = anotherVar;" { + t.Errorf("program.String() wrong. got=%q", program.String()) + } +} diff --git a/parser/parser.go b/parser/parser.go index 41cbc57..c8504bb 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -7,6 +7,22 @@ import ( "monkey_interpreter/token" ) +const ( + _ int = iota + LOWEST + EQUALS // == + LESSGRATER // < or > + SUM // + + PRODUCT // * + PREFIX // -X or !X + CALL // myFunction(X) +) + +type ( + prefixParseFn func() ast.Expression + infixParseFn func(expression ast.Expression) ast.Expression +) + type Parser struct { l *lexer.Lexer @@ -14,6 +30,9 @@ type Parser struct { curToken token.Token peekToken token.Token + + prefixParseFns map[token.TokenType]prefixParseFn + infixParseFns map[token.TokenType]infixParseFn } func New(l *lexer.Lexer) *Parser { @@ -26,9 +45,20 @@ 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 } +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 +} + func (p *Parser) Errors() []string { return p.errors } @@ -65,7 +95,7 @@ func (p *Parser) parseStatement() ast.Statement { case token.RETURN: return p.parseReturnStatement() default: - return nil + return p.parseExpressionStatement() } } @@ -121,3 +151,32 @@ func (p *Parser) parseReturnStatement() *ast.ReturnStatement { return stmt } + +func (p *Parser) parseExpressionStatement() *ast.ExpressionStatement { + stmt := &ast.ExpressionStatement{Token: p.curToken} + + stmt.Expression = p.parseExpression(LOWEST) + + if p.peekTokenIs(token.SEMICOLON) { + p.nextToken() + } + + return stmt +} + +func (p *Parser) parseExpression(precedence int) ast.Expression { + prefix := p.prefixParseFns[p.curToken.Type] + if prefix == nil { + return nil + } + leftExp := prefix() + + return leftExp +} + +func (p *Parser) parseIdentifier() ast.Expression { + return &ast.Identifier{ + Token: p.curToken, + Value: p.curToken.Literal, + } +} diff --git a/parser/parser_test.go b/parser/parser_test.go index e5378ed..f0b0d06 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -14,7 +14,6 @@ let foobar = 838383; ` l := lexer.New(input) p := New(l) - program := p.ParseProgram() checkParserErrors(t, p) @@ -85,7 +84,6 @@ return 993322; l := lexer.New(input) p := New(l) - program := p.ParseProgram() checkParserErrors(t, p) @@ -105,3 +103,31 @@ return 993322; } } } + +func TestIdentifierExpression(t *testing.T) { + input := `foobar;` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program has not enough statements. got=%d", 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]) + } + + ident, ok := stmt.Expression.(*ast.Identifier) + if !ok { + t.Fatalf("exp not *ast.Identifier. got=%T", stmt.Expression) + } + if ident.Value != "foobar" { + t.Errorf("ident.Value not %s. got=%s", "foobar", ident.Value) + } + if ident.TokenLiteral() != "foobar" { + t.Errorf("ident.TokenLiteral not %s. got=%s", "foobar", ident.TokenLiteral()) + } +}