diff --git a/README.md b/README.md index e36f080..aba29c1 100644 --- a/README.md +++ b/README.md @@ -77,8 +77,7 @@ For more detail about language, you can check [here](https://ninja.jonathan.pt) # Demo -Resolving katas you can check this repository -https://adventofcode.com/2015 +Resolved [katas](https://github.com/gravataLonga/ninja-lang-katas) from this [website](https://adventofcode.com/2015) # Syntax @@ -484,6 +483,24 @@ var true false function delete enum return if else for import break case ``` +## Lexical Scooping + +[Inspiration](https://craftinginterpreters.com/resolving-and-binding.html) + +> To “resolve” a variable usage, we only need to calculate how many “hops” away the declared variable will be in the +> environment chain. The interesting question is when to do this calculation—or, put differently, where in our +> interpreter’s implementation do we stuff the code for it? + +## A variable resolution pass + +> After the parser produces the syntax tree, but before the interpreter starts executing it, we’ll do a single walk over +> the tree to resolve all of the variables it contains. Additional passes between parsing and execution are common. +> If Lox had static types, we could slide a type checker in there. Optimizations are often implemented in separate +> passes like this too. Basically, any work that doesn't rely on state that’s only available at runtime can be done +> in this way. + +When binding a variable we need to know how depth we are in rabbit hole. + ## Examples 1. Check tests diff --git a/ast/array_literal.go b/ast/array_literal.go index 1ec65cf..cf99236 100644 --- a/ast/array_literal.go +++ b/ast/array_literal.go @@ -12,7 +12,7 @@ type ArrayLiteral struct { } func (al *ArrayLiteral) expressionNode() {} -func (al *ArrayLiteral) TokenLiteral() string { return string(al.Token.Literal) } +func (al *ArrayLiteral) TokenLiteral() string { return al.Token.Literal } func (al *ArrayLiteral) String() string { var out bytes.Buffer elements := make([]string, len(al.Elements)) diff --git a/ast/block_statement.go b/ast/block_statement.go index 84b86c8..30defd7 100644 --- a/ast/block_statement.go +++ b/ast/block_statement.go @@ -11,7 +11,7 @@ type BlockStatement struct { } func (bs *BlockStatement) statementNode() {} -func (bs *BlockStatement) TokenLiteral() string { return string(bs.Token.Literal) } +func (bs *BlockStatement) TokenLiteral() string { return bs.Token.Literal } func (bs *BlockStatement) String() string { var out bytes.Buffer for _, s := range bs.Statements { diff --git a/ast/boolean.go b/ast/boolean.go index dcd4965..3147fbe 100644 --- a/ast/boolean.go +++ b/ast/boolean.go @@ -8,5 +8,5 @@ type Boolean struct { } func (il *Boolean) expressionNode() {} -func (il *Boolean) TokenLiteral() string { return string(il.Token.Literal) } -func (il *Boolean) String() string { return string(il.Token.Literal) } +func (il *Boolean) TokenLiteral() string { return il.Token.Literal } +func (il *Boolean) String() string { return il.Token.Literal } diff --git a/ast/break.go b/ast/break.go index 78ada99..6175a01 100644 --- a/ast/break.go +++ b/ast/break.go @@ -9,7 +9,7 @@ type BreakStatement struct { } func (rs *BreakStatement) statementNode() {} -func (rs *BreakStatement) TokenLiteral() string { return string(rs.Token.Literal) } +func (rs *BreakStatement) TokenLiteral() string { return rs.Token.Literal } func (rs *BreakStatement) String() string { return rs.TokenLiteral() } diff --git a/ast/call_expression.go b/ast/call_expression.go index d4e64de..5183167 100644 --- a/ast/call_expression.go +++ b/ast/call_expression.go @@ -13,7 +13,7 @@ type CallExpression struct { } func (ce *CallExpression) expressionNode() {} -func (ce *CallExpression) TokenLiteral() string { return string(ce.Token.Literal) } +func (ce *CallExpression) TokenLiteral() string { return ce.Token.Literal } func (ce *CallExpression) String() string { var out bytes.Buffer args := make([]string, len(ce.Arguments)) diff --git a/ast/delete_statement.go b/ast/delete_statement.go index 59660be..88ca33b 100644 --- a/ast/delete_statement.go +++ b/ast/delete_statement.go @@ -12,7 +12,7 @@ type DeleteStatement struct { } func (de *DeleteStatement) statementNode() {} -func (de *DeleteStatement) TokenLiteral() string { return string(de.Token.Literal) } +func (de *DeleteStatement) TokenLiteral() string { return de.Token.Literal } func (de *DeleteStatement) String() string { var out bytes.Buffer diff --git a/ast/expression_statement.go b/ast/expression_statement.go index d58f9f3..841d131 100644 --- a/ast/expression_statement.go +++ b/ast/expression_statement.go @@ -3,12 +3,12 @@ package ast import "ninja/token" type ExpressionStatement struct { - Token token.Token // the first token of the expression + Token token.Token Expression Expression } func (es *ExpressionStatement) statementNode() {} -func (es *ExpressionStatement) TokenLiteral() string { return string(es.Token.Literal) } +func (es *ExpressionStatement) TokenLiteral() string { return es.Token.Literal } func (es *ExpressionStatement) String() string { if es.Expression != nil { return es.Expression.String() diff --git a/ast/float_literal.go b/ast/float_literal.go index 9084e21..0f8d926 100644 --- a/ast/float_literal.go +++ b/ast/float_literal.go @@ -10,12 +10,7 @@ type FloatLiteral struct { } func (il *FloatLiteral) expressionNode() {} -func (il *FloatLiteral) TokenLiteral() string { return string(il.Token.Literal) } +func (il *FloatLiteral) TokenLiteral() string { return il.Token.Literal } func (il *FloatLiteral) String() string { return string(il.Token.Literal) } - -// ToBeDeleted return always smallest decimal place -func ToBeDeleted(f float64, precision uint) float64 { - return f -} diff --git a/ast/for_statement.go b/ast/for_statement.go index 11c0fea..89143a9 100644 --- a/ast/for_statement.go +++ b/ast/for_statement.go @@ -14,7 +14,7 @@ type ForStatement struct { } func (fs *ForStatement) expressionNode() {} -func (fs *ForStatement) TokenLiteral() string { return string(fs.Token.Literal) } +func (fs *ForStatement) TokenLiteral() string { return fs.Token.Literal } func (fs *ForStatement) String() string { var out bytes.Buffer diff --git a/ast/function.go b/ast/function.go index baae5e2..1db38b7 100644 --- a/ast/function.go +++ b/ast/function.go @@ -14,7 +14,7 @@ type Function struct { } func (fl *Function) expressionNode() {} -func (fl *Function) TokenLiteral() string { return string(fl.Token.Literal) } +func (fl *Function) TokenLiteral() string { return fl.Token.Literal } func (fl *Function) String() string { var out bytes.Buffer params := make([]string, len(fl.Parameters)) diff --git a/ast/function_literal.go b/ast/function_literal.go index e3d537a..91eb53a 100644 --- a/ast/function_literal.go +++ b/ast/function_literal.go @@ -13,7 +13,7 @@ type FunctionLiteral struct { } func (fl *FunctionLiteral) expressionNode() {} -func (fl *FunctionLiteral) TokenLiteral() string { return string(fl.Token.Literal) } +func (fl *FunctionLiteral) TokenLiteral() string { return fl.Token.Literal } func (fl *FunctionLiteral) String() string { var out bytes.Buffer params := make([]string, len(fl.Parameters)) diff --git a/ast/function_literal_test.go b/ast/function_literal_test.go index 4abfa59..4344682 100644 --- a/ast/function_literal_test.go +++ b/ast/function_literal_test.go @@ -30,7 +30,7 @@ func TestFunctionLiteral_String(t *testing.T) { } blockStatement := &BlockStatement{Token: token.Token{Type: token.LBRACE, Literal: "{"}, Statements: stmts} - argumentsIdentifier := []*Identifier{} + var argumentsIdentifier []*Identifier for _, arg := range tt.parameters { integerLiteral := &Identifier{Token: token.Token{Type: token.INT, Literal: strconv.FormatInt(arg, 10)}, Value: strconv.FormatInt(arg, 10)} argumentsIdentifier = append(argumentsIdentifier, integerLiteral) diff --git a/ast/hash_literal.go b/ast/hash_literal.go index 4651563..02d0166 100644 --- a/ast/hash_literal.go +++ b/ast/hash_literal.go @@ -12,7 +12,7 @@ type HashLiteral struct { } func (hl *HashLiteral) expressionNode() {} -func (hl *HashLiteral) TokenLiteral() string { return string(hl.Token.Literal) } +func (hl *HashLiteral) TokenLiteral() string { return hl.Token.Literal } func (hl *HashLiteral) String() string { var out bytes.Buffer pairs := make([]string, len(hl.Pairs)) diff --git a/ast/identifier.go b/ast/identifier.go index 306852f..8963637 100644 --- a/ast/identifier.go +++ b/ast/identifier.go @@ -8,7 +8,7 @@ type Identifier struct { } func (i *Identifier) expressionNode() {} -func (i *Identifier) TokenLiteral() string { return string(i.Token.Literal) } +func (i *Identifier) TokenLiteral() string { return i.Token.Literal } func (i *Identifier) String() string { return i.Value } diff --git a/ast/if_expression.go b/ast/if_expression.go index 0a3c847..d583254 100644 --- a/ast/if_expression.go +++ b/ast/if_expression.go @@ -13,7 +13,7 @@ type IfExpression struct { } func (ie *IfExpression) expressionNode() {} -func (ie *IfExpression) TokenLiteral() string { return string(ie.Token.Literal) } +func (ie *IfExpression) TokenLiteral() string { return ie.Token.Literal } func (ie *IfExpression) String() string { var out bytes.Buffer out.WriteString("if") diff --git a/ast/import.go b/ast/import.go index c71eb37..26bef05 100644 --- a/ast/import.go +++ b/ast/import.go @@ -12,7 +12,7 @@ type Import struct { } func (i *Import) expressionNode() {} -func (i *Import) TokenLiteral() string { return string(i.Token.Literal) } +func (i *Import) TokenLiteral() string { return i.Token.Literal } func (i *Import) String() string { var out bytes.Buffer diff --git a/ast/index_expression.go b/ast/index_expression.go index f5fe733..105163e 100644 --- a/ast/index_expression.go +++ b/ast/index_expression.go @@ -12,7 +12,7 @@ type IndexExpression struct { } func (ie *IndexExpression) expressionNode() {} -func (ie *IndexExpression) TokenLiteral() string { return string(ie.Token.Literal) } +func (ie *IndexExpression) TokenLiteral() string { return ie.Token.Literal } func (ie *IndexExpression) String() string { var out bytes.Buffer out.WriteString("(") diff --git a/ast/infix_expression.go b/ast/infix_expression.go index 2f7fff3..3ce50e1 100644 --- a/ast/infix_expression.go +++ b/ast/infix_expression.go @@ -13,7 +13,7 @@ type InfixExpression struct { } func (oe *InfixExpression) expressionNode() {} -func (oe *InfixExpression) TokenLiteral() string { return string(oe.Token.Literal) } +func (oe *InfixExpression) TokenLiteral() string { return oe.Token.Literal } func (oe *InfixExpression) String() string { var out bytes.Buffer diff --git a/ast/integer_literal.go b/ast/integer_literal.go index 2950cc4..a26e8ab 100644 --- a/ast/integer_literal.go +++ b/ast/integer_literal.go @@ -8,7 +8,7 @@ type IntegerLiteral struct { } func (il *IntegerLiteral) expressionNode() {} -func (il *IntegerLiteral) TokenLiteral() string { return string(il.Token.Literal) } +func (il *IntegerLiteral) TokenLiteral() string { return il.Token.Literal } func (il *IntegerLiteral) String() string { return il.TokenLiteral() } diff --git a/ast/object_call.go b/ast/object_call.go index 0655775..086cb8d 100644 --- a/ast/object_call.go +++ b/ast/object_call.go @@ -12,7 +12,7 @@ type ObjectCall struct { } func (oc *ObjectCall) expressionNode() {} -func (oc *ObjectCall) TokenLiteral() string { return string(oc.Token.Literal) } +func (oc *ObjectCall) TokenLiteral() string { return oc.Token.Literal } func (oc *ObjectCall) String() string { var out bytes.Buffer out.WriteString("(") diff --git a/ast/posfix_expression.go b/ast/posfix_expression.go index c728939..20abd85 100644 --- a/ast/posfix_expression.go +++ b/ast/posfix_expression.go @@ -12,7 +12,7 @@ type PostfixExpression struct { } func (pe *PostfixExpression) expressionNode() {} -func (pe *PostfixExpression) TokenLiteral() string { return string(pe.Token.Literal) } +func (pe *PostfixExpression) TokenLiteral() string { return pe.Token.Literal } func (pe *PostfixExpression) String() string { var out bytes.Buffer diff --git a/ast/prefix_expression.go b/ast/prefix_expression.go index 393607f..239b109 100644 --- a/ast/prefix_expression.go +++ b/ast/prefix_expression.go @@ -12,7 +12,7 @@ type PrefixExpression struct { } func (pe *PrefixExpression) expressionNode() {} -func (pe *PrefixExpression) TokenLiteral() string { return string(pe.Token.Literal) } +func (pe *PrefixExpression) TokenLiteral() string { return pe.Token.Literal } func (pe *PrefixExpression) String() string { var out bytes.Buffer diff --git a/ast/return_statement.go b/ast/return_statement.go index e1a53be..ca127e9 100644 --- a/ast/return_statement.go +++ b/ast/return_statement.go @@ -11,7 +11,7 @@ type ReturnStatement struct { } func (rs *ReturnStatement) statementNode() {} -func (rs *ReturnStatement) TokenLiteral() string { return string(rs.Token.Literal) } +func (rs *ReturnStatement) TokenLiteral() string { return rs.Token.Literal } func (rs *ReturnStatement) String() string { var out bytes.Buffer out.WriteString(rs.TokenLiteral() + " ") diff --git a/ast/string.go b/ast/string.go index 93269cd..fd1834c 100644 --- a/ast/string.go +++ b/ast/string.go @@ -8,7 +8,7 @@ type StringLiteral struct { } func (il *StringLiteral) expressionNode() {} -func (il *StringLiteral) TokenLiteral() string { return string(il.Token.Literal) } +func (il *StringLiteral) TokenLiteral() string { return il.Token.Literal } func (il *StringLiteral) String() string { return il.TokenLiteral() } diff --git a/ast/var_statement.go b/ast/var_statement.go index f978c54..fc6bdf0 100644 --- a/ast/var_statement.go +++ b/ast/var_statement.go @@ -12,7 +12,7 @@ type VarStatement struct { } func (ls *VarStatement) statementNode() {} -func (ls *VarStatement) TokenLiteral() string { return string(ls.Token.Literal) } +func (ls *VarStatement) TokenLiteral() string { return ls.Token.Literal } func (ls *VarStatement) String() string { var out bytes.Buffer out.WriteString(ls.TokenLiteral() + " ") @@ -35,7 +35,7 @@ type AssignStatement struct { func (ls *AssignStatement) expressionNode() {} func (ls *AssignStatement) statementNode() {} -func (ls *AssignStatement) TokenLiteral() string { return string(ls.Token.Literal) } +func (ls *AssignStatement) TokenLiteral() string { return ls.Token.Literal } func (ls *AssignStatement) String() string { var out bytes.Buffer out.WriteString(ls.Name.String()) diff --git a/evaluator/evaluator.go b/evaluator/evaluator.go index 63e918e..66a65c7 100644 --- a/evaluator/evaluator.go +++ b/evaluator/evaluator.go @@ -8,7 +8,6 @@ import ( func Eval(node ast.Node, env *object.Environment) object.Object { switch node := node.(type) { - // Ast Program Eval case *ast.Program: return evalProgram(node.Statements, env) @@ -153,6 +152,7 @@ func Eval(node ast.Node, env *object.Environment) object.Object { case *ast.ScopeOperatorExpression: return evalScopeOperatorExpression(node, env) } + return nil } diff --git a/evaluator/function_test.go b/evaluator/function_test.go index 4b693a3..0a43bef 100644 --- a/evaluator/function_test.go +++ b/evaluator/function_test.go @@ -110,10 +110,10 @@ func TestCallFunction(t *testing.T) { "function add(a, b) { return function test(x, y) { return a + b + x + y }; } add(10, 10)(10, 10);", 40, }, - //{ - // "var a = 0; function add() { return function increment() { a++; return a; }}; var b = add()(); add()();", - // 2, - // }, + /*{ + "var a = 0; function add() { return function increment() { a++; return a; }}; var b = add()(); add()();", + 2, + },*/ } for i, tt := range tests { diff --git a/object/environment.go b/object/environment.go index 6f31070..67e4fdb 100644 --- a/object/environment.go +++ b/object/environment.go @@ -28,3 +28,16 @@ func (e *Environment) Set(name string, val Object) Object { e.store[name] = val return val } + +/* +func (e *Environment) Set(name string, val Object) Object { + _, ok := e.store[name] + if !ok && e.outer != nil { + e.outer.Set(name, val) + } else { + e.store[name] = val + } + + return val +} +*/ diff --git a/object/object.go b/object/object.go index 0853bd7..7fc7778 100644 --- a/object/object.go +++ b/object/object.go @@ -23,6 +23,7 @@ type Cloneable interface { // Comparable is the interface for comparing two Object and their underlying // values. It is the responsibility of the caller (left) to check for types. +// E.g.: 1 > 1, it will return -1. left isn't greater than 1. type Comparable interface { Compare(right Object) int8 } @@ -59,6 +60,10 @@ func IsError(o Object) bool { } func IsTruthy(o Object) bool { + if o == nil { + return false + } + switch o.Type() { case NULL_OBJ: return false diff --git a/object/object_test.go b/object/object_test.go index c3637d6..3959d50 100644 --- a/object/object_test.go +++ b/object/object_test.go @@ -1,6 +1,9 @@ package object -import "testing" +import ( + "fmt" + "testing" +) func TestStringHashKey(t *testing.T) { hello1 := &String{Value: "Hello World"} @@ -20,6 +23,72 @@ func TestStringHashKey(t *testing.T) { } } +func TestHahsableString(t *testing.T) { + s := &String{Value: "Hello World"} + i := &Integer{Value: 1} + f := &Float{Value: 1} + + tests := []struct { + oLeft Object + oRight Object + isEqual bool + }{ + { + &String{Value: "Hello World"}, + &String{Value: "Hello World"}, + false, + }, + { + s, + s, + true, + }, + { + &Integer{Value: 1}, + &Integer{Value: 1}, + false, + }, + { + i, + i, + true, + }, + { + &Float{Value: 1}, + &Float{Value: 1}, + false, + }, + { + f, + f, + true, + }, + { + &Boolean{Value: true}, + &Boolean{Value: true}, + false, + }, + { + TRUE, + TRUE, + true, + }, + } + + for i, tt := range tests { + t.Run(fmt.Sprintf("TestHahsableString[%d]", i), func(t *testing.T) { + hashLeft, _ := tt.oLeft.(Hashable) + hashRight, _ := tt.oRight.(Hashable) + equal := hashLeft == hashRight + + if equal != tt.isEqual { + t.Fatalf("Object %s and %s are %v. Expected %v", tt.oLeft.Inspect(), tt.oRight.Inspect(), equal, tt.isEqual) + } + + }) + } +} + func TestIsTruthy(t *testing.T) { tests := []struct { obj Object @@ -77,6 +146,10 @@ func TestIsTruthy(t *testing.T) { &Boolean{Value: false}, false, }, + { + nil, + false, + }, } for _, tt := range tests { diff --git a/parser/assign.go b/parser/assign.go index 65d646e..0198d86 100644 --- a/parser/assign.go +++ b/parser/assign.go @@ -16,7 +16,7 @@ func (p *Parser) parseVarStatement() *ast.VarStatement { return nil } - stmt.Name = &ast.Identifier{Token: p.curToken, Value: string(p.curToken.Literal)} + stmt.Name = &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} if !p.expectPeek(token.ASSIGN) { return nil @@ -41,7 +41,7 @@ func (p *Parser) parseVarStatement() *ast.VarStatement { func (p *Parser) parseAssignStatement() *ast.AssignStatement { stmt := &ast.AssignStatement{Token: p.curToken} - stmt.Name = &ast.Identifier{Token: p.curToken, Value: string(p.curToken.Literal)} + stmt.Name = &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} if !p.expectPeek(token.ASSIGN) { return nil diff --git a/parser/expression.go b/parser/expression.go index 5e53ffe..d45b39f 100644 --- a/parser/expression.go +++ b/parser/expression.go @@ -22,7 +22,7 @@ func (p *Parser) parseExpression(precedence int) ast.Expression { return nil } - leftExp := prefix.fn() + leftExp := prefix() for precedence < p.peekPrecedence() { infix, ok := p.infixParseFns[p.peekToken.Type] @@ -30,7 +30,7 @@ func (p *Parser) parseExpression(precedence int) ast.Expression { return leftExp } p.nextToken() - leftExp = infix.fn(leftExp) + leftExp = infix(leftExp) } return leftExp diff --git a/parser/identifier.go b/parser/identifier.go index b536e93..2e36cb0 100644 --- a/parser/identifier.go +++ b/parser/identifier.go @@ -8,8 +8,8 @@ import ( func (p *Parser) parseIdentifier() ast.Expression { if p.peekTokenIs(token.ASSIGN) { p.peekError(token.IDENT) - return &ast.Identifier{Token: p.curToken, Value: string(p.curToken.Literal)} + return &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} } - return &ast.Identifier{Token: p.curToken, Value: string(p.curToken.Literal)} + return &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} } diff --git a/parser/parser.go b/parser/parser.go index 29a58ca..ae47973 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -47,8 +47,11 @@ type Parser struct { curToken token.Token peekToken token.Token - prefixParseFns map[token.TokenType]fnPrefixPrecedence - infixParseFns map[token.TokenType]fnInfixPrecedence + prefixParseFns map[token.TokenType]prefixParseFn + infixParseFns map[token.TokenType]infixParseFn + + prefixParsePrecedence map[token.TokenType]int + infixParsePrecedence map[token.TokenType]int } // associativity if 1 then is right, 0, mean left. @@ -62,7 +65,8 @@ func New(l *lexer.Lexer) *Parser { errors: []string{}, } - p.prefixParseFns = make(map[token.TokenType]fnPrefixPrecedence) + p.prefixParseFns = make(map[token.TokenType]prefixParseFn) + p.prefixParsePrecedence = make(map[token.TokenType]int) p.registerPrefix(token.IDENT, p.parseIdentifier, LOWEST) p.registerPrefix(token.INT, p.parseIntegerLiteral, LOWEST) p.registerPrefix(token.FLOAT, p.parseFloatLiteral, LOWEST) @@ -82,7 +86,8 @@ func New(l *lexer.Lexer) *Parser { p.registerPrefix(token.IMPORT, p.parseImport, LOWEST) // p.registerPrefix(token.BIT_NOT, p.parsePrefixExpression) - p.infixParseFns = make(map[token.TokenType]fnInfixPrecedence) + p.infixParseFns = make(map[token.TokenType]infixParseFn) + p.infixParsePrecedence = make(map[token.TokenType]int) p.registerInfix(token.ASSIGN, p.parseInfixAssignExpression, ASSIGN) p.registerInfix(token.PLUS, p.parseInfixExpression, SUM) p.registerInfix(token.MINUS, p.parseInfixExpression, SUM) @@ -148,19 +153,13 @@ func (p *Parser) noPrefixParseFnError(t token.TokenType) { } func (p *Parser) registerPrefix(tokenType token.TokenType, fn prefixParseFn, precedence int) { - st := fnPrefixPrecedence{ - fn: fn, - precedence: precedence, - } - p.prefixParseFns[tokenType] = st + p.prefixParseFns[tokenType] = fn + p.prefixParsePrecedence[tokenType] = precedence } func (p *Parser) registerInfix(tokenType token.TokenType, fn infixParseFn, precedence int) { - st := fnInfixPrecedence{ - fn: fn, - precedence: precedence, - } - p.infixParseFns[tokenType] = st + p.infixParseFns[tokenType] = fn + p.infixParsePrecedence[tokenType] = precedence } func (p *Parser) curTokenIs(tok token.TokenType) bool { @@ -205,12 +204,12 @@ func (p *Parser) peekError(t ...token.TokenType) { } func (p *Parser) peekPrecedence() int { - ps, ok := p.infixParseFns[p.peekToken.Type] + precedence, ok := p.infixParsePrecedence[p.peekToken.Type] if !ok { return LOWEST } - return ps.precedence + return precedence } // curAssociativity 0 means left associativity @@ -223,12 +222,12 @@ func (p *Parser) curAssociativity() int { } func (p *Parser) curPrecedence() int { - ps, ok := p.infixParseFns[p.curToken.Type] + precedence, ok := p.infixParsePrecedence[p.curToken.Type] if !ok { return LOWEST } - return ps.precedence + return precedence } func (p *Parser) nextToken() { diff --git a/semantic/semantic.go b/semantic/semantic.go new file mode 100644 index 0000000..fe32b0b --- /dev/null +++ b/semantic/semantic.go @@ -0,0 +1,111 @@ +package semantic + +import ( + "fmt" + "ninja/ast" + "ninja/parser" +) + +// Semantic here is where we are going doing some semantic analyze +// using visitor pattern +type Semantic struct { + p *parser.Parser + scopeStack Stack + errors []string +} + +func New(p *parser.Parser) *Semantic { + return &Semantic{p: p} +} + +func (s *Semantic) Errors() []string { + return s.errors +} + +func (s *Semantic) NewError(format string, a ...interface{}) { + s.errors = append(s.errors, fmt.Sprintf(format, a...)) +} + +func (s *Semantic) Analyze() ast.Node { + return s.analyze(s.p.ParseProgram()) +} + +// beginScope record scope how deep is +func (s *Semantic) beginScope() { + s.scopeStack.Push(&Scope{}) +} + +// endScope remove one scope top of head of scope +func (s *Semantic) endScope() { + s.scopeStack.Pop() +} + +// declare will keep track of declare variables +func (s *Semantic) declare(name string) { + peek, ok := s.scopeStack.Peek() + if !ok { + return + } + + *peek = Scope{name: false} +} + +// resolve after a variable been resolve we mark it as resolved. +func (s *Semantic) resolve(name string) { + peek, ok := s.scopeStack.Peek() + if !ok { + return + } + + *peek = Scope{name: true} +} + +func (s *Semantic) expectIdentifierDeclare(name string) bool { + peek, ok := s.scopeStack.Peek() + if !ok { + s.NewError("Can't read local variable %s in its own initializer", name) + return false + } + + v, ok := (*peek)[name] + if !ok { + s.NewError("Identifier %s not exists on current scope", name) + return false + } + + if !v { + s.NewError("Can't read local variable %s in its own initializer", name) + return false + } + + return true +} + +func (s *Semantic) analyze(node ast.Node) ast.Node { + switch node := node.(type) { + case *ast.Program: + for _, v := range node.Statements { + s.analyze(v) + } + case *ast.ExpressionStatement: + s.analyze(node.Expression) + case *ast.Function: + s.declare(node.Name.Value) + s.resolve(node.Name.Value) + + s.analyze(node.Body) + case *ast.BlockStatement: + s.beginScope() + for _, stmt := range node.Statements { + s.analyze(stmt) + } + s.endScope() + case *ast.Identifier: + s.expectIdentifierDeclare(node.Value) + case *ast.VarStatement: + s.declare(node.Name.Value) + s.analyze(node.Value) + s.resolve(node.Name.Value) + } + return node +} diff --git a/semantic/semantic_test.go b/semantic/semantic_test.go new file mode 100644 index 0000000..d8ae783 --- /dev/null +++ b/semantic/semantic_test.go @@ -0,0 +1,73 @@ +package semantic + +import ( + "fmt" + "ninja/lexer" + "ninja/parser" + "strings" + "testing" +) + +func TestResolveVar(t *testing.T) { + tests := []struct { + input string + erroMessage interface{} + }{ + { + `var a = a;`, + "Can't read local variable a in its own initializer", + }, + } + + for i, tt := range tests { + t.Run(fmt.Sprintf("TestResolveVar[%d]", i), func(t *testing.T) { + s := testSemantic(tt.input, t) + + if len(s.Errors()) <= 0 { + t.Fatalf("Semantic Analyse didn't get any error") + } + + if s.Errors()[0] != tt.erroMessage { + t.Errorf("Semantic Analyse expected %s. Got: %s", tt.erroMessage, s.Errors()[0]) + } + }) + } +} + +// checkParserErrors check if there are parser errors +func checkParserErrors(t *testing.T, p *parser.Parser) { + errors := p.Errors() + if len(errors) == 0 { + return + } + t.Errorf("parser has %d errors", len(errors)) + for _, msg := range errors { + t.Errorf("parser error: %q", msg) + } + t.FailNow() +} + +// checkParserErrors check if there are parser errors +func checkSemanticErrors(t *testing.T, s *Semantic) { + errors := s.Errors() + if len(errors) == 0 { + return + } + t.Errorf("semantic analyzer has %d errors", len(errors)) + for _, msg := range errors { + t.Errorf("semantic analyzer : %q", msg) + } + t.FailNow() +} + +// testEval execute input code and check if there are parser error +// and return result object.Object +func testSemantic(input string, t *testing.T) *Semantic { + l := lexer.New(strings.NewReader(input)) + p := parser.New(l) + s := New(p) + s.Analyze() + + checkParserErrors(t, p) + return s +} diff --git a/semantic/stack.go b/semantic/stack.go new file mode 100644 index 0000000..73040fc --- /dev/null +++ b/semantic/stack.go @@ -0,0 +1,32 @@ +package semantic + +type Scope map[string]bool + +type Stack []*Scope + +func (s *Stack) IsEmpty() bool { + return len(*s) == 0 +} + +func (s *Stack) Push(i *Scope) { + *s = append(*s, i) +} + +func (s *Stack) Pop() (*Scope, bool) { + if s.IsEmpty() { + return nil, false + } + index := len(*s) - 1 + element := (*s)[index] + *s = (*s)[:index] + return element, true +} + +func (s *Stack) Peek() (*Scope, bool) { + if s.IsEmpty() { + return nil, false + } + index := len(*s) - 1 + scope := (*s)[index] + return scope, true +} diff --git a/semantic/stack_test.go b/semantic/stack_test.go new file mode 100644 index 0000000..f04f2d3 --- /dev/null +++ b/semantic/stack_test.go @@ -0,0 +1,80 @@ +package semantic + +import ( + "fmt" + "testing" +) + +func TestStack_IsEmpty(t *testing.T) { + tests := []struct { + st Stack + expected bool + }{ + { + Stack{}, + true, + }, + { + Stack{&Scope{"hello": false}}, + false, + }, + } + + for i, tt := range tests { + t.Run(fmt.Sprintf("TestStack_IsEmpty[%d]", i), func(t *testing.T) { + if tt.st.IsEmpty() != tt.expected { + t.Errorf("Stack expected to be empty %v. Got %v", tt.st.IsEmpty(), tt.expected) + } + }) + } +} + +func TestStack_Push(t *testing.T) { + + stack := &Stack{} + scope := Scope{"hello": false} + + stack.Push(&scope) + popScope, ok := stack.Pop() + + if !ok { + t.Fatalf("Unable to pop elements from Stack") + } + + if popScope != &scope { + t.Errorf("Popped scope isn't equal of scope created!") + } + + if !stack.IsEmpty() { + t.Errorf("Stack isn't empty") + } +} + +func TestStack_Peek(t *testing.T) { + + stack := &Stack{} + scope := Scope{"hello": false} + + stack.Push(&scope) + peekScope, ok := stack.Peek() + + if !ok { + t.Fatalf("Unable to peek scope") + } + + if stack.IsEmpty() { + t.Errorf("Stack can't be empty, we are only peek") + } + + if peekScope != &scope { + t.Errorf("Peeked scope isn't equal") + } + + *peekScope = Scope{"world": true} + + pooppedScope, _ := stack.Pop() + + if v := (*pooppedScope)["world"]; !v { + t.Errorf("Popped value wasn't change") + } +}