umx_compiler

UMX virtual machine "Monkey" interpreter / bytecode compiler
git clone git://bsandro.tech/umx_compiler
Log | Files | Refs

commit 45420a626bbd8fa777c8cadbb7b24646db156a58
parent 8885f23423bb7f442000fab6261d9fb427b930e9
Author: bsandro <email@bsandro.tech>
Date:   Mon, 13 Jun 2022 23:21:23 +0300

prefix operators: shriek(!) and minus(-)

Diffstat:
Mast/ast.go | 18++++++++++++++++++
Mparser/parser.go | 18++++++++++++++++++
Mparser/parser_test.go | 58++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 94 insertions(+), 0 deletions(-)

diff --git a/ast/ast.go b/ast/ast.go @@ -20,6 +20,24 @@ type Expression interface { expressionNode() } +type 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() +} + type Identifier struct { Token token.Token Value string diff --git a/parser/parser.go b/parser/parser.go @@ -43,6 +43,8 @@ func New(l *lexer.Lexer) *Parser { p.prefixParseFns = make(map[token.TokenType]prefixParseFn) p.registerPrefix(token.IDENT, p.parseIdentifier) p.registerPrefix(token.INT, p.parseIntegerLiteral) + p.registerPrefix(token.SHRIEK, p.parsePrefixExpression) + p.registerPrefix(token.MINUS, p.parsePrefixExpression) return p } @@ -120,6 +122,7 @@ func (p *Parser) parseExpressionStatement() *ast.ExpressionStatement { func (p *Parser) parseExpression(precedence int) ast.Expression { prefix := p.prefixParseFns[p.curToken.Type] if prefix == nil { + p.noPrefixParseFnError(p.curToken.Type) return nil } else { leftExp := prefix() @@ -173,3 +176,18 @@ func (p *Parser) registerPrefix(tokenType token.TokenType, fn prefixParseFn) { func (p *Parser) registerInfix(tokenType token.TokenType, fn infixParseFn) { p.infixParseFns[tokenType] = fn } + +func (p *Parser) noPrefixParseFnError(t token.TokenType) { + msg := fmt.Sprintf("no prefix parse function for %s", t) + p.errors = append(p.errors, msg) +} + +func (p *Parser) parsePrefixExpression() ast.Expression { + expression := &ast.PrefixExpression{ + Token: p.curToken, + Operator: p.curToken.Literal, + } + p.nextToken() + expression.Right = p.parseExpression(PREFIX) + return expression +} diff --git a/parser/parser_test.go b/parser/parser_test.go @@ -4,6 +4,7 @@ import ( "interp/ast" "interp/lexer" "testing" + "fmt" ) func TestLetStatements(t *testing.T) { @@ -165,3 +166,60 @@ func TestIntegerLiteralExpression(t *testing.T) { t.Errorf("literal.TokenLiteral is not '8' but %s", literal.TokenLiteral()) } } + +func TestParsingPrefixExpressions(t *testing.T) { + prefixTests := []struct{ + input string + operator string + intValue int64 + }{ + {"!5;", "!", 5}, + {"-15;", "-", 15}, + } + + for _, tt := range prefixTests { + l := lexer.New(tt.input) + p := New(l) + prg := p.ParseProgram() + checkParserErrors(t, p) + + if len(prg.Statements) != 1 { + t.Fatalf("prg.Statements count is wrong: %d instead of 1", 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]) + } + + expr, ok := statement.Expression.(*ast.PrefixExpression) + if !ok { + t.Fatalf("statement.Expression is not PrefixExpression but %T", statement.Expression) + } + + if expr.Operator != tt.operator { + t.Fatalf("exp.Operator is not %s but %s", tt.operator, expr.Operator) + } + + if !testIntegerLiteral(t, expr.Right, tt.intValue) { + return + } + } +} + +func testIntegerLiteral(t *testing.T, il ast.Expression, value int64) bool { + integ, ok := il.(*ast.IntegerLiteral) + if !ok { + t.Errorf("il is not *ast.IntegerLiteral but %T", il) + return false + } + if integ.Value != value { + t.Errorf("integ.Value is not %d but %d", value, integ.Value) + return false + } + if integ.TokenLiteral() != fmt.Sprintf("%d", value) { + t.Errorf("integ.TokenLiteral not %d but %s", value, integ.TokenLiteral()) + return false + } + return true +}