Skip to content

Commit

Permalink
FEAT: now working on parsing prefix tokens (in Monkey: ! or -), but b…
Browse files Browse the repository at this point in the history
…roken, because parseExpression doesn't recognize it yet;

61/208 @ pdf
  • Loading branch information
MKaczkow committed Sep 2, 2024
1 parent 7a68912 commit da8e89d
Show file tree
Hide file tree
Showing 3 changed files with 183 additions and 69 deletions.
35 changes: 35 additions & 0 deletions monkey/interpreter/ast/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
34 changes: 34 additions & 0 deletions monkey/interpreter/parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"monkey/interpreter/token"

"fmt"
"strconv"
)

const (
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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
}
Expand Down
183 changes: 114 additions & 69 deletions monkey/interpreter/parser/parser_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package parser

import (
"fmt"
"monkey/interpreter/ast"
"monkey/interpreter/lexer"
"testing"
Expand Down Expand Up @@ -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 := `
Expand Down Expand Up @@ -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())
Expand All @@ -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 {
Expand Down

0 comments on commit da8e89d

Please sign in to comment.