diff --git a/monkey/interpreter/ast/ast.go b/monkey/interpreter/ast/ast.go index edb72ac..fe18eee 100644 --- a/monkey/interpreter/ast/ast.go +++ b/monkey/interpreter/ast/ast.go @@ -110,3 +110,38 @@ func (es *ExpressionStatement) String() string { } return "" } + +type IntegerLiteral struct { + Token token.Token + Value int64 +} + +func (il *IntegerLiteral) expressionNode() {} +func (il *IntegerLiteral) TokenLiteral() string { + return il.Token.Literal +} +func (il *IntegerLiteral) String() string { + return il.Token.Literal +} + + +func PrefixExpression struct { + Token token.Token + Operator string + Right Expression +} + +func (pe *PrefixExpression) expressionNode() {} +func (pe *PrefixExpression) TokenLiteral() string { + return pe.Token.Literal +} +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() +} diff --git a/monkey/interpreter/parser/parser.go b/monkey/interpreter/parser/parser.go index 43faab9..b15a0ba 100644 --- a/monkey/interpreter/parser/parser.go +++ b/monkey/interpreter/parser/parser.go @@ -6,6 +6,7 @@ import ( "monkey/interpreter/token" "fmt" + "strconv" ) const ( @@ -42,6 +43,10 @@ func New(l *lexer.Lexer) *Parser { errors: []string{}, } + p.prefixParseFns = make(map[token.TokenType]prefixParseFn) + p.registerPrefix(token.IDENT, p.parseIdentifier) + p.registerPrefix(token.INT, p.parseIntegerLiteral) + // Read two tokens, so curToken and peekToken are both set p.nextToken() p.nextToken() @@ -73,6 +78,10 @@ func (p *Parser) nextToken() { p.peekToken = p.l.NextToken() } +func (p *Parser) parseIdentifier() ast.Expression { + return &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} +} + func (p *Parser) parseStatement() ast.Statement { switch p.curToken.Type { case token.LET: @@ -132,6 +141,31 @@ func (p *Parser) parseExpressionStatement() *ast.ExpressionStatement { 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) parseIntegerLiteral() ast.Expression { + lit := &ast.IntegerLiteral{Token: p.curToken} + + value, err := strconv.ParseInt(p.curToken.Literal, 0, 64) + if err != nil { + msg := fmt.Sprintf("Could not parse %q as integer.", p.curToken.Literal) + p.errors = append(p.errors, msg) + return nil + } + + lit.Value = value + + return lit +} + func (p *Parser) curTokenIs(t token.TokenType) bool { return p.curToken.Type == t } diff --git a/monkey/interpreter/parser/parser_test.go b/monkey/interpreter/parser/parser_test.go index b2b00a7..d49f2cd 100644 --- a/monkey/interpreter/parser/parser_test.go +++ b/monkey/interpreter/parser/parser_test.go @@ -1,6 +1,7 @@ package parser import ( + "fmt" "monkey/interpreter/ast" "monkey/interpreter/lexer" "testing" @@ -42,75 +43,64 @@ let foobar = 838383; } } -// TODO: commented out for CI to work (xd) - -// func TestLetStatementsTokenMissing(t *testing.T) { -// input := ` -// let x 5; -// let y = 10; -// let foobar = 838383; -// ` -// l := lexer.New(input) -// p := New(l) - -// program := p.ParseProgram() -// checkParserErrors(t, p) - -// if program == nil { -// t.Fatalf("ParseProgram() returned nil") -// } -// if len(program.Statements) != 3 { -// t.Fatalf("program.Statements does not contain 3 statements. got=%d", -// len(program.Statements)) -// } - -// tests := []struct { -// expectedIdentifier string -// }{ -// {"x"}, -// {"y"}, -// {"foobar"}, -// } - -// for i, tt := range tests { -// stmt := program.Statements[i] -// if !testLetStatement(t, stmt, tt.expectedIdentifier) { -// return -// } -// } -// } - -// TODO: commented out for CI to work (xd) -// 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()) -// } -// } +func TestIntegerLiteralExpression(t *testing.T) { + input := "5;" + 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]) + } + literal, ok := stmt.Expression.(*ast.IntegerLiteral) + if !ok { + t.Fatalf("exp not *ast.IntegerLiteral. got=%T", stmt.Expression) + } + if literal.Value != 5 { + t.Errorf("literal.Value not %d. got=%d", 5, literal.Value) + } + if literal.TokenLiteral() != "5" { + t.Errorf("literal.TokenLiteral not %s. got=%s", "5", + literal.TokenLiteral()) + } +} + +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()) + } +} func TestReturnStatements(t *testing.T) { input := ` @@ -141,6 +131,43 @@ return 993322; } } +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("stmt 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 + } + } +} + 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()) @@ -167,6 +194,24 @@ func testLetStatement(t *testing.T, s ast.Statement, name string) bool { return true } +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("inted.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 checkParserErrors(t *testing.T, p *Parser) { errors := p.Errors() if len(errors) == 0 {