Skip to content

Implement variable resolution and global environment management#63

Merged
ryan-gang merged 16 commits intoresolving-bindingfrom
resolving-golox
Feb 27, 2025
Merged

Implement variable resolution and global environment management#63
ryan-gang merged 16 commits intoresolving-bindingfrom
resolving-golox

Conversation

@ryan-gang
Copy link
Contributor

@ryan-gang ryan-gang commented Feb 26, 2025

Introduce methods for resolving local and global variables, manage global environment, and enhance semantic error handling. This update improves the interpreter's ability to handle variable scopes and ensures proper error reporting during name resolution.

Summary by CodeRabbit

  • New Features

    • Enhanced error reporting with improved semantic analysis for clearer messages during code evaluation.
    • Strengthened handling of variable scopes and local state, ensuring more reliable function behavior.
  • Tests

    • Expanded automated test coverage to verify scoping, error handling, and function execution.
    • Updated test routines provide more robust validation of overall interpreter performance.

@ryan-gang ryan-gang self-assigned this Feb 26, 2025
@coderabbitai
Copy link

coderabbitai bot commented Feb 26, 2025

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

This pull request makes multiple updates across the codebase. The Makefile test targets have been adjusted and expanded. The interpreter now includes a semantic resolution phase before evaluation, with improved error handling. Changes in the environment and function modules add methods and a locals field to support variable scoping. Additionally, a new resolver module and a semantic error handling file have been introduced, while test cases for resolving and function behavior have been updated across several fixture files and test programs.

Changes

File(s) Summary of Changes
Makefile Updated test targets: removed a blank line, updated the directory for test_resolving_w_jlox, and modified test_all to include additional tests.
internal/lox/api/run_api.go Modified the Run function to perform semantic resolution before interpretation, with updated error handling and environment/locals management.
internal/lox/environment.go Added new methods: GetAt, Ancestor, and AssignAt for navigating and modifying variables in ancestor scopes.
internal/lox/functions.go Introduced a new Locals field in UserFunction and updated the constructor and Call method to use local variables.
internal/lox/globals.go Introduced global variables GlobalEnv and globals; updated native function initialization and added a ResetGlobalEnv function.
internal/lox/interpreter.go Adjusted the signatures of BasicInterpret, Interpret, and Eval to include locals; refined error handling for semantic errors.
internal/lox/resolver.go New module for name resolution, implementing scope tracking, variable declarations, and error checking during AST resolution.
internal/lox/semantic_error.go New file for handling semantic errors, featuring PrintSemanticError, MakeSemanticError, and a global error flag.
internal/stages_test.go Added new test cases for both in-progress and completed resolving stages in the Jlox interpreter.
internal/test_helpers/fixtures/pass_functions*
internal/test_helpers/fixtures/pass_resolving*
Modified test fixtures: updated isEven input values from 93 to 75 and incorporated comprehensive tests for variable resolution and scoping.
test_programs/f9/2.lox Changed the argument to the isEven function from a random placeholder to a specific value (75).

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant RunAPI
    participant Resolver
    participant Interpreter
    participant Environment

    Client->>RunAPI: Execute parsed statements
    RunAPI->>Resolver: Resolve(statements)
    Resolver-->>RunAPI: Return locals (or error)
    alt Semantic Error Occurs
        RunAPI-->>Client: Return exit code 65 with error
    else
        RunAPI->>Environment: Create/use global environment
        RunAPI->>Interpreter: Interpret(statements, env, locals)
        Interpreter-->>RunAPI: Execution result
        RunAPI-->>Client: Return output
    end
Loading

Poem

I’m a rabbit coding fast and free,
Hoping through environments with glee.
Semantic errors now get their due,
Functions and tests hop into view.
In these changes I find delight,
Bounding through code both day and night.
🐰💻 Happy hops in every byte!


🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@ryan-gang ryan-gang force-pushed the resolving-golox branch 2 times, most recently from 67ff6ff to 1e616c6 Compare February 27, 2025 14:18
- Introduce GlobalEnv and globals variables for managing global environment
- Modify InitializeNativeFunctions to use GlobalEnv
- Add ResetGlobalEnv method to restore original global environment reference
- 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
- 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
- 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
- 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
Modify the error checking logic to ensure semantic errors are properly handled when resolving statements
…ution

- 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
- 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
- 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
Add HadSemanticError to the list of error flags being reset, ensuring a clean slate for error tracking across different runs
- 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
Add test_functions_w_jlox and test_resolving_w_jlox to the test_all target, ensuring comprehensive test coverage for recent language feature implementations
Add test cases for resolving implementation in different stages of the jlox interpreter, covering in-progress and completed resolving scenarios
@ryan-gang
Copy link
Contributor Author

@coderabbitai full review

@coderabbitai
Copy link

coderabbitai bot commented Feb 27, 2025

✅ Actions performed

Full review triggered.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (8)
internal/lox/functions.go (1)

