Ast Binding
This commit is contained in:
+78
-30
@@ -45,7 +45,7 @@ type
|
||||
|
||||
// --- Static method to run the binder ---
|
||||
// Traverses the AST and resolves all identifiers. This must be called before evaluating the AST.
|
||||
class procedure Bind(const ARootNode: IExpressionNode); static;
|
||||
class procedure Bind(const ARootNode: IExpressionNode; const AScope: IExecutionScope); static;
|
||||
end;
|
||||
|
||||
// TAstTraverser provides a default AST traversal implementation.
|
||||
@@ -73,7 +73,8 @@ implementation
|
||||
|
||||
uses
|
||||
System.Classes,
|
||||
System.Generics.Collections;
|
||||
System.Generics.Collections,
|
||||
Myc.Ast.Scope; // Needed for the TExecutionScope cast
|
||||
|
||||
type
|
||||
{ TAstNode }
|
||||
@@ -296,9 +297,11 @@ type
|
||||
FParent: TSymbolTable;
|
||||
FSymbols: TDictionary<string, TSymbol>;
|
||||
FNextSlotIndex: Integer;
|
||||
procedure DefinePreResolved(const Name: string; Index: Integer);
|
||||
public
|
||||
constructor Create(AParent: TSymbolTable);
|
||||
destructor Destroy; override;
|
||||
procedure PopulateFromScope(const AScope: IExecutionScope);
|
||||
function Define(const Name: string): TSymbol;
|
||||
function Resolve(const Name: string; out Symbol: TSymbol; out Depth: Integer): Boolean;
|
||||
end;
|
||||
@@ -310,12 +313,11 @@ type
|
||||
procedure EnterScope;
|
||||
procedure ExitScope;
|
||||
public
|
||||
constructor Create;
|
||||
constructor Create(AInitialScope: IExecutionScope);
|
||||
destructor Destroy; override;
|
||||
function VisitIdentifier(const Node: IIdentifierNode): TAstValue; override;
|
||||
function VisitLambdaExpression(const Node: ILambdaExpressionNode): TAstValue; override;
|
||||
function VisitVariableDeclaration(const Node: IVariableDeclarationNode): TAstValue; override;
|
||||
function VisitBlockExpression(const Node: IBlockExpressionNode): TAstValue; override;
|
||||
end;
|
||||
|
||||
{ TConstantNode }
|
||||
@@ -745,6 +747,38 @@ begin
|
||||
inherited;
|
||||
end;
|
||||
|
||||
procedure TSymbolTable.DefinePreResolved(const Name: string; Index: Integer);
|
||||
var
|
||||
symbol: TSymbol;
|
||||
begin
|
||||
// Defines a symbol with a specific, pre-determined index.
|
||||
if not FSymbols.ContainsKey(Name) then
|
||||
begin
|
||||
symbol.Name := Name;
|
||||
symbol.SlotIndex := Index;
|
||||
FSymbols.Add(Name, symbol);
|
||||
|
||||
// Ensure the next automatically assigned index is correct.
|
||||
if (Index >= FNextSlotIndex) then
|
||||
FNextSlotIndex := Index + 1;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TSymbolTable.PopulateFromScope(const AScope: IExecutionScope);
|
||||
var
|
||||
scopeImpl: TExecutionScope;
|
||||
pair: TPair<string, Integer>;
|
||||
begin
|
||||
// ** THE FIX IS HERE **
|
||||
// Iterate over the Key-Value pairs of the scope's dictionary to preserve the exact indices.
|
||||
if not Assigned(AScope) then
|
||||
Exit;
|
||||
|
||||
scopeImpl := (AScope as TExecutionScope);
|
||||
for pair in scopeImpl.NameToIndex do
|
||||
DefinePreResolved(pair.Key, pair.Value);
|
||||
end;
|
||||
|
||||
function TSymbolTable.Define(const Name: string): TSymbol;
|
||||
begin
|
||||
Result.Name := Name;
|
||||
@@ -772,16 +806,41 @@ end;
|
||||
|
||||
{ TBinder }
|
||||
|
||||
constructor TBinder.Create;
|
||||
// Helper function to recursively build the symbol table hierarchy
|
||||
function CreateSymbolTableFromScope(AScope: IExecutionScope): TSymbolTable;
|
||||
begin
|
||||
inherited;
|
||||
FCurrentScope := TSymbolTable.Create(nil); // Start with a global scope
|
||||
if not Assigned(AScope) then
|
||||
Exit(nil);
|
||||
|
||||
// Recursively create the parent symbol table first
|
||||
Result := TSymbolTable.Create(CreateSymbolTableFromScope(AScope.Parent));
|
||||
// Then populate the current level
|
||||
Result.PopulateFromScope(AScope);
|
||||
end;
|
||||
|
||||
constructor TBinder.Create(AInitialScope: IExecutionScope);
|
||||
begin
|
||||
inherited Create;
|
||||
// Correctly and recursively build the symbol table hierarchy from the execution scope.
|
||||
FCurrentScope := CreateSymbolTableFromScope(AInitialScope);
|
||||
|
||||
// If no initial scope was provided, create a single empty root scope.
|
||||
if not Assigned(FCurrentScope) then
|
||||
FCurrentScope := TSymbolTable.Create(nil);
|
||||
end;
|
||||
|
||||
destructor TBinder.Destroy;
|
||||
var
|
||||
scopeToFree: TSymbolTable;
|
||||
begin
|
||||
Assert(not Assigned(FCurrentScope.FParent), 'Scope leak in binder.');
|
||||
FCurrentScope.Free;
|
||||
// The previous Assert was incorrect for binders initialized with nested scopes.
|
||||
// This new implementation robustly cleans up the entire symbol table chain.
|
||||
while Assigned(FCurrentScope) do
|
||||
begin
|
||||
scopeToFree := FCurrentScope;
|
||||
FCurrentScope := FCurrentScope.FParent;
|
||||
scopeToFree.Free;
|
||||
end;
|
||||
inherited;
|
||||
end;
|
||||
|
||||
@@ -799,17 +858,6 @@ begin
|
||||
oldScope.Free;
|
||||
end;
|
||||
|
||||
function TBinder.VisitBlockExpression(const Node: IBlockExpressionNode): TAstValue;
|
||||
begin
|
||||
EnterScope;
|
||||
try
|
||||
// Default traversal of all expressions in the block
|
||||
inherited VisitBlockExpression(Node);
|
||||
finally
|
||||
ExitScope;
|
||||
end;
|
||||
end;
|
||||
|
||||
function TBinder.VisitIdentifier(const Node: IIdentifierNode): TAstValue;
|
||||
var
|
||||
symbol: TSymbol;
|
||||
@@ -836,6 +884,9 @@ var
|
||||
begin
|
||||
EnterScope;
|
||||
try
|
||||
// Reserve a slot for 'Self' first.
|
||||
FCurrentScope.Define('Self');
|
||||
|
||||
// 1. Define all parameters in the new scope.
|
||||
for param in Node.Parameters do
|
||||
FCurrentScope.Define(param.Name);
|
||||
@@ -850,24 +901,21 @@ end;
|
||||
|
||||
function TBinder.VisitVariableDeclaration(const Node: IVariableDeclarationNode): TAstValue;
|
||||
begin
|
||||
// 1. First, visit the initializer expression to resolve its identifiers.
|
||||
if Assigned(Node.Initializer) then
|
||||
Node.Initializer.Accept(Self);
|
||||
|
||||
// 2. Then, define the new variable in the current scope.
|
||||
// For recursive functions, we must define the name in the scope FIRST,
|
||||
// so it can be resolved inside its own initializer (i.e., the lambda body).
|
||||
FCurrentScope.Define(Node.Identifier.Name);
|
||||
|
||||
// 3. Finally, visit the declaration's own identifier to resolve it.
|
||||
Node.Identifier.Accept(Self);
|
||||
inherited;
|
||||
end;
|
||||
|
||||
{ TAst }
|
||||
|
||||
class procedure TAst.Bind(const ARootNode: IExpressionNode);
|
||||
class procedure TAst.Bind(const ARootNode: IExpressionNode; const AScope: IExecutionScope);
|
||||
var
|
||||
binder: IAstVisitor;
|
||||
begin
|
||||
binder := TBinder.Create;
|
||||
// Create a binder instance, pre-populating its symbol table with the given scope.
|
||||
binder := TBinder.Create(AScope);
|
||||
// Traverse the AST to resolve all identifiers.
|
||||
ARootNode.Accept(binder);
|
||||
end;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user