Go Symbol Resolution
This challenge focuses on building a fundamental component of programming language compilers and interpreters: symbol resolution. You'll create a system that can track and resolve identifiers (symbols) within a defined scope, ensuring that each variable, function, or type is correctly understood in its context. This is crucial for semantic analysis, type checking, and ultimately, code execution.
Problem Description
Your task is to implement a symbol resolution mechanism in Go. This mechanism should be able to:
- Define and Register Symbols: Allow for the declaration of symbols (e.g., variables, functions, types) within a specific scope.
- Resolve Symbols: Given an identifier (symbol name) and a current scope, find the declaration of that symbol. The resolution should correctly handle nested scopes, prioritizing symbols in the innermost scope.
- Handle Scope Management: Implement a system for creating, entering, and exiting scopes.
- Detect Undefined Symbols: Report an error when an identifier is used but has not been declared within the accessible scopes.
Key Requirements:
- Scope Stack: Use a data structure (like a stack) to manage active scopes. Each scope should maintain a collection of declared symbols.
- Symbol Table: Within each scope, you need a way to store symbols. A map is a suitable choice for this.
- Resolution Logic: When resolving a symbol, traverse the scope stack from the most recent (innermost) to the oldest (outermost) scope.
- Error Reporting: Provide a clear mechanism for reporting undefined symbols, including the identifier name and potentially the scope at which it was not found.
Expected Behavior:
- When
ResolveSymbol("x")is called within a scope where "x" is declared, it should return the declaration of "x". - When
ResolveSymbol("y")is called within a scope where "y" is not declared but is declared in an outer scope, it should return the declaration of "y" from the outer scope. - When
ResolveSymbol("z")is called and "z" is not declared in any accessible scope, an error should be returned.
Edge Cases:
- Empty scopes.
- Deeply nested scopes.
- Symbols with the same name in different scopes (inner scope should shadow outer scope).
- Resolving symbols when there are no active scopes.
Examples
Example 1:
Input:
Declarations:
Global Scope: ["a" (int), "b" (string)]
Scope 1 (entered from Global): ["c" (bool), "a" (float64)]
Scope 2 (entered from Scope 1): ["d" (rune)]
Resolutions:
- Resolve "a" in Scope 2
- Resolve "b" in Scope 2
- Resolve "c" in Scope 2
- Resolve "d" in Scope 2
- Resolve "e" in Scope 2
Output:
Symbol "a" resolved to float64 (declared in Scope 1)
Symbol "b" resolved to string (declared in Global Scope)
Symbol "c" resolved to bool (declared in Scope 1)
Symbol "d" resolved to rune (declared in Scope 2)
Error: Undefined symbol "e" at scope level of Scope 2
Explanation: "a" is found in Scope 1 (float64), shadowing the global "a" (int). "b" is found in the Global Scope. "c" is found in Scope 1. "d" is found in Scope 2. "e" is not found in any scope.
Example 2:
Input:
Declarations:
Global Scope: ["x" (int)]
Scope 1 (entered from Global): []
Scope 2 (entered from Scope 1): ["x" (string)]
Resolutions:
- Resolve "x" in Scope 1
Output:
Symbol "x" resolved to int (declared in Global Scope)
Explanation: When resolving "x" in Scope 1, the symbol table for Scope 1 is empty. The resolution proceeds to the outer scope (Global Scope) where "x" (int) is found.
Example 3: (Nested shadowing)
Input:
Declarations:
Global Scope: ["count" (int)]
Scope 1: ["count" (float64)]
Scope 2: ["count" (bool)]
Resolutions:
- Resolve "count" in Scope 2
Output:
Symbol "count" resolved to bool (declared in Scope 2)
Explanation: The innermost scope (Scope 2) declares "count" as a boolean, so this declaration is found and returned.
Constraints
- There can be a maximum of 50 nested scopes.
- Each scope can hold a maximum of 100 unique symbols.
- Symbol names will consist of alphanumeric characters and underscores, starting with an alphabet or underscore.
- The total number of symbol resolutions attempted in a single execution will not exceed 500.
Notes
Consider how you will represent a "Symbol". It might be a struct or a simple type containing at least its name and type information. The "type information" can be represented by a string for this challenge (e.g., "int", "string", "bool").
You'll need to design your ScopeManager or similar structure to handle the stack-like behavior of scopes.
Think about how you will signal an error for an undefined symbol. Returning an error type along with the resolved symbol (or an indicator that no symbol was found) is a common pattern in Go.