umx_compiler

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

commit 734ab8682a5f8bcff89e9bf5f344852d9e849929
parent 800aaef02c811fab39454b02cdb7589b45c61e66
Author: bsandro <email@bsandro.tech>
Date:   Mon, 13 Jun 2022 17:57:43 +0300

identifier expressions support

Diffstat:
Mast/ast.go | 49+++++++++++++++++++++++++++++++++++++++++++++++++
Aast/ast_test.go | 28++++++++++++++++++++++++++++
Mparser/parser.go | 54+++++++++++++++++++++++++++++++++++++++++++++++++++++-
Mparser/parser_test.go | 31+++++++++++++++++++++++++++++++
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()) + } +}