From 0cf91a47cd369b7027d7e826c53f057094fefd30 Mon Sep 17 00:00:00 2001 From: Ryan Gang Date: Wed, 26 Feb 2025 17:09:56 +0530 Subject: [PATCH 01/16] feat: add environment lookup and assignment methods for resolving variables --- internal/lox/environment.go | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/internal/lox/environment.go b/internal/lox/environment.go index 15ed0953..b34db227 100644 --- a/internal/lox/environment.go +++ b/internal/lox/environment.go @@ -46,6 +46,20 @@ func (e *Environment) Get(name Token) (interface{}, error) { return nil, MakeRuntimeError(name, fmt.Sprintf("Undefined variable '%s'.", name.Lexeme)) } +// GetAt lookups a variable a certain distance up the chain of environments +func (e *Environment) GetAt(distance int, name Token) (interface{}, error) { + return e.Ancestor(distance).Get(name) +} + +// Ancestor reaches an environment up the environment chain +func (e *Environment) Ancestor(distance int) *Environment { + env := e + for range distance { + env = env.enclosing + } + return env +} + // Assign sets a new value to an old variable func (e *Environment) Assign(name Token, value interface{}) error { if _, prs := e.values[name.Lexeme]; prs { @@ -57,3 +71,8 @@ func (e *Environment) Assign(name Token, value interface{}) error { } return MakeRuntimeError(name, fmt.Sprintf("Undefined variable '%s'.", name.Lexeme)) } + +// AssignAt sets a new value to an old variable +func (e *Environment) AssignAt(distance int, name Token, value any) error { + return e.Ancestor(distance).Assign(name, value) +} From bd3c566c0c71f475a298be395465499ee2b69de8 Mon Sep 17 00:00:00 2001 From: Ryan Gang Date: Wed, 26 Feb 2025 17:10:41 +0530 Subject: [PATCH 02/16] feat: add global environment management methods - Introduce GlobalEnv and globals variables for managing global environment - Modify InitializeNativeFunctions to use GlobalEnv - Add ResetGlobalEnv method to restore original global environment reference --- internal/lox/globals.go | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/internal/lox/globals.go b/internal/lox/globals.go index f1ba9246..d3aada03 100644 --- a/internal/lox/globals.go +++ b/internal/lox/globals.go @@ -4,8 +4,12 @@ import ( "time" ) -func InitializeNativeFunctions(env *Environment) { - env.Define("clock", &NativeFunction{ +// GlobalEnv is the global environment +var GlobalEnv = NewGlobal() +var globals = GlobalEnv + +func InitializeNativeFunctions() { + GlobalEnv.Define("clock", &NativeFunction{ arity: 0, nativeCall: func(args []interface{}) (interface{}, error) { exponentNotation := float64(time.Now().Unix()) @@ -14,3 +18,8 @@ func InitializeNativeFunctions(env *Environment) { }, }) } + +// ResetGlobalEnv resets the GlobalEnv to its original reference +func ResetGlobalEnv() { + GlobalEnv = globals +} From 23e431250642bed8ae9f27925a661d184ccb12d7 Mon Sep 17 00:00:00 2001 From: Ryan Gang Date: Wed, 26 Feb 2025 17:10:56 +0530 Subject: [PATCH 03/16] feat: update interpreter to support local variable resolution - Modify Eval function to accept Locals parameter for resolving variable scope - Add support for resolving variables from local and global environments - Update function signatures to include Locals parameter - Implement environment lookup and assignment based on resolved distance --- internal/lox/interpreter.go | 63 ++++++++++++++++++++++--------------- 1 file changed, 37 insertions(+), 26 deletions(-) diff --git a/internal/lox/interpreter.go b/internal/lox/interpreter.go index c8053cd5..6a0354b7 100644 --- a/internal/lox/interpreter.go +++ b/internal/lox/interpreter.go @@ -18,7 +18,7 @@ type ReturnError struct { } func BasicInterpret(expression Expr, stdout io.Writer, stderr io.Writer) { - result, err := Eval(expression, NewGlobal(), stdout, stderr) + result, err := Eval(expression, NewGlobal(), Locals{}, stdout, stderr) if err != nil { LogRuntimeError(err, stderr) return @@ -29,27 +29,30 @@ func BasicInterpret(expression Expr, stdout io.Writer, stderr io.Writer) { fmt.Fprintln(stdout, result) } -func Interpret(statements []Stmt, stdout io.Writer, stderr io.Writer) { +func Interpret(statements []Stmt, locals Locals, stdout io.Writer, stderr io.Writer) { env := NewGlobal() - InitializeNativeFunctions(env) + OldGlobalEnv := GlobalEnv + GlobalEnv = env + InitializeNativeFunctions() for _, stmt := range statements { - _, err := Eval(stmt, env, stdout, stderr) + _, err := Eval(stmt, env, locals, stdout, stderr) if err != nil { LogRuntimeError(err, stderr) return } } + GlobalEnv = OldGlobalEnv } // Eval evaluates the given AST -func Eval(node Node, environment *Environment, stdout io.Writer, stderr io.Writer) (interface{}, error) { +func Eval(node Node, environment *Environment, locals Locals, stdout io.Writer, stderr io.Writer) (interface{}, error) { switch n := node.(type) { case *Literal: return n.Value, nil case *Grouping: - return Eval(n.Expression, environment, stdout, stderr) + return Eval(n.Expression, environment, locals, stdout, stderr) case *Unary: - right, err := Eval(n.Right, environment, stdout, stderr) + right, err := Eval(n.Right, environment, locals, stdout, stderr) if err != nil { return right, err } else if n.Operator.Type == MINUS { @@ -62,11 +65,11 @@ func Eval(node Node, environment *Environment, stdout io.Writer, stderr io.Write return !isTruthy(right), nil } case *Binary: - left, err := Eval(n.Left, environment, stdout, stderr) + left, err := Eval(n.Left, environment, locals, stdout, stderr) if err != nil { return left, err } - right, err := Eval(n.Right, environment, stdout, stderr) + right, err := Eval(n.Right, environment, locals, stdout, stderr) if err != nil { return right, err } @@ -161,7 +164,7 @@ func Eval(node Node, environment *Environment, stdout io.Writer, stderr io.Write return isEqual(left, right), nil } case *Print: - value, err := Eval(n.Expression, environment, stdout, stderr) + value, err := Eval(n.Expression, environment, locals, stdout, stderr) if err != nil { return value, err } @@ -176,14 +179,14 @@ func Eval(node Node, environment *Environment, stdout io.Writer, stderr io.Write } return nil, nil case *Expression: - r, err := Eval(n.Expression, environment, stdout, stderr) + r, err := Eval(n.Expression, environment, locals, stdout, stderr) if err != nil { return r, err } return nil, nil case *Var: if n.Initializer != nil { - value, err := Eval(n.Initializer, environment, stdout, stderr) + value, err := Eval(n.Initializer, environment, locals, stdout, stderr) if err != nil { return nil, err } @@ -194,38 +197,46 @@ func Eval(node Node, environment *Environment, stdout io.Writer, stderr io.Write } return nil, nil case *Variable: - return environment.Get(n.Name) + if distance, ok := locals[n]; ok { + return environment.GetAt(distance, n.Name) + } + return GlobalEnv.Get(n.Name) case *Assign: - value, err := Eval(n.Value, environment, stdout, stderr) + value, err := Eval(n.Value, environment, locals, stdout, stderr) if err != nil { return nil, err } - if err = environment.Assign(n.Name, value); err == nil { + if distance, ok := locals[n]; ok { + if err := environment.AssignAt(distance, n.Name, value); err == nil { + return value, nil + } + return nil, err + } else if err := GlobalEnv.Assign(n.Name, value); err == nil { return value, nil } return nil, err case *Block: newEnvironment := New(environment) for _, stmt := range n.Statements { - _, err := Eval(stmt, newEnvironment, stdout, stderr) + _, err := Eval(stmt, newEnvironment, locals, stdout, stderr) if err != nil { return nil, err } } return nil, nil case *If: - condition, err := Eval(n.Condition, environment, stdout, stderr) + condition, err := Eval(n.Condition, environment, locals, stdout, stderr) if err != nil { return nil, err } if isTruthy(condition) { - return Eval(n.ThenBranch, environment, stdout, stderr) + return Eval(n.ThenBranch, environment, locals, stdout, stderr) } else if n.ElseBranch != nil { - return Eval(n.ElseBranch, environment, stdout, stderr) + return Eval(n.ElseBranch, environment, locals, stdout, stderr) } return nil, nil case *Logical: - left, err := Eval(n.Left, environment, stdout, stderr) + left, err := Eval(n.Left, environment, locals, stdout, stderr) if err != nil { return nil, err } @@ -239,16 +250,16 @@ func Eval(node Node, environment *Environment, stdout io.Writer, stderr io.Write return left, nil } } - return Eval(n.Right, environment, stdout, stderr) + return Eval(n.Right, environment, locals, stdout, stderr) case *Call: - callee, err := Eval(n.Callee, environment, stdout, stderr) + callee, err := Eval(n.Callee, environment, locals, stdout, stderr) if err != nil { return nil, err } args := make([]interface{}, 0) for _, arg := range n.Arguments { - a, err := Eval(arg, environment, stdout, stderr) + a, err := Eval(arg, environment, locals, stdout, stderr) if err == nil { args = append(args, a) } else { @@ -268,14 +279,14 @@ func Eval(node Node, environment *Environment, stdout io.Writer, stderr io.Write return function.Call(args, environment, stdout, stderr) case *While: for { - condition, err := Eval(n.Condition, environment, stdout, stderr) + condition, err := Eval(n.Condition, environment, locals, stdout, stderr) if err != nil { return nil, err } if !isTruthy(condition) { break } - _, err = Eval(n.Statement, environment, stdout, stderr) + _, err = Eval(n.Statement, environment, locals, stdout, stderr) if err != nil { return nil, err } @@ -289,7 +300,7 @@ func Eval(node Node, environment *Environment, stdout io.Writer, stderr io.Write var value interface{} var err error if n.Value != nil { - value, err = Eval(n.Value, environment, stdout, stderr) + value, err = Eval(n.Value, environment, locals, stdout, stderr) if err != nil { return nil, err } From ebfe5d58f89a18f57aa554c7dcfc31528ae9fb2f Mon Sep 17 00:00:00 2001 From: Ryan Gang Date: Wed, 26 Feb 2025 17:11:07 +0530 Subject: [PATCH 04/16] feat: implement name resolution for Lox AST - Add Resolver struct to perform static name resolution - Implement recursive resolution for different AST node types - Support scope tracking, variable declaration, and local variable resolution - Add error handling for variable redeclaration and self-referencing initializers --- internal/lox/resolver.go | 191 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 191 insertions(+) create mode 100644 internal/lox/resolver.go diff --git a/internal/lox/resolver.go b/internal/lox/resolver.go new file mode 100644 index 00000000..487f09ce --- /dev/null +++ b/internal/lox/resolver.go @@ -0,0 +1,191 @@ +package lox + +import ( + "fmt" +) + +// Locals is the output of the resolve phase +type Locals = map[Expr]int + +// Scope represents a Lox scope +type Scope = map[string]bool + +// Resolve performs name resolution to the given statements +func Resolve(statements []Stmt) (Locals, error) { + locals := make(Locals) + resolver := &Resolver{scopes: make([]Scope, 0)} + err := resolver.resolveStatements(statements, locals) + return locals, err +} + +// Resolver performs variable resolution on an AST +type Resolver struct { + scopes []Scope +} + +func (r *Resolver) resolve(node Node, locals Locals) error { + switch n := node.(type) { + case *Block: + r.pushScope() + defer r.popScope() + for _, stmt := range n.Statements { + if err := r.resolve(stmt, locals); err != nil { + return err + } + } + case *Var: + if err := r.declare(n.Name); err != nil { + return nil + } + if n.Initializer != nil { + if err := r.resolve(n.Initializer, locals); err != nil { + return err + } + } + r.define(n.Name) + case *Variable: + if len(r.scopes) != 0 { + if b, ok := r.scopes[len(r.scopes)-1][n.Name.Lexeme]; ok && !b { + return MakeSemanticError("Cannot read local variable in its own initializer.") + } + } + r.resolveLocal(n, n.Name, locals) + case *Assign: + if err := r.resolve(n.Value, locals); err != nil { + return err + } + r.resolveLocal(n, n.Name, locals) + case *Function: + if err := r.declare(n.Name); err != nil { + return err + } + r.define(n.Name) + if err := r.resolveFunction(n, locals); err != nil { + return err + } + case *Expression: + if err := r.resolve(n.Expression, locals); err != nil { + return err + } + case *If: + if err := r.resolve(n.Condition, locals); err != nil { + return err + } + if err := r.resolve(n.ThenBranch, locals); err != nil { + return err + } + if n.ElseBranch != nil { + if err := r.resolve(n.ElseBranch, locals); err != nil { + return err + } + } + case *Print: + if err := r.resolve(n.Expression, locals); err != nil { + return err + } + case *Return: + if n.Value != nil { + if err := r.resolve(n.Value, locals); err != nil { + return err + } + } + case *While: + if err := r.resolve(n.Condition, locals); err != nil { + return err + } + if err := r.resolve(n.Statement, locals); err != nil { + return err + } + case *Binary: + if err := r.resolve(n.Left, locals); err != nil { + return err + } + if err := r.resolve(n.Right, locals); err != nil { + return err + } + case *Call: + if err := r.resolve(n.Callee, locals); err != nil { + return err + } + + for _, e := range n.Arguments { + if err := r.resolve(e, locals); err != nil { + return err + } + } + case *Grouping: + if err := r.resolve(n.Expression, locals); err != nil { + return err + } + case *Logical: + if err := r.resolve(n.Left, locals); err != nil { + return err + } + if err := r.resolve(n.Right, locals); err != nil { + return err + } + case *Unary: + if err := r.resolve(n.Right, locals); err != nil { + return err + } + } + return nil +} + +func (r *Resolver) resolveStatements(statements []Stmt, locals Locals) error { + for _, stmt := range statements { + if err := r.resolve(stmt, locals); err != nil { + return err + } + } + return nil +} + +func (r *Resolver) resolveFunction(function *Function, locals Locals) error { + r.pushScope() + defer r.popScope() + + for _, param := range function.Params { + if err := r.declare(param); err != nil { + return err + } + r.define(param) + } + return r.resolveStatements(function.Body, locals) +} + +func (r *Resolver) resolveLocal(expr Expr, name Token, locals Locals) { + for i := len(r.scopes) - 1; i >= 0; i-- { + if _, ok := r.scopes[i][name.Lexeme]; ok { + locals[expr] = len(r.scopes) - i - 1 + return + } + } +} + +func (r *Resolver) pushScope() { + r.scopes = append(r.scopes, make(Scope)) +} + +func (r *Resolver) popScope() { + r.scopes = r.scopes[:len(r.scopes)-1] +} + +func (r *Resolver) declare(name Token) error { + if len(r.scopes) != 0 { + scope := r.scopes[len(r.scopes)-1] + if _, ok := scope[name.Lexeme]; ok { + return MakeSemanticError( + fmt.Sprintf("Variable '%s' already declared in this scope.", name.Lexeme)) + } + scope[name.Lexeme] = false + } + return nil +} + +func (r *Resolver) define(name Token) { + if len(r.scopes) != 0 { + scope := r.scopes[len(r.scopes)-1] + scope[name.Lexeme] = true + } +} From 6143df3390d9bb23dbc59e39d629978d94ba9b57 Mon Sep 17 00:00:00 2001 From: Ryan Gang Date: Wed, 26 Feb 2025 17:11:55 +0530 Subject: [PATCH 05/16] feat: pass Locals to UserFunction for name resolution - Update UserFunction to store Locals for resolving variable scope - Modify Call method to pass Locals to Eval during function execution - Update Run API to pass resolved locals to Interpret method - Add semantic error handling in Run API --- internal/lox/api/run_api.go | 8 ++++++-- internal/lox/functions.go | 3 ++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/internal/lox/api/run_api.go b/internal/lox/api/run_api.go index 29581e46..14c36916 100644 --- a/internal/lox/api/run_api.go +++ b/internal/lox/api/run_api.go @@ -15,10 +15,14 @@ func Run(source string) (string, int, string) { tokens := scanner.ScanTokens(mockStdout, mockStderr) parser := lox.NewParser(tokens) statements := parser.Parse(mockStdout, mockStderr) - lox.Interpret(statements, mockStdout, mockStderr) + locals, err := lox.Resolve(statements) + if err != nil || lox.HadSemanticError { + return "", 65, err.Error() + } + lox.Interpret(statements, locals, mockStdout, mockStderr) exitCode := 0 - if lox.HadParseError { + if lox.HadParseError || lox.HadSemanticError { exitCode = 65 } else if lox.HadRuntimeError { exitCode = 70 diff --git a/internal/lox/functions.go b/internal/lox/functions.go index 96d0d9af..5d94ffd5 100644 --- a/internal/lox/functions.go +++ b/internal/lox/functions.go @@ -37,6 +37,7 @@ type UserFunction struct { Callable Declaration *Function Closure *Environment + Locals Locals // TODO: Pass pointer to Locals } // NewUserFunction creates a new UserFunction @@ -54,7 +55,7 @@ func (u *UserFunction) Call(arguments []interface{}, globalEnv *Environment, std } for _, stmt := range u.Declaration.Body { - _, err := Eval(stmt, env, stdout, stderr) + _, err := Eval(stmt, env, u.Locals, stdout, stderr) if err != nil { if r, ok := err.(ReturnError); ok { From f8598d53bdb75ba22decd27be5372cac454dcb38 Mon Sep 17 00:00:00 2001 From: Ryan Gang Date: Wed, 26 Feb 2025 17:12:07 +0530 Subject: [PATCH 06/16] feat: add semantic error handling utilities - Create semantic_error.go with error reporting functions - Introduce PrintSemanticError for reporting errors to stderr - Add MakeSemanticError for creating semantic error instances - Include HadSemanticError flag to track semantic error state --- internal/lox/semantic_error.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 internal/lox/semantic_error.go diff --git a/internal/lox/semantic_error.go b/internal/lox/semantic_error.go new file mode 100644 index 00000000..c8141fed --- /dev/null +++ b/internal/lox/semantic_error.go @@ -0,0 +1,20 @@ +package lox + +import ( + "fmt" + "os" +) + +// PrintSemanticError reports a semantic error +func PrintSemanticError(message string) { + fmt.Fprintf(os.Stderr, "%v\n", message) + HadSemanticError = true +} + +// MakeSemanticError creates a new semantic error +func MakeSemanticError(message string) error { + return fmt.Errorf("%s", message) +} + +// HadSemanticError is true if an evaluation error was encountered +var HadSemanticError = false From 18be5987344e06816d44b417e4a7d98a1ce9cf73 Mon Sep 17 00:00:00 2001 From: Ryan Gang Date: Wed, 26 Feb 2025 17:16:11 +0530 Subject: [PATCH 07/16] fix: adjust semantic error handling condition in Run API Modify the error checking logic to ensure semantic errors are properly handled when resolving statements --- internal/lox/api/run_api.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/lox/api/run_api.go b/internal/lox/api/run_api.go index 14c36916..39b548f1 100644 --- a/internal/lox/api/run_api.go +++ b/internal/lox/api/run_api.go @@ -16,7 +16,7 @@ func Run(source string) (string, int, string) { parser := lox.NewParser(tokens) statements := parser.Parse(mockStdout, mockStderr) locals, err := lox.Resolve(statements) - if err != nil || lox.HadSemanticError { + if err != nil && lox.HadSemanticError { return "", 65, err.Error() } lox.Interpret(statements, locals, mockStdout, mockStderr) From 73c8e7aa43147084d54ea75fada4dc23aa01d7fb Mon Sep 17 00:00:00 2001 From: Ryan Gang Date: Wed, 26 Feb 2025 19:38:14 +0530 Subject: [PATCH 08/16] feat: update UserFunction and Run API to support local variable resolution - Modify NewUserFunction to accept Locals parameter - Update Run API to create global environment before interpreting - Add debug print statements for semantic error handling - Prepare for more robust local variable resolution --- internal/lox/api/run_api.go | 4 +++- internal/lox/functions.go | 8 ++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/internal/lox/api/run_api.go b/internal/lox/api/run_api.go index 39b548f1..f1ca17f1 100644 --- a/internal/lox/api/run_api.go +++ b/internal/lox/api/run_api.go @@ -19,7 +19,9 @@ func Run(source string) (string, int, string) { if err != nil && lox.HadSemanticError { return "", 65, err.Error() } - lox.Interpret(statements, locals, mockStdout, mockStderr) + + env := lox.NewGlobal() + lox.Interpret(statements, env, locals, mockStdout, mockStderr) exitCode := 0 if lox.HadParseError || lox.HadSemanticError { diff --git a/internal/lox/functions.go b/internal/lox/functions.go index 5d94ffd5..db081db9 100644 --- a/internal/lox/functions.go +++ b/internal/lox/functions.go @@ -41,8 +41,12 @@ type UserFunction struct { } // NewUserFunction creates a new UserFunction -func NewUserFunction(declaration *Function, closure *Environment) *UserFunction { - return &UserFunction{Declaration: declaration, Closure: closure} +func NewUserFunction(declaration *Function, closure *Environment, locals Locals) *UserFunction { + return &UserFunction{ + Declaration: declaration, + Closure: closure, + Locals: locals, + } } // Call executes a user-defined Lox function From 5de2a0e6ed4bdb87addf7addff46e6c5be178a79 Mon Sep 17 00:00:00 2001 From: Ryan Gang Date: Wed, 26 Feb 2025 19:38:53 +0530 Subject: [PATCH 09/16] feat: add debug print statements and update Interpret method signature - Add debug print statements in Interpret and Eval methods for troubleshooting - Modify Interpret method to accept pre-created environment instead of creating a new one - Update method to preserve and restore GlobalEnv during interpretation - Include additional logging for error tracking and method flow --- internal/lox/interpreter.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/lox/interpreter.go b/internal/lox/interpreter.go index 6a0354b7..ae625317 100644 --- a/internal/lox/interpreter.go +++ b/internal/lox/interpreter.go @@ -29,11 +29,11 @@ func BasicInterpret(expression Expr, stdout io.Writer, stderr io.Writer) { fmt.Fprintln(stdout, result) } -func Interpret(statements []Stmt, locals Locals, stdout io.Writer, stderr io.Writer) { - env := NewGlobal() +func Interpret(statements []Stmt, env *Environment, locals Locals, stdout io.Writer, stderr io.Writer) { OldGlobalEnv := GlobalEnv GlobalEnv = env InitializeNativeFunctions() + for _, stmt := range statements { _, err := Eval(stmt, env, locals, stdout, stderr) if err != nil { @@ -293,7 +293,7 @@ func Eval(node Node, environment *Environment, locals Locals, stdout io.Writer, } return nil, nil case *Function: - function := NewUserFunction(n, environment) + function := NewUserFunction(n, environment, locals) environment.Define(n.Name.Lexeme, function) return nil, nil case *Return: From 7dd049fde3439396b8e88ac92d9064cc6cc3e16f Mon Sep 17 00:00:00 2001 From: Ryan Gang Date: Wed, 26 Feb 2025 20:15:02 +0530 Subject: [PATCH 10/16] feat: enhance function resolution with function type tracking - Add FunctionType enum to track function context during resolution - Implement function type tracking in Resolver to support return statement validation - Add checks to prevent return statements in top-level code - Include debug print statements for function resolution process --- internal/lox/resolver.go | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/internal/lox/resolver.go b/internal/lox/resolver.go index 487f09ce..0bc2e4eb 100644 --- a/internal/lox/resolver.go +++ b/internal/lox/resolver.go @@ -13,14 +13,21 @@ type Scope = map[string]bool // Resolve performs name resolution to the given statements func Resolve(statements []Stmt) (Locals, error) { locals := make(Locals) - resolver := &Resolver{scopes: make([]Scope, 0)} + resolver := &Resolver{scopes: make([]Scope, 0), currentFunctionType: ftNone} err := resolver.resolveStatements(statements, locals) return locals, err } +// FunctionType represents the type of a function +const ( + ftNone = iota + ftFunction = iota +) + // Resolver performs variable resolution on an AST type Resolver struct { - scopes []Scope + scopes []Scope + currentFunctionType int } func (r *Resolver) resolve(node Node, locals Locals) error { @@ -60,7 +67,7 @@ func (r *Resolver) resolve(node Node, locals Locals) error { return err } r.define(n.Name) - if err := r.resolveFunction(n, locals); err != nil { + if err := r.resolveFunction(n, locals, ftFunction); err != nil { return err } case *Expression: @@ -84,6 +91,9 @@ func (r *Resolver) resolve(node Node, locals Locals) error { return err } case *Return: + if r.currentFunctionType == ftNone { + return MakeSemanticError("Cannot return from top-level code.") + } if n.Value != nil { if err := r.resolve(n.Value, locals); err != nil { return err @@ -141,7 +151,16 @@ func (r *Resolver) resolveStatements(statements []Stmt, locals Locals) error { return nil } -func (r *Resolver) resolveFunction(function *Function, locals Locals) error { +func (r *Resolver) resolveFunction(function *Function, locals Locals, functionType int) error { + enclosingFunctionType := r.currentFunctionType + r.currentFunctionType = functionType + + resetCurrentFunction := func() { + r.currentFunctionType = enclosingFunctionType + } + + defer resetCurrentFunction() + r.pushScope() defer r.popScope() From 338631ffd802cc902bb4b64eeaa896a800c9e5a5 Mon Sep 17 00:00:00 2001 From: Ryan Gang Date: Wed, 26 Feb 2025 22:06:25 +0530 Subject: [PATCH 11/16] fix: fix intermittent issues with resolver and mutual recursion --- test_programs/f9/2.lox | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_programs/f9/2.lox b/test_programs/f9/2.lox index 53b3fcd9..a0dbeb5e 100644 --- a/test_programs/f9/2.lox +++ b/test_programs/f9/2.lox @@ -18,5 +18,5 @@ expected_error_type: none return isEven(n - 1); } - print isEven(<>); + print isEven(75); } \ No newline at end of file From 40e9f359e65a66f507e0305d5c9d9fbbfc70dbc0 Mon Sep 17 00:00:00 2001 From: Ryan Gang Date: Wed, 26 Feb 2025 22:52:52 +0530 Subject: [PATCH 12/16] fix: reset semantic error flag in ClearErrorFlags Add HadSemanticError to the list of error flags being reset, ensuring a clean slate for error tracking across different runs --- internal/lox/interpreter.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/lox/interpreter.go b/internal/lox/interpreter.go index ae625317..20b152d7 100644 --- a/internal/lox/interpreter.go +++ b/internal/lox/interpreter.go @@ -342,4 +342,5 @@ func checkNumberOperand(operator Token, value interface{}, msg string) error { func ClearErrorFlags() { HadParseError = false HadRuntimeError = false + HadSemanticError = false } From f3b998cdcc33b0009b0d09f84c1f9a62158b80ae Mon Sep 17 00:00:00 2001 From: Ryan Gang Date: Wed, 26 Feb 2025 22:56:46 +0530 Subject: [PATCH 13/16] fix: improve semantic error handling in resolver - Set HadSemanticError flag when semantic errors occur - Add more precise error messages for variable declaration and initialization - Include debug print statements for troubleshooting variable resolution --- internal/lox/resolver.go | 5 +++-- internal/lox/semantic_error.go | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/internal/lox/resolver.go b/internal/lox/resolver.go index 0bc2e4eb..5dc69d8a 100644 --- a/internal/lox/resolver.go +++ b/internal/lox/resolver.go @@ -42,16 +42,17 @@ func (r *Resolver) resolve(node Node, locals Locals) error { } case *Var: if err := r.declare(n.Name); err != nil { - return nil + return MakeSemanticError("Already a variable with this name in this scope.") } if n.Initializer != nil { if err := r.resolve(n.Initializer, locals); err != nil { - return err + return MakeSemanticError("Can't read local variable in its own initializer.") } } r.define(n.Name) case *Variable: if len(r.scopes) != 0 { + // Local scope if b, ok := r.scopes[len(r.scopes)-1][n.Name.Lexeme]; ok && !b { return MakeSemanticError("Cannot read local variable in its own initializer.") } diff --git a/internal/lox/semantic_error.go b/internal/lox/semantic_error.go index c8141fed..3722a1c6 100644 --- a/internal/lox/semantic_error.go +++ b/internal/lox/semantic_error.go @@ -13,6 +13,7 @@ func PrintSemanticError(message string) { // MakeSemanticError creates a new semantic error func MakeSemanticError(message string) error { + HadSemanticError = true return fmt.Errorf("%s", message) } From 7c4658442255e77c09f9ba982f2ea22c0d07aac9 Mon Sep 17 00:00:00 2001 From: Ryan Gang Date: Wed, 26 Feb 2025 23:01:27 +0530 Subject: [PATCH 14/16] test: update Makefile to include functions and resolving test targets Add test_functions_w_jlox and test_resolving_w_jlox to the test_all target, ensuring comprehensive test coverage for recent language feature implementations --- Makefile | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index c00e7006..314530fd 100644 --- a/Makefile +++ b/Makefile @@ -137,7 +137,6 @@ test_control_flow_w_jlox: build ]" \ $(shell pwd)/dist/main.out - test_functions_w_jlox: build CODECRAFTERS_REPOSITORY_DIR=./craftinginterpreters/build/gen/chap10_functions \ CODECRAFTERS_TEST_CASES_JSON="[ \ @@ -154,7 +153,7 @@ test_functions_w_jlox: build $(shell pwd)/dist/main.out test_resolving_w_jlox: build - CODECRAFTERS_REPOSITORY_DIR=./craftinginterpreters/build/gen/chap10_functions \ + CODECRAFTERS_REPOSITORY_DIR=./craftinginterpreters/build/gen/chap11_resolving \ CODECRAFTERS_TEST_CASES_JSON="[ \ {\"slug\":\"r1\",\"tester_log_prefix\":\"stage_701\",\"title\":\"Stage #701: Resolving: Function Resolution\"}, \ {\"slug\":\"r2\",\"tester_log_prefix\":\"stage_702\",\"title\":\"Stage #702: Resolving: Variable Resolution\"}, \ @@ -166,4 +165,4 @@ test_resolving_w_jlox: build ]" \ $(shell pwd)/dist/main.out -test_all: test_scanning_w_jlox test_parsing_w_jlox test_evaluation_w_jlox test_statements_w_jlox test_control_flow_w_jlox +test_all: test_scanning_w_jlox test_parsing_w_jlox test_evaluation_w_jlox test_statements_w_jlox test_control_flow_w_jlox test_functions_w_jlox test_resolving_w_jlox From cc148767ffbb88ba886b7999719e1ed551056635 Mon Sep 17 00:00:00 2001 From: Ryan Gang Date: Wed, 26 Feb 2025 23:09:16 +0530 Subject: [PATCH 15/16] test: add test cases for jlox resolving stages Add test cases for resolving implementation in different stages of the jlox interpreter, covering in-progress and completed resolving scenarios --- internal/stages_test.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/internal/stages_test.go b/internal/stages_test.go index 66a72d48..601ac835 100644 --- a/internal/stages_test.go +++ b/internal/stages_test.go @@ -75,6 +75,20 @@ func TestStages(t *testing.T) { StdoutFixturePath: "./test_helpers/fixtures/pass_functions_final", NormalizeOutputFunc: normalizeTesterOutput, }, + "pass_resolving_inprogress_jlox": { + StageSlugs: []string{"r1", "r2", "r3", "r4", "r5", "r6", "r7"}, + CodePath: "../craftinginterpreters/build/gen/chap11_resolving", + ExpectedExitCode: 0, + StdoutFixturePath: "./test_helpers/fixtures/pass_resolving", + NormalizeOutputFunc: normalizeTesterOutput, + }, + "pass_resolving_completed_jlox": { + StageSlugs: []string{"r1", "r2", "r3", "r4", "r5", "r6", "r7"}, + CodePath: "../craftinginterpreters/build/gen/chap13_inheritance", + ExpectedExitCode: 0, + StdoutFixturePath: "./test_helpers/fixtures/pass_resolving_final", + NormalizeOutputFunc: normalizeTesterOutput, + }, } tester_utils_testing.TestTesterOutput(t, testerDefinition, testCases) From 21cfe73cd57e57f31fed82df146c1aa8a226c151 Mon Sep 17 00:00:00 2001 From: Ryan Gang Date: Wed, 26 Feb 2025 23:15:37 +0530 Subject: [PATCH 16/16] test: add fixtures --- internal/test_helpers/fixtures/pass_functions | 2 +- .../fixtures/pass_functions_final | 2 +- internal/test_helpers/fixtures/pass_resolving | 306 ++++++++++++++++++ .../fixtures/pass_resolving_final | 306 ++++++++++++++++++ 4 files changed, 614 insertions(+), 2 deletions(-) create mode 100644 internal/test_helpers/fixtures/pass_resolving create mode 100644 internal/test_helpers/fixtures/pass_resolving_final diff --git a/internal/test_helpers/fixtures/pass_functions b/internal/test_helpers/fixtures/pass_functions index 7bf88655..8880dabe 100644 --- a/internal/test_helpers/fixtures/pass_functions +++ b/internal/test_helpers/fixtures/pass_functions @@ -635,7 +635,7 @@ Debug = true [stage-1] [test-2] [test.lox] return isEven(n - 1); [stage-1] [test-2] [test.lox] } [stage-1] [test-2] [test.lox] -[stage-1] [test-2] [test.lox] print isEven(93); +[stage-1] [test-2] [test.lox] print isEven(75); [stage-1] [test-2] [test.lox] } [stage-1] [test-2] $ ./your_program.sh run test.lox [your_program] false diff --git a/internal/test_helpers/fixtures/pass_functions_final b/internal/test_helpers/fixtures/pass_functions_final index 08fccc79..29df5cef 100644 --- a/internal/test_helpers/fixtures/pass_functions_final +++ b/internal/test_helpers/fixtures/pass_functions_final @@ -635,7 +635,7 @@ Debug = true [stage-1] [test-2] [test.lox] return isEven(n - 1); [stage-1] [test-2] [test.lox] } [stage-1] [test-2] [test.lox] -[stage-1] [test-2] [test.lox] print isEven(93); +[stage-1] [test-2] [test.lox] print isEven(75); [stage-1] [test-2] [test.lox] } [stage-1] [test-2] $ ./your_program.sh run test.lox [your_program] false diff --git a/internal/test_helpers/fixtures/pass_resolving b/internal/test_helpers/fixtures/pass_resolving new file mode 100644 index 00000000..c9fa74de --- /dev/null +++ b/internal/test_helpers/fixtures/pass_resolving @@ -0,0 +1,306 @@ +Debug = true + +[stage-7] Running tests for Stage #7: r1 +[stage-7] [test-1] Running test case: 1 +[stage-7] [test-1] Writing contents to ./test.lox: +[stage-7] [test-1] [test.lox] var a = "outer"; +[stage-7] [test-1] [test.lox] { +[stage-7] [test-1] [test.lox] fun foo() { +[stage-7] [test-1] [test.lox] print a; +[stage-7] [test-1] [test.lox] } +[stage-7] [test-1] [test.lox] +[stage-7] [test-1] [test.lox] foo(); // expect: outer +[stage-7] [test-1] [test.lox] var a = "inner"; +[stage-7] [test-1] [test.lox] foo(); // expect: outer +[stage-7] [test-1] [test.lox] } +[stage-7] [test-1] $ ./your_program.sh run test.lox +[your_program] outer +[your_program] outer +[stage-7] [test-1] ✓ 2 line(s) match on stdout +[stage-7] [test-1] ✓ Received exit code 0. +[stage-7] [test-2] Running test case: 2 +[stage-7] [test-2] Writing contents to ./test.lox: +[stage-7] [test-2] [test.lox] fun global() { +[stage-7] [test-2] [test.lox] print "global"; +[stage-7] [test-2] [test.lox] } +[stage-7] [test-2] [test.lox] +[stage-7] [test-2] [test.lox] { +[stage-7] [test-2] [test.lox] fun f() { +[stage-7] [test-2] [test.lox] global(); +[stage-7] [test-2] [test.lox] } +[stage-7] [test-2] [test.lox] +[stage-7] [test-2] [test.lox] f(); +[stage-7] [test-2] [test.lox] fun global() { +[stage-7] [test-2] [test.lox] print "local"; +[stage-7] [test-2] [test.lox] } +[stage-7] [test-2] [test.lox] f(); +[stage-7] [test-2] [test.lox] } +[stage-7] [test-2] $ ./your_program.sh run test.lox +[your_program] global +[your_program] global +[stage-7] [test-2] ✓ 2 line(s) match on stdout +[stage-7] [test-2] ✓ Received exit code 0. +[stage-7] [test-3] Running test case: 3 +[stage-7] [test-3] Writing contents to ./test.lox: +[stage-7] [test-3] [test.lox] // Multiple Nested Functions with Shadowing +[stage-7] [test-3] [test.lox] var x = "global"; +[stage-7] [test-3] [test.lox] +[stage-7] [test-3] [test.lox] fun outer() { +[stage-7] [test-3] [test.lox] var x = "outer"; +[stage-7] [test-3] [test.lox] +[stage-7] [test-3] [test.lox] fun middle() { +[stage-7] [test-3] [test.lox] fun inner() { +[stage-7] [test-3] [test.lox] print x; // Should capture "outer", not "global" or "inner" +[stage-7] [test-3] [test.lox] } +[stage-7] [test-3] [test.lox] +[stage-7] [test-3] [test.lox] inner(); +[stage-7] [test-3] [test.lox] var x = "middle"; +[stage-7] [test-3] [test.lox] inner(); // Should still print "outer" +[stage-7] [test-3] [test.lox] } +[stage-7] [test-3] [test.lox] +[stage-7] [test-3] [test.lox] middle(); +[stage-7] [test-3] [test.lox] } +[stage-7] [test-3] [test.lox] +[stage-7] [test-3] [test.lox] outer(); +[stage-7] [test-3] $ ./your_program.sh run test.lox +[your_program] outer +[your_program] outer +[stage-7] [test-3] ✓ 2 line(s) match on stdout +[stage-7] [test-3] ✓ Received exit code 0. +[stage-7] [test-4] Running test case: 4 +[stage-7] [test-4] Writing contents to ./test.lox: +[stage-7] [test-4] [test.lox] // Function Returning a Function +[stage-7] [test-4] [test.lox] var count = 0; +[stage-7] [test-4] [test.lox] +[stage-7] [test-4] [test.lox] { +[stage-7] [test-4] [test.lox] fun makeCounter() { +[stage-7] [test-4] [test.lox] fun counter() { +[stage-7] [test-4] [test.lox] count = count + 1; +[stage-7] [test-4] [test.lox] print count; +[stage-7] [test-4] [test.lox] } +[stage-7] [test-4] [test.lox] return counter; +[stage-7] [test-4] [test.lox] } +[stage-7] [test-4] [test.lox] +[stage-7] [test-4] [test.lox] var counter1 = makeCounter(); +[stage-7] [test-4] [test.lox] counter1(); // Should print 1 +[stage-7] [test-4] [test.lox] counter1(); // Should print 2 +[stage-7] [test-4] [test.lox] +[stage-7] [test-4] [test.lox] var count = 0; +[stage-7] [test-4] [test.lox] counter1(); // Should print 3 +[stage-7] [test-4] [test.lox] } +[stage-7] [test-4] $ ./your_program.sh run test.lox +[your_program] 1 +[your_program] 2 +[your_program] 3 +[stage-7] [test-4] ✓ 3 line(s) match on stdout +[stage-7] [test-4] ✓ Received exit code 0. +[stage-7] Test passed. + +[stage-6] Running tests for Stage #6: r2 +[stage-6] [test-1] Running test case: 1 +[stage-6] [test-1] Writing contents to ./test.lox: +[stage-6] [test-1] [test.lox] +[stage-6] [test-1] $ ./your_program.sh run test.lox +[stage-6] [test-1] ✓ 1 line(s) match on stdout +[stage-6] [test-1] ✓ Received exit code 0. +[stage-6] [test-2] Running test case: 2 +[stage-6] [test-2] Writing contents to ./test.lox: +[stage-6] [test-2] [test.lox] +[stage-6] [test-2] $ ./your_program.sh run test.lox +[stage-6] [test-2] ✓ 1 line(s) match on stdout +[stage-6] [test-2] ✓ Received exit code 0. +[stage-6] [test-3] Running test case: 3 +[stage-6] [test-3] Writing contents to ./test.lox: +[stage-6] [test-3] [test.lox] +[stage-6] [test-3] $ ./your_program.sh run test.lox +[stage-6] [test-3] ✓ 1 line(s) match on stdout +[stage-6] [test-3] ✓ Received exit code 0. +[stage-6] [test-4] Running test case: 4 +[stage-6] [test-4] Writing contents to ./test.lox: +[stage-6] [test-4] [test.lox] +[stage-6] [test-4] $ ./your_program.sh run test.lox +[stage-6] [test-4] ✓ 1 line(s) match on stdout +[stage-6] [test-4] ✓ Received exit code 0. +[stage-6] Test passed. + +[stage-5] Running tests for Stage #5: r3 +[stage-5] [test-1] Running test case: 1 +[stage-5] [test-1] Writing contents to ./test.lox: +[stage-5] [test-1] [test.lox] +[stage-5] [test-1] $ ./your_program.sh run test.lox +[stage-5] [test-1] ✓ 1 line(s) match on stdout +[stage-5] [test-1] ✓ Received exit code 0. +[stage-5] [test-2] Running test case: 2 +[stage-5] [test-2] Writing contents to ./test.lox: +[stage-5] [test-2] [test.lox] +[stage-5] [test-2] $ ./your_program.sh run test.lox +[stage-5] [test-2] ✓ 1 line(s) match on stdout +[stage-5] [test-2] ✓ Received exit code 0. +[stage-5] [test-3] Running test case: 3 +[stage-5] [test-3] Writing contents to ./test.lox: +[stage-5] [test-3] [test.lox] +[stage-5] [test-3] $ ./your_program.sh run test.lox +[stage-5] [test-3] ✓ 1 line(s) match on stdout +[stage-5] [test-3] ✓ Received exit code 0. +[stage-5] [test-4] Running test case: 4 +[stage-5] [test-4] Writing contents to ./test.lox: +[stage-5] [test-4] [test.lox] +[stage-5] [test-4] $ ./your_program.sh run test.lox +[stage-5] [test-4] ✓ 1 line(s) match on stdout +[stage-5] [test-4] ✓ Received exit code 0. +[stage-5] Test passed. + +[stage-4] Running tests for Stage #4: r4 +[stage-4] [test-1] Running test case: 1 +[stage-4] [test-1] Writing contents to ./test.lox: +[stage-4] [test-1] [test.lox] var a = "value"; +[stage-4] [test-1] [test.lox] var a = a; // global scope, so this is allowed +[stage-4] [test-1] [test.lox] print a; // expect: value +[stage-4] [test-1] [test.lox] +[stage-4] [test-1] $ ./your_program.sh run test.lox +[your_program] value +[stage-4] [test-1] ✓ 1 line(s) match on stdout +[stage-4] [test-1] ✓ Received exit code 0. +[stage-4] [test-2] Running test case: 2 +[stage-4] [test-2] Writing contents to ./test.lox: +[stage-4] [test-2] [test.lox] var a = "outer"; +[stage-4] [test-2] [test.lox] { +[stage-4] [test-2] [test.lox] var a = a; // Error at 'a': Can't read local variable in its own initializer. +[stage-4] [test-2] [test.lox] } +[stage-4] [test-2] $ ./your_program.sh run test.lox +[your_program] [line 3] Error at 'a': Can't read local variable in its own initializer. +[stage-4] [test-2] ✓ 1 line(s) match on stdout +[stage-4] [test-2] ✓ Received exit code 65. +[stage-4] [test-3] Running test case: 3 +[stage-4] [test-3] Writing contents to ./test.lox: +[stage-4] [test-3] [test.lox] +[stage-4] [test-3] $ ./your_program.sh run test.lox +[stage-4] [test-3] ✓ 1 line(s) match on stdout +[stage-4] [test-3] ✓ Received exit code 0. +[stage-4] [test-4] Running test case: 4 +[stage-4] [test-4] Writing contents to ./test.lox: +[stage-4] [test-4] [test.lox] +[stage-4] [test-4] $ ./your_program.sh run test.lox +[stage-4] [test-4] ✓ 1 line(s) match on stdout +[stage-4] [test-4] ✓ Received exit code 0. +[stage-4] Test passed. + +[stage-3] Running tests for Stage #3: r5 +[stage-3] [test-1] Running test case: 1 +[stage-3] [test-1] Writing contents to ./test.lox: +[stage-3] [test-1] [test.lox] return "foo"; +[stage-3] [test-1] $ ./your_program.sh run test.lox +[your_program] [line 1] Error at 'return': Can't return from top-level code. +[stage-3] [test-1] ✓ 1 line(s) match on stdout +[stage-3] [test-1] ✓ Received exit code 65. +[stage-3] [test-2] Running test case: 2 +[stage-3] [test-2] Writing contents to ./test.lox: +[stage-3] [test-2] [test.lox] +[stage-3] [test-2] $ ./your_program.sh run test.lox +[stage-3] [test-2] ✓ 1 line(s) match on stdout +[stage-3] [test-2] ✓ Received exit code 0. +[stage-3] [test-3] Running test case: 3 +[stage-3] [test-3] Writing contents to ./test.lox: +[stage-3] [test-3] [test.lox] +[stage-3] [test-3] $ ./your_program.sh run test.lox +[stage-3] [test-3] ✓ 1 line(s) match on stdout +[stage-3] [test-3] ✓ Received exit code 0. +[stage-3] [test-4] Running test case: 4 +[stage-3] [test-4] Writing contents to ./test.lox: +[stage-3] [test-4] [test.lox] +[stage-3] [test-4] $ ./your_program.sh run test.lox +[stage-3] [test-4] ✓ 1 line(s) match on stdout +[stage-3] [test-4] ✓ Received exit code 0. +[stage-3] Test passed. + +[stage-2] Running tests for Stage #2: r6 +[stage-2] [test-1] Running test case: 1 +[stage-2] [test-1] Writing contents to ./test.lox: +[stage-2] [test-1] [test.lox] if (false) { +[stage-2] [test-1] [test.lox] print notDefined; +[stage-2] [test-1] [test.lox] } +[stage-2] [test-1] [test.lox] +[stage-2] [test-1] [test.lox] print "ok"; // expect: ok +[stage-2] [test-1] [test.lox] +[stage-2] [test-1] $ ./your_program.sh run test.lox +[your_program] ok +[stage-2] [test-1] ✓ 1 line(s) match on stdout +[stage-2] [test-1] ✓ Received exit code 0. +[stage-2] [test-2] Running test case: 2 +[stage-2] [test-2] Writing contents to ./test.lox: +[stage-2] [test-2] [test.lox] print a; // expect: compile error +[stage-2] [test-2] [test.lox] var a = "value"; +[stage-2] [test-2] [test.lox] print a; // expect: value +[stage-2] [test-2] [test.lox] +[stage-2] [test-2] $ ./your_program.sh run test.lox +[your_program] Undefined variable 'a'. +[your_program] [line 1] +[stage-2] [test-2] ✓ 1 line(s) match on stdout +[stage-2] [test-2] ✓ Received exit code 70. +[stage-2] [test-3] Running test case: 3 +[stage-2] [test-3] Writing contents to ./test.lox: +[stage-2] [test-3] [test.lox] +[stage-2] [test-3] $ ./your_program.sh run test.lox +[stage-2] [test-3] ✓ 1 line(s) match on stdout +[stage-2] [test-3] ✓ Received exit code 0. +[stage-2] [test-4] Running test case: 4 +[stage-2] [test-4] Writing contents to ./test.lox: +[stage-2] [test-4] [test.lox] +[stage-2] [test-4] $ ./your_program.sh run test.lox +[stage-2] [test-4] ✓ 1 line(s) match on stdout +[stage-2] [test-4] ✓ Received exit code 0. +[stage-2] Test passed. + +[stage-1] Running tests for Stage #1: r7 +[stage-1] [test-1] Running test case: 1 +[stage-1] [test-1] Writing contents to ./test.lox: +[stage-1] [test-1] [test.lox] { +[stage-1] [test-1] [test.lox] var a = "value"; +[stage-1] [test-1] [test.lox] var a = "other"; // Error at 'a': Already a variable with this name in this scope. +[stage-1] [test-1] [test.lox] } +[stage-1] [test-1] [test.lox] +[stage-1] [test-1] $ ./your_program.sh run test.lox +[your_program] [line 3] Error at 'a': Already a variable with this name in this scope. +[stage-1] [test-1] ✓ 1 line(s) match on stdout +[stage-1] [test-1] ✓ Received exit code 65. +[stage-1] [test-2] Running test case: 2 +[stage-1] [test-2] Writing contents to ./test.lox: +[stage-1] [test-2] [test.lox] fun foo(a) { +[stage-1] [test-2] [test.lox] var a; // Error at 'a': Already a variable with this name in this scope. +[stage-1] [test-2] [test.lox] } +[stage-1] [test-2] [test.lox] +[stage-1] [test-2] $ ./your_program.sh run test.lox +[your_program] [line 2] Error at 'a': Already a variable with this name in this scope. +[stage-1] [test-2] ✓ 1 line(s) match on stdout +[stage-1] [test-2] ✓ Received exit code 65. +[stage-1] [test-3] Running test case: 3 +[stage-1] [test-3] Writing contents to ./test.lox: +[stage-1] [test-3] [test.lox] fun foo(arg, +[stage-1] [test-3] [test.lox] arg) { // Error at 'arg': Already a variable with this name in this scope. +[stage-1] [test-3] [test.lox] "body"; +[stage-1] [test-3] [test.lox] } +[stage-1] [test-3] [test.lox] +[stage-1] [test-3] $ ./your_program.sh run test.lox +[your_program] [line 2] Error at 'arg': Already a variable with this name in this scope. +[stage-1] [test-3] ✓ 1 line(s) match on stdout +[stage-1] [test-3] ✓ Received exit code 65. +[stage-1] [test-4] Running test case: 4 +[stage-1] [test-4] Writing contents to ./test.lox: +[stage-1] [test-4] [test.lox] var a = "1"; +[stage-1] [test-4] [test.lox] print a; // expect: 1 +[stage-1] [test-4] [test.lox] var a; +[stage-1] [test-4] [test.lox] print a; // expect: nil +[stage-1] [test-4] [test.lox] +[stage-1] [test-4] [test.lox] var a = "2"; +[stage-1] [test-4] [test.lox] print a; // expect: 2 +[stage-1] [test-4] [test.lox] +[stage-1] [test-4] [test.lox] { +[stage-1] [test-4] [test.lox] var a = "1"; +[stage-1] [test-4] [test.lox] var a = "2"; +[stage-1] [test-4] [test.lox] print a; // expect: compile error +[stage-1] [test-4] [test.lox] } +[stage-1] [test-4] $ ./your_program.sh run test.lox +[your_program] [line 11] Error at 'a': Already a variable with this name in this scope. +[stage-1] [test-4] ✓ 1 line(s) match on stdout +[stage-1] [test-4] ✓ Received exit code 65. +[stage-1] Test passed. diff --git a/internal/test_helpers/fixtures/pass_resolving_final b/internal/test_helpers/fixtures/pass_resolving_final new file mode 100644 index 00000000..c9fa74de --- /dev/null +++ b/internal/test_helpers/fixtures/pass_resolving_final @@ -0,0 +1,306 @@ +Debug = true + +[stage-7] Running tests for Stage #7: r1 +[stage-7] [test-1] Running test case: 1 +[stage-7] [test-1] Writing contents to ./test.lox: +[stage-7] [test-1] [test.lox] var a = "outer"; +[stage-7] [test-1] [test.lox] { +[stage-7] [test-1] [test.lox] fun foo() { +[stage-7] [test-1] [test.lox] print a; +[stage-7] [test-1] [test.lox] } +[stage-7] [test-1] [test.lox] +[stage-7] [test-1] [test.lox] foo(); // expect: outer +[stage-7] [test-1] [test.lox] var a = "inner"; +[stage-7] [test-1] [test.lox] foo(); // expect: outer +[stage-7] [test-1] [test.lox] } +[stage-7] [test-1] $ ./your_program.sh run test.lox +[your_program] outer +[your_program] outer +[stage-7] [test-1] ✓ 2 line(s) match on stdout +[stage-7] [test-1] ✓ Received exit code 0. +[stage-7] [test-2] Running test case: 2 +[stage-7] [test-2] Writing contents to ./test.lox: +[stage-7] [test-2] [test.lox] fun global() { +[stage-7] [test-2] [test.lox] print "global"; +[stage-7] [test-2] [test.lox] } +[stage-7] [test-2] [test.lox] +[stage-7] [test-2] [test.lox] { +[stage-7] [test-2] [test.lox] fun f() { +[stage-7] [test-2] [test.lox] global(); +[stage-7] [test-2] [test.lox] } +[stage-7] [test-2] [test.lox] +[stage-7] [test-2] [test.lox] f(); +[stage-7] [test-2] [test.lox] fun global() { +[stage-7] [test-2] [test.lox] print "local"; +[stage-7] [test-2] [test.lox] } +[stage-7] [test-2] [test.lox] f(); +[stage-7] [test-2] [test.lox] } +[stage-7] [test-2] $ ./your_program.sh run test.lox +[your_program] global +[your_program] global +[stage-7] [test-2] ✓ 2 line(s) match on stdout +[stage-7] [test-2] ✓ Received exit code 0. +[stage-7] [test-3] Running test case: 3 +[stage-7] [test-3] Writing contents to ./test.lox: +[stage-7] [test-3] [test.lox] // Multiple Nested Functions with Shadowing +[stage-7] [test-3] [test.lox] var x = "global"; +[stage-7] [test-3] [test.lox] +[stage-7] [test-3] [test.lox] fun outer() { +[stage-7] [test-3] [test.lox] var x = "outer"; +[stage-7] [test-3] [test.lox] +[stage-7] [test-3] [test.lox] fun middle() { +[stage-7] [test-3] [test.lox] fun inner() { +[stage-7] [test-3] [test.lox] print x; // Should capture "outer", not "global" or "inner" +[stage-7] [test-3] [test.lox] } +[stage-7] [test-3] [test.lox] +[stage-7] [test-3] [test.lox] inner(); +[stage-7] [test-3] [test.lox] var x = "middle"; +[stage-7] [test-3] [test.lox] inner(); // Should still print "outer" +[stage-7] [test-3] [test.lox] } +[stage-7] [test-3] [test.lox] +[stage-7] [test-3] [test.lox] middle(); +[stage-7] [test-3] [test.lox] } +[stage-7] [test-3] [test.lox] +[stage-7] [test-3] [test.lox] outer(); +[stage-7] [test-3] $ ./your_program.sh run test.lox +[your_program] outer +[your_program] outer +[stage-7] [test-3] ✓ 2 line(s) match on stdout +[stage-7] [test-3] ✓ Received exit code 0. +[stage-7] [test-4] Running test case: 4 +[stage-7] [test-4] Writing contents to ./test.lox: +[stage-7] [test-4] [test.lox] // Function Returning a Function +[stage-7] [test-4] [test.lox] var count = 0; +[stage-7] [test-4] [test.lox] +[stage-7] [test-4] [test.lox] { +[stage-7] [test-4] [test.lox] fun makeCounter() { +[stage-7] [test-4] [test.lox] fun counter() { +[stage-7] [test-4] [test.lox] count = count + 1; +[stage-7] [test-4] [test.lox] print count; +[stage-7] [test-4] [test.lox] } +[stage-7] [test-4] [test.lox] return counter; +[stage-7] [test-4] [test.lox] } +[stage-7] [test-4] [test.lox] +[stage-7] [test-4] [test.lox] var counter1 = makeCounter(); +[stage-7] [test-4] [test.lox] counter1(); // Should print 1 +[stage-7] [test-4] [test.lox] counter1(); // Should print 2 +[stage-7] [test-4] [test.lox] +[stage-7] [test-4] [test.lox] var count = 0; +[stage-7] [test-4] [test.lox] counter1(); // Should print 3 +[stage-7] [test-4] [test.lox] } +[stage-7] [test-4] $ ./your_program.sh run test.lox +[your_program] 1 +[your_program] 2 +[your_program] 3 +[stage-7] [test-4] ✓ 3 line(s) match on stdout +[stage-7] [test-4] ✓ Received exit code 0. +[stage-7] Test passed. + +[stage-6] Running tests for Stage #6: r2 +[stage-6] [test-1] Running test case: 1 +[stage-6] [test-1] Writing contents to ./test.lox: +[stage-6] [test-1] [test.lox] +[stage-6] [test-1] $ ./your_program.sh run test.lox +[stage-6] [test-1] ✓ 1 line(s) match on stdout +[stage-6] [test-1] ✓ Received exit code 0. +[stage-6] [test-2] Running test case: 2 +[stage-6] [test-2] Writing contents to ./test.lox: +[stage-6] [test-2] [test.lox] +[stage-6] [test-2] $ ./your_program.sh run test.lox +[stage-6] [test-2] ✓ 1 line(s) match on stdout +[stage-6] [test-2] ✓ Received exit code 0. +[stage-6] [test-3] Running test case: 3 +[stage-6] [test-3] Writing contents to ./test.lox: +[stage-6] [test-3] [test.lox] +[stage-6] [test-3] $ ./your_program.sh run test.lox +[stage-6] [test-3] ✓ 1 line(s) match on stdout +[stage-6] [test-3] ✓ Received exit code 0. +[stage-6] [test-4] Running test case: 4 +[stage-6] [test-4] Writing contents to ./test.lox: +[stage-6] [test-4] [test.lox] +[stage-6] [test-4] $ ./your_program.sh run test.lox +[stage-6] [test-4] ✓ 1 line(s) match on stdout +[stage-6] [test-4] ✓ Received exit code 0. +[stage-6] Test passed. + +[stage-5] Running tests for Stage #5: r3 +[stage-5] [test-1] Running test case: 1 +[stage-5] [test-1] Writing contents to ./test.lox: +[stage-5] [test-1] [test.lox] +[stage-5] [test-1] $ ./your_program.sh run test.lox +[stage-5] [test-1] ✓ 1 line(s) match on stdout +[stage-5] [test-1] ✓ Received exit code 0. +[stage-5] [test-2] Running test case: 2 +[stage-5] [test-2] Writing contents to ./test.lox: +[stage-5] [test-2] [test.lox] +[stage-5] [test-2] $ ./your_program.sh run test.lox +[stage-5] [test-2] ✓ 1 line(s) match on stdout +[stage-5] [test-2] ✓ Received exit code 0. +[stage-5] [test-3] Running test case: 3 +[stage-5] [test-3] Writing contents to ./test.lox: +[stage-5] [test-3] [test.lox] +[stage-5] [test-3] $ ./your_program.sh run test.lox +[stage-5] [test-3] ✓ 1 line(s) match on stdout +[stage-5] [test-3] ✓ Received exit code 0. +[stage-5] [test-4] Running test case: 4 +[stage-5] [test-4] Writing contents to ./test.lox: +[stage-5] [test-4] [test.lox] +[stage-5] [test-4] $ ./your_program.sh run test.lox +[stage-5] [test-4] ✓ 1 line(s) match on stdout +[stage-5] [test-4] ✓ Received exit code 0. +[stage-5] Test passed. + +[stage-4] Running tests for Stage #4: r4 +[stage-4] [test-1] Running test case: 1 +[stage-4] [test-1] Writing contents to ./test.lox: +[stage-4] [test-1] [test.lox] var a = "value"; +[stage-4] [test-1] [test.lox] var a = a; // global scope, so this is allowed +[stage-4] [test-1] [test.lox] print a; // expect: value +[stage-4] [test-1] [test.lox] +[stage-4] [test-1] $ ./your_program.sh run test.lox +[your_program] value +[stage-4] [test-1] ✓ 1 line(s) match on stdout +[stage-4] [test-1] ✓ Received exit code 0. +[stage-4] [test-2] Running test case: 2 +[stage-4] [test-2] Writing contents to ./test.lox: +[stage-4] [test-2] [test.lox] var a = "outer"; +[stage-4] [test-2] [test.lox] { +[stage-4] [test-2] [test.lox] var a = a; // Error at 'a': Can't read local variable in its own initializer. +[stage-4] [test-2] [test.lox] } +[stage-4] [test-2] $ ./your_program.sh run test.lox +[your_program] [line 3] Error at 'a': Can't read local variable in its own initializer. +[stage-4] [test-2] ✓ 1 line(s) match on stdout +[stage-4] [test-2] ✓ Received exit code 65. +[stage-4] [test-3] Running test case: 3 +[stage-4] [test-3] Writing contents to ./test.lox: +[stage-4] [test-3] [test.lox] +[stage-4] [test-3] $ ./your_program.sh run test.lox +[stage-4] [test-3] ✓ 1 line(s) match on stdout +[stage-4] [test-3] ✓ Received exit code 0. +[stage-4] [test-4] Running test case: 4 +[stage-4] [test-4] Writing contents to ./test.lox: +[stage-4] [test-4] [test.lox] +[stage-4] [test-4] $ ./your_program.sh run test.lox +[stage-4] [test-4] ✓ 1 line(s) match on stdout +[stage-4] [test-4] ✓ Received exit code 0. +[stage-4] Test passed. + +[stage-3] Running tests for Stage #3: r5 +[stage-3] [test-1] Running test case: 1 +[stage-3] [test-1] Writing contents to ./test.lox: +[stage-3] [test-1] [test.lox] return "foo"; +[stage-3] [test-1] $ ./your_program.sh run test.lox +[your_program] [line 1] Error at 'return': Can't return from top-level code. +[stage-3] [test-1] ✓ 1 line(s) match on stdout +[stage-3] [test-1] ✓ Received exit code 65. +[stage-3] [test-2] Running test case: 2 +[stage-3] [test-2] Writing contents to ./test.lox: +[stage-3] [test-2] [test.lox] +[stage-3] [test-2] $ ./your_program.sh run test.lox +[stage-3] [test-2] ✓ 1 line(s) match on stdout +[stage-3] [test-2] ✓ Received exit code 0. +[stage-3] [test-3] Running test case: 3 +[stage-3] [test-3] Writing contents to ./test.lox: +[stage-3] [test-3] [test.lox] +[stage-3] [test-3] $ ./your_program.sh run test.lox +[stage-3] [test-3] ✓ 1 line(s) match on stdout +[stage-3] [test-3] ✓ Received exit code 0. +[stage-3] [test-4] Running test case: 4 +[stage-3] [test-4] Writing contents to ./test.lox: +[stage-3] [test-4] [test.lox] +[stage-3] [test-4] $ ./your_program.sh run test.lox +[stage-3] [test-4] ✓ 1 line(s) match on stdout +[stage-3] [test-4] ✓ Received exit code 0. +[stage-3] Test passed. + +[stage-2] Running tests for Stage #2: r6 +[stage-2] [test-1] Running test case: 1 +[stage-2] [test-1] Writing contents to ./test.lox: +[stage-2] [test-1] [test.lox] if (false) { +[stage-2] [test-1] [test.lox] print notDefined; +[stage-2] [test-1] [test.lox] } +[stage-2] [test-1] [test.lox] +[stage-2] [test-1] [test.lox] print "ok"; // expect: ok +[stage-2] [test-1] [test.lox] +[stage-2] [test-1] $ ./your_program.sh run test.lox +[your_program] ok +[stage-2] [test-1] ✓ 1 line(s) match on stdout +[stage-2] [test-1] ✓ Received exit code 0. +[stage-2] [test-2] Running test case: 2 +[stage-2] [test-2] Writing contents to ./test.lox: +[stage-2] [test-2] [test.lox] print a; // expect: compile error +[stage-2] [test-2] [test.lox] var a = "value"; +[stage-2] [test-2] [test.lox] print a; // expect: value +[stage-2] [test-2] [test.lox] +[stage-2] [test-2] $ ./your_program.sh run test.lox +[your_program] Undefined variable 'a'. +[your_program] [line 1] +[stage-2] [test-2] ✓ 1 line(s) match on stdout +[stage-2] [test-2] ✓ Received exit code 70. +[stage-2] [test-3] Running test case: 3 +[stage-2] [test-3] Writing contents to ./test.lox: +[stage-2] [test-3] [test.lox] +[stage-2] [test-3] $ ./your_program.sh run test.lox +[stage-2] [test-3] ✓ 1 line(s) match on stdout +[stage-2] [test-3] ✓ Received exit code 0. +[stage-2] [test-4] Running test case: 4 +[stage-2] [test-4] Writing contents to ./test.lox: +[stage-2] [test-4] [test.lox] +[stage-2] [test-4] $ ./your_program.sh run test.lox +[stage-2] [test-4] ✓ 1 line(s) match on stdout +[stage-2] [test-4] ✓ Received exit code 0. +[stage-2] Test passed. + +[stage-1] Running tests for Stage #1: r7 +[stage-1] [test-1] Running test case: 1 +[stage-1] [test-1] Writing contents to ./test.lox: +[stage-1] [test-1] [test.lox] { +[stage-1] [test-1] [test.lox] var a = "value"; +[stage-1] [test-1] [test.lox] var a = "other"; // Error at 'a': Already a variable with this name in this scope. +[stage-1] [test-1] [test.lox] } +[stage-1] [test-1] [test.lox] +[stage-1] [test-1] $ ./your_program.sh run test.lox +[your_program] [line 3] Error at 'a': Already a variable with this name in this scope. +[stage-1] [test-1] ✓ 1 line(s) match on stdout +[stage-1] [test-1] ✓ Received exit code 65. +[stage-1] [test-2] Running test case: 2 +[stage-1] [test-2] Writing contents to ./test.lox: +[stage-1] [test-2] [test.lox] fun foo(a) { +[stage-1] [test-2] [test.lox] var a; // Error at 'a': Already a variable with this name in this scope. +[stage-1] [test-2] [test.lox] } +[stage-1] [test-2] [test.lox] +[stage-1] [test-2] $ ./your_program.sh run test.lox +[your_program] [line 2] Error at 'a': Already a variable with this name in this scope. +[stage-1] [test-2] ✓ 1 line(s) match on stdout +[stage-1] [test-2] ✓ Received exit code 65. +[stage-1] [test-3] Running test case: 3 +[stage-1] [test-3] Writing contents to ./test.lox: +[stage-1] [test-3] [test.lox] fun foo(arg, +[stage-1] [test-3] [test.lox] arg) { // Error at 'arg': Already a variable with this name in this scope. +[stage-1] [test-3] [test.lox] "body"; +[stage-1] [test-3] [test.lox] } +[stage-1] [test-3] [test.lox] +[stage-1] [test-3] $ ./your_program.sh run test.lox +[your_program] [line 2] Error at 'arg': Already a variable with this name in this scope. +[stage-1] [test-3] ✓ 1 line(s) match on stdout +[stage-1] [test-3] ✓ Received exit code 65. +[stage-1] [test-4] Running test case: 4 +[stage-1] [test-4] Writing contents to ./test.lox: +[stage-1] [test-4] [test.lox] var a = "1"; +[stage-1] [test-4] [test.lox] print a; // expect: 1 +[stage-1] [test-4] [test.lox] var a; +[stage-1] [test-4] [test.lox] print a; // expect: nil +[stage-1] [test-4] [test.lox] +[stage-1] [test-4] [test.lox] var a = "2"; +[stage-1] [test-4] [test.lox] print a; // expect: 2 +[stage-1] [test-4] [test.lox] +[stage-1] [test-4] [test.lox] { +[stage-1] [test-4] [test.lox] var a = "1"; +[stage-1] [test-4] [test.lox] var a = "2"; +[stage-1] [test-4] [test.lox] print a; // expect: compile error +[stage-1] [test-4] [test.lox] } +[stage-1] [test-4] $ ./your_program.sh run test.lox +[your_program] [line 11] Error at 'a': Already a variable with this name in this scope. +[stage-1] [test-4] ✓ 1 line(s) match on stdout +[stage-1] [test-4] ✓ Received exit code 65. +[stage-1] Test passed.