40-40: Consider referencing Locals by pointer
The TODO suggests using a pointer to Locals. This could avoid accidental copies and simplify scope management, especially if future changes require mutating the local variable map.

internal/lox/globals.go (2)

7-10: Beware shared global state
Using a single global environment (GlobalEnv) might introduce concurrency or re-initialization challenges if multiple runs occur in parallel. Consider concurrency-safe usage or separate env instances if needed.


11-11: Parameter removal trade-off
Removing env from InitializeNativeFunctions reduces complexity but also flexibility for test scenarios that might require custom environments.

internal/lox/api/run_api.go (1)

27-27: Single exit code for parse & semantic errors
Using 65 for both parse and semantic errors unifies error handling but can limit nuanced reporting. If needed, consider distinct exit codes for deeper insight into failures.

internal/lox/semantic_error.go (1)

20-21: Consider documentation for global state implications.

While the variable name is descriptive, you may want to add a comment about how this global state is managed across multiple error scenarios and whether it gets reset between runs.

internal/lox/environment.go (1)

75-78: Ensure type consistency across function signatures.

The AssignAt method uses any as a parameter type, while other methods in this file use interface{}. While any is a Go 1.18+ alias for interface{}, maintaining consistency would improve code readability.

-func (e *Environment) AssignAt(distance int, name Token, value any) error {
+func (e *Environment) AssignAt(distance int, name Token, value interface{}) error {
internal/test_helpers/fixtures/pass_resolving_final (1)

1-1: Consider adding a toggle for debug mode instead of a hard-coded flag.

The line Debug = true suggests that debug output is permanently enabled. Allowing debug mode to be set via an environment variable or command-line argument makes it easier to switch on or off in different environments without modifying the file.

internal/lox/interpreter.go (1)

48-120: Logical extension to pass Locals into Eval.

By threading locals through all expression evaluations, you correctly support scoped name resolution. This is essential for the new resolver. Just watch out for any performance overhead in large codebases with deeply nested expressions. Consider short-circuit logic or caching expansions if needed.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6420c22 and 21cfe73.

📒 Files selected for processing (14)
  • Makefile (2 hunks)
  • internal/lox/api/run_api.go (1 hunks)
  • internal/lox/environment.go (2 hunks)
  • internal/lox/functions.go (2 hunks)
  • internal/lox/globals.go (2 hunks)
  • internal/lox/interpreter.go (9 hunks)
  • internal/lox/resolver.go (1 hunks)
  • internal/lox/semantic_error.go (1 hunks)
  • internal/stages_test.go (1 hunks)
  • internal/test_helpers/fixtures/pass_functions (1 hunks)
  • internal/test_helpers/fixtures/pass_functions_final (1 hunks)
  • internal/test_helpers/fixtures/pass_resolving (1 hunks)
  • internal/test_helpers/fixtures/pass_resolving_final (1 hunks)
  • test_programs/f9/2.lox (1 hunks)
🔇 Additional comments (31)
test_programs/f9/2.lox (1)

21-21: Change looks good - using explicit value instead of placeholder.

The change from a placeholder <<RANDOM_INTEGER_1>> to an explicit value of 75 is appropriate for a test case. Since the threshold is defined as 50 (line 7), this value is beyond the threshold and will return false without deep recursion, making it a good test case.

internal/test_helpers/fixtures/pass_functions_final (1)

638-638: Test fixture consistently updated to match the test program.

The test fixture has been updated to use 75 as the input value for isEven() function, maintaining consistency with the changes made in the test program file.

internal/test_helpers/fixtures/pass_functions (1)

638-638: Consistent test value updated in all related files.

The input value has been consistently updated to 75 across all test fixtures and test programs, ensuring coherent test behavior. The output in line 641 correctly shows "false" as expected, since 75 > threshold (50).

Makefile (2)

156-156: Smooth transition to new chapter directory
Pointing CODECRAFTERS_REPOSITORY_DIR to chap11_resolving effectively aligns with the newly introduced resolving tests. This helps ensure the correct set of fixtures is used for resolution.


168-168: Comprehensive test coverage
Including test_resolving_w_jlox in test_all ensures that the new resolution tests won't be overlooked.

internal/lox/functions.go (2)

44-49: Verify invocations of the new constructor
Ensure all calls to NewUserFunction correctly pass the locals parameter. Missing updates could lead to inconsistent scope handling.


62-62: Local variable resolution
Calling Eval with u.Locals ensures statements use the correct local scope. This appropriately aligns with the new resolution logic.

internal/lox/globals.go (1)

22-25: Partial environment reset
ResetGlobalEnv reassigns GlobalEnv to the initial globals reference but doesn’t reinstantiate it. If user code modifies globals, a full re-initialization might be needed for a clean slate.

internal/lox/api/run_api.go (2)

18-21: Semantic resolution handling
Introducing lox.Resolve before interpretation is a solid approach to ensure variable scope validation. The early return on semantic errors clearly signals resolution issues.


23-24: Isolated environment per run
Creating a new global environment each time avoids state leakage between executions. This is beneficial for consistent testing and prevents cross-run interference.

internal/lox/semantic_error.go (2)

8-12: Clean implementation of semantic error reporting.

The PrintSemanticError function provides a clear way to report semantic errors to stderr while updating the global error flag.


14-18: Well-designed semantic error creation function.

The MakeSemanticError function creates and returns properly formatted error objects, maintaining consistency with error handling patterns.

internal/stages_test.go (1)

78-91: Test cases for resolving functionality well structured.

The test cases for both in-progress and completed resolving stages follow the established pattern in the codebase. This consistency makes the test suite easier to maintain and understand.

internal/lox/environment.go (2)

49-52: Effective implementation of hierarchical scope lookup.

The GetAt method elegantly uses the Ancestor method to navigate to the appropriate environment before performing the lookup.


54-61: Well-designed environment navigation.

The Ancestor method efficiently traverses the environment chain using idiomatic Go with a range loop over the distance value.

internal/test_helpers/fixtures/pass_resolving (4)

1-96: Comprehensive test coverage for variable resolution scenarios.

The test suite for Stage #7 covers important variable resolution scenarios including closure behavior, variable shadowing, and nested function scopes. These tests will help ensure the resolver correctly implements lexical scoping.


155-173: Good error handling tests for variable initialization.

Testing the error case where a local variable is referenced in its own initializer is an important semantic check that prevents subtle bugs.


191-195: Appropriate validation of return statement restrictions.

Testing that return statements are not allowed in top-level code ensures proper function boundary enforcement, which is essential for predictable language behavior.


257-305: Thorough validation of variable redeclaration rules.

The tests comprehensively cover various scenarios of variable redeclaration, including local scope redeclaration, parameter redeclaration, and function parameter duplication.

internal/test_helpers/fixtures/pass_resolving_final (3)

3-20: Good comprehensive test for variable shadowing in nested scopes.

The test logs and expected output effectively cover how outer and inner variables are accessed within nested functions. This approach ensures that the scoping rules are thoroughly checked. Nice work capturing both the initial and redeclared variable behaviors.


72-98: Excellent illustration of closures by returning a function.

This test shows that the function returned by makeCounter() continues to reference and modify the count variable declared in an outer scope. This confirms that closures are working correctly. No concerns here.


254-306: Thorough validation of redeclaration errors.

These tests confirm that redeclaring variables within the same scope correctly triggers an error. The fixture also checks for parameter name collisions and re-declarations of the same variable in short succession. This ensures robust error handling.

internal/lox/interpreter.go (4)

21-21: Verify that passing an empty Locals{} won't break external usage.

Using Locals{} by default in BasicInterpret is fine, provided that any references to local variable resolutions outside minimal test expressions handle this scenario gracefully. Consider verifying that no calls rely on a non-empty locals.


32-45: GlobalEnv override may mask errors in nested calls.

Saving the current GlobalEnv, overriding it, and then restoring it is convenient but can introduce unintentional side effects if future code depends on the original environment mid-interpretation. Ensure that no concurrency or parallel evaluations attempt to read from GlobalEnv while it’s swapped.


166-296: Proper local vs. global variable handling.

Lines for variable reads (Variable) and writes (Assign) correctly check locals[n] first, then fall back to GlobalEnv. This order ensures locals take precedence, aligning with typical scoping rules. Great job maintaining clarity in these conditionals.


345-345: Confirm that resetting HadSemanticError won't mask subsequent semantic errors.

Clearing HadSemanticError is appropriate before a fresh interpretation run. Just ensure that any semantic issues discovered afterward are still being captured and reported accurately.

internal/lox/resolver.go (5)

1-9: Concise introduction for resolving local scopes.

Defining Locals as map[Expr]int is straightforward and keeps the resolution overhead minimal. This helps maintain an efficient lookup for scoping during interpretation. No concerns here.


13-19: Return both the Locals map and the resolution error.

The Resolve function's return signature is intuitive, ensuring callers receive both the computed scope data and any semantic errors that arise. This design is consistent and improves the clarity of error handling in the interpreter.


44-52: Error handling in variable declarations.

Declaring a variable before initialization, then immediately resolving its initializer prevents self-references. The current approach effectively catches “read in own initializer” scenarios. Nicely done.


67-74: Well-structured resolver for function declarations.

By declaring and defining the function name, then resolving its body in a new scope, you ensure all function parameters and local variables are accounted for. This is a clean approach that avoids premature access to partially-defined functions.


177-184: Local resolution logic is straightforward and efficient.

Iterating scopes from the innermost outward provides the correct distance for environment lookups. This pattern integrates seamlessly with the updated interpreter, ensuring correct variable references in nested scopes.

@ryan-gang ryan-gang merged commit 21cfe73 into resolving-binding Feb 27, 2025
1 of 3 checks passed
@ryan-gang
Copy link
Contributor Author

This PR implements resolving and binding in our internal golox.

@ryan-gang ryan-gang deleted the resolving-golox branch February 28, 2025 07:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant