Skip to content

Commit 535808c

Browse files
committed
feat: Implement lexical scoping for variable resolution and assignment using pre-computed identifier distances and updated environment operations.
1 parent 9a2a7fb commit 535808c

7 files changed

Lines changed: 105 additions & 37 deletions

File tree

evaluator/evaluator.go

Lines changed: 31 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,15 @@ import (
1616
var moduleCache = map[string]*object.Module{}
1717

1818
type Evaluator struct {
19-
loopDepth int
19+
loopDepth int
20+
Resolutions map[ast.Node]int
2021
}
2122

2223
func (e *Evaluator) Eval(node ast.Node, env *object.Environment) object.Object {
2324
switch node := node.(type) {
2425
//statement
2526
case *ast.Program:
26-
return e.evalProgram(node, *env)
27+
return e.evalProgram(node, env)
2728
case *ast.ExpressionStatement:
2829
return e.Eval(node.Expression, env)
2930
case *ast.ReturnStatement:
@@ -138,11 +139,11 @@ func (e *Evaluator) Eval(node ast.Node, env *object.Environment) object.Object {
138139
}
139140
return evalInfixExpression(node, left, right)
140141
case *ast.BlockStatement:
141-
return e.evalBlockStatements(node, *env)
142+
return e.evalBlockStatements(node, env)
142143
case *ast.IfExpression:
143-
return e.evalIfExpression(node, *env)
144+
return e.evalIfExpression(node, env)
144145
case *ast.Identifier:
145-
return evalIdentifier(node, env)
146+
return e.evalIdentifier(node, env)
146147
case *ast.FunctionLiteral:
147148
params := node.Parameters
148149
body := node.Body
@@ -259,7 +260,7 @@ func (e *Evaluator) evalAssignment(node *ast.InfixExpression, env *object.Enviro
259260
if node.Operator == "=" {
260261
finalVal = val
261262
} else {
262-
currentVal := evalIdentifier(left, env)
263+
currentVal := e.evalIdentifier(left, env)
263264
if isError(currentVal) {
264265
return currentVal
265266
}
@@ -270,8 +271,11 @@ func (e *Evaluator) evalAssignment(node *ast.InfixExpression, env *object.Enviro
270271
return finalVal
271272
}
272273

273-
if isConst, ok := env.Consts[left.Value]; ok && isConst {
274-
return object.NewError(node.Line(), node.Column(), "cannot reassign to const: %s", left.Value)
274+
if dist, ok := e.Resolutions[left]; ok {
275+
if !env.UpdateAt(dist, left.Value, finalVal) {
276+
return object.NewError(node.Line(), node.Column(), "cannot reassign to const: %s", left.Value)
277+
}
278+
return finalVal
275279
}
276280

277281
_, updated := env.Update(left.Value, finalVal)
@@ -626,17 +630,23 @@ func (e *Evaluator) evalExpression(exps []ast.Expression, env *object.Environmen
626630
return result
627631
}
628632

629-
func evalIdentifier(node *ast.Identifier, env *object.Environment) object.Object {
633+
func (e *Evaluator) evalIdentifier(node *ast.Identifier, env *object.Environment) object.Object {
634+
if dist, ok := e.Resolutions[node]; ok {
635+
if val, ok := env.GetAt(dist, node.Value); ok {
636+
return val
637+
}
638+
}
639+
630640
if val, ok := env.Get(node.Value); ok {
631641
return val
632642
}
633643
return object.NewError(node.Line(), node.Column(), "identifier not found: %s", node.Value)
634644
}
635645

636-
func (e *Evaluator) evalProgram(program *ast.Program, env object.Environment) object.Object {
646+
func (e *Evaluator) evalProgram(program *ast.Program, env *object.Environment) object.Object {
637647
var result object.Object
638648
for _, statement := range program.Statements {
639-
result = e.Eval(statement, &env)
649+
result = e.Eval(statement, env)
640650

641651
switch result := result.(type) {
642652
case *object.ReturnValue:
@@ -649,15 +659,16 @@ func (e *Evaluator) evalProgram(program *ast.Program, env object.Environment) ob
649659
return result
650660
}
651661

652-
func (e *Evaluator) evalBlockStatements(block *ast.BlockStatement, env object.Environment) object.Object {
662+
func (e *Evaluator) evalBlockStatements(block *ast.BlockStatement, env *object.Environment) object.Object {
653663
var result object.Object
664+
blockEnv := object.NewEnclosedEnvironment(env)
654665

655666
for _, statement := range block.Statements {
656-
result = e.Eval(statement, &env)
667+
result = e.Eval(statement, blockEnv)
657668

658669
if result != nil {
659670
rt := result.Type()
660-
if rt == object.RETURN_VALUE_OBJ || rt == object.ERROR_OBJ {
671+
if rt == object.RETURN_VALUE_OBJ || rt == object.ERROR_OBJ || rt == object.BREAK_OBJ || rt == object.CONTINUE_OBJ {
661672
return result
662673
}
663674
}
@@ -666,29 +677,29 @@ func (e *Evaluator) evalBlockStatements(block *ast.BlockStatement, env object.En
666677
return result
667678
}
668679

669-
func (e *Evaluator) evalIfExpression(node *ast.IfExpression, env object.Environment) object.Object {
670-
condition := e.Eval(node.Condition, &env)
680+
func (e *Evaluator) evalIfExpression(node *ast.IfExpression, env *object.Environment) object.Object {
681+
condition := e.Eval(node.Condition, env)
671682

672683
if isError(condition) {
673684
return condition
674685
}
675686

676687
if isTruthy(condition) {
677-
return e.Eval(node.Consequence, &env)
688+
return e.Eval(node.Consequence, env)
678689
}
679690

680691
for _, v := range node.IfElse {
681-
condition := e.Eval(v.Condition, &env)
692+
condition := e.Eval(v.Condition, env)
682693
if isError(condition) {
683694
return condition
684695
}
685696
if isTruthy(condition) {
686-
return e.Eval(v.Consequence, &env)
697+
return e.Eval(v.Consequence, env)
687698
}
688699
}
689700

690701
if node.Alternative != nil {
691-
return e.Eval(node.Alternative, &env)
702+
return e.Eval(node.Alternative, env)
692703
}
693704

694705
return object.NULL

evaluator/evaluator_test.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"github.com/walonCode/code-lang/object"
88
"github.com/walonCode/code-lang/parser"
99
"github.com/walonCode/code-lang/std/general"
10+
"github.com/walonCode/code-lang/symbol"
1011
)
1112

1213
func testEval(input string) object.Object {
@@ -21,7 +22,15 @@ func testEval(input string) object.Object {
2122
env.Set(name, obj)
2223
}
2324

24-
evaluator := Evaluator{}
25+
builder := symbol.NewBuilder()
26+
// Pre-populate symbol table with builtins for tests
27+
for name := range genMod.Members {
28+
builder.Define(name, symbol.FUNCTION)
29+
}
30+
31+
builder.Visit(program)
32+
33+
evaluator := Evaluator{Resolutions: builder.Resolutions}
2534

2635
return evaluator.Eval(program, env)
2736
}

object/environment.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,32 @@ func (e *Environment) Update(name string, val Object) (Object, bool) {
5454
}
5555
return nil, false
5656
}
57+
58+
func (e *Environment) GetAt(distance int, name string) (Object, bool) {
59+
ancestor := e.ancestor(distance)
60+
if ancestor == nil {
61+
return nil, false
62+
}
63+
obj, ok := ancestor.Store[name]
64+
return obj, ok
65+
}
66+
67+
func (e *Environment) UpdateAt(distance int, name string, val Object) bool {
68+
ancestor := e.ancestor(distance)
69+
if ancestor == nil {
70+
return false
71+
}
72+
if isConst, ok := ancestor.Consts[name]; ok && isConst {
73+
return false
74+
}
75+
ancestor.Store[name] = val
76+
return true
77+
}
78+
79+
func (e *Environment) ancestor(distance int) *Environment {
80+
curr := e
81+
for i := 0; i < distance && curr != nil; i++ {
82+
curr = curr.outer
83+
}
84+
return curr
85+
}

object/object.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ const (
3232
TIME_OBJ = "TIME"
3333
STRUCT_TYPE = "STRUCT"
3434
STRUCT_INSTANCE = "STRUCT"
35-
BREAK_0BJ = "BREAK"
36-
CONTINUE_OBJ = "CONTINUE"
35+
BREAK_OBJ = "BREAK"
36+
CONTINUE_OBJ = "CONTINUE"
3737
)
3838

3939
// this allows us only to have on Bolean object and Null object
@@ -265,9 +265,11 @@ func (s *StructInstance) Type() ObjectType { return "STRUCT_INSTANCE" }
265265
func (s *StructInstance) Inspect() string { return s.TypeName }
266266

267267
type Break struct{}
268-
func (b *Break) Type() ObjectType { return "STRUCT_INSTANCE" }
268+
269+
func (b *Break) Type() ObjectType { return BREAK_OBJ }
269270
func (b *Break) Inspect() string { return "break" }
270271

271272
type Continue struct{}
272-
func (c *Continue) Type() ObjectType { return "STRUCT_INSTANCE" }
273-
func (c *Continue) Inspect() string { return "break" }
273+
274+
func (c *Continue) Type() ObjectType { return CONTINUE_OBJ }
275+
func (c *Continue) Inspect() string { return "continue" }

repl/repl.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ func Start(out io.Writer) {
7777
continue
7878
}
7979

80-
evaluator := &evaluator.Evaluator{}
80+
evaluator := &evaluator.Evaluator{Resolutions: builder.Resolutions}
8181

8282
evaluated := evaluator.Eval(programe, env)
8383
if evaluated != nil {
@@ -141,7 +141,7 @@ func Execute(source string, out io.Writer) {
141141
return
142142
}
143143

144-
evaluator := evaluator.Evaluator{}
144+
evaluator := evaluator.Evaluator{Resolutions: builder.Resolutions}
145145
evaluated := evaluator.Eval(program, env)
146146
if evaluated != nil && evaluated.Type() == object.ERROR_OBJ {
147147
io.WriteString(out, evaluated.Inspect())

symbol/builder.go

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@ import (
77
)
88

99
type Builder struct {
10-
Global *Scope
11-
Current *Scope
12-
Errors []string
10+
Global *Scope
11+
Current *Scope
12+
Errors []string
13+
Resolutions map[ast.Node]int
1314
}
1415

1516
func (b *Builder) error(line, col int, format string, args ...any) {
@@ -19,8 +20,9 @@ func (b *Builder) error(line, col int, format string, args ...any) {
1920
func NewBuilder() *Builder {
2021
global := NewScope("global", nil)
2122
return &Builder{
22-
Global: global,
23-
Current: global,
23+
Global: global,
24+
Current: global,
25+
Resolutions: make(map[ast.Node]int),
2426
}
2527
}
2628

@@ -144,8 +146,10 @@ func (b *Builder) VisitExpression(expr ast.Expression) {
144146
if e == nil {
145147
return
146148
}
147-
if sym := b.Resolve(e.Value); sym == nil {
149+
if _, distance := b.Current.ResolveWithDistance(e.Value); distance == -1 {
148150
b.error(e.Line(), e.Column(), "undefined identifier: %s", e.Value)
151+
} else {
152+
b.Resolutions[e] = distance
149153
}
150154
case *ast.IntegerLiteral, *ast.Boolean, *ast.StringLiteral, *ast.FloatLiteral, *ast.CharLiteral:
151155
// No symbols to define

symbol/scope.go

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
package symbol
22

33
type Scope struct {
4-
Name string
4+
Name string
55
Parent *Scope
66
Symbols map[string]*Symbol
77
}
88

99
func NewScope(name string, parent *Scope) *Scope {
1010
return &Scope{
11-
Name: name,
11+
Name: name,
1212
Parent: parent,
1313
Symbols: make(map[string]*Symbol),
1414
}
@@ -26,4 +26,17 @@ func (s *Scope) Resolve(name string) *Symbol {
2626
return s.Parent.Resolve(name)
2727
}
2828
return nil
29-
}
29+
}
30+
31+
func (s *Scope) ResolveWithDistance(name string) (*Symbol, int) {
32+
distance := 0
33+
curr := s
34+
for curr != nil {
35+
if sym, ok := curr.Symbols[name]; ok {
36+
return sym, distance
37+
}
38+
curr = curr.Parent
39+
distance++
40+
}
41+
return nil, -1
42+
}

0 commit comments

Comments
 (0)