Fixed closure upvalue scoping
This commit is contained in:
@@ -10,8 +10,14 @@ uses
|
||||
Myc.Ast.Nodes,
|
||||
Myc.Ast.Visitor,
|
||||
Myc.Ast.Scope,
|
||||
Myc.Ast.Analyzer,
|
||||
Myc.Ast;
|
||||
|
||||
//TODO Es gibt hier einen fundamentalen Fehler. Der Binder ist ist in einem Durchlauf nicht in der Lage, Upvalues zu erkennen.
|
||||
// Wenn in TransformVariableDeclaration eine Variable definiert wird, ist sie grundsätzlich lokal, obwohl sie in einer nested lambda als upvalue benutzt werden könnte.
|
||||
// Deshalb muss eigentlich zunächst ermittelt werden, wleche variablen später zur upvalue werden.
|
||||
// Dafür ist Myc.Ast.Analyzer möglicherweise nützlich.
|
||||
|
||||
type
|
||||
// The binder is a transformer that enriches the AST with semantic information
|
||||
// like resolved addresses, scopes, and tail-call annotations.
|
||||
@@ -36,6 +42,9 @@ type
|
||||
FNestedLambdaCount: Integer;
|
||||
FIsTailStack: TStack<Boolean>;
|
||||
FNextIsTail: Boolean;
|
||||
// Contains all variable declarations that are captured by a closure.
|
||||
// This is populated by a pre-pass before the transformation begins.
|
||||
FBoxedDeclarations: THashSet<IVariableDeclarationNode>;
|
||||
|
||||
procedure EnterScope;
|
||||
procedure ExitScope;
|
||||
@@ -73,6 +82,15 @@ type
|
||||
property Address: TResolvedAddress read FAddress;
|
||||
end;
|
||||
|
||||
// A bound variable declaration node that includes whether the variable is captured (boxed).
|
||||
TBoundVariableDeclarationNode = class(TVariableDeclarationNode)
|
||||
private
|
||||
FIsBoxed: Boolean;
|
||||
public
|
||||
constructor Create(const AIdentifier: IIdentifierNode; AInitializer: IAstNode; AIsBoxed: Boolean);
|
||||
property IsBoxed: Boolean read FIsBoxed;
|
||||
end;
|
||||
|
||||
TBoundLambdaExpressionNode = class(TLambdaExpressionNode)
|
||||
private
|
||||
FScopeDescriptor: IScopeDescriptor;
|
||||
@@ -127,6 +145,13 @@ begin
|
||||
FAddress := AAddress;
|
||||
end;
|
||||
|
||||
{ TBoundVariableDeclarationNode }
|
||||
constructor TBoundVariableDeclarationNode.Create(const AIdentifier: IIdentifierNode; AInitializer: IAstNode; AIsBoxed: Boolean);
|
||||
begin
|
||||
inherited Create(AIdentifier, AInitializer);
|
||||
FIsBoxed := AIsBoxed;
|
||||
end;
|
||||
|
||||
{ TBoundLambdaExpressionNode }
|
||||
constructor TBoundLambdaExpressionNode.Create(
|
||||
const AUnboundNode: ILambdaExpressionNode;
|
||||
@@ -192,12 +217,14 @@ begin
|
||||
FNestedLambdaCount := 0;
|
||||
FIsTailStack := TStack<Boolean>.Create;
|
||||
FNextIsTail := True;
|
||||
FBoxedDeclarations := nil;
|
||||
end;
|
||||
|
||||
destructor TAstBinder.Destroy;
|
||||
begin
|
||||
FIsTailStack.Free;
|
||||
FUpvalueStack.Free;
|
||||
FBoxedDeclarations.Free;
|
||||
inherited;
|
||||
end;
|
||||
|
||||
@@ -221,12 +248,20 @@ end;
|
||||
|
||||
function TAstBinder.Execute(const RootNode: IAstNode; out Descriptor: IScopeDescriptor): IAstNode;
|
||||
begin
|
||||
EnterScope;
|
||||
// First pass: Analyze the entire AST to find all variable declarations
|
||||
// that are captured by closures (upvalues).
|
||||
FBoxedDeclarations := TUpvalueAnalyzer.Analyze(RootNode, FCurrentDescriptor.Parent);
|
||||
try
|
||||
Result := Accept(RootNode).AsIntf<IAstNode>;
|
||||
Descriptor := FCurrentDescriptor;
|
||||
// Second pass: Transform the tree, using the information from the analysis pass.
|
||||
EnterScope;
|
||||
try
|
||||
Result := Accept(RootNode).AsIntf<IAstNode>;
|
||||
Descriptor := FCurrentDescriptor;
|
||||
finally
|
||||
ExitScope;
|
||||
end;
|
||||
finally
|
||||
ExitScope;
|
||||
// The binder now owns the hash set, which will be freed in the destructor.
|
||||
end;
|
||||
end;
|
||||
|
||||
@@ -290,11 +325,13 @@ var
|
||||
slotIndex: Integer;
|
||||
address: TResolvedAddress;
|
||||
boundIdentifier: IIdentifierNode;
|
||||
isBoxed: Boolean;
|
||||
begin
|
||||
if not IsValidIdentifier(Node.Identifier.Name) then
|
||||
raise Exception.CreateFmt('Invalid identifier name: "%s".', [Node.Identifier.Name]);
|
||||
|
||||
FNextIsTail := False;
|
||||
initializer := nil;
|
||||
if Node.Initializer <> nil then
|
||||
initializer := Accept(Node.Initializer).AsIntf<IAstNode>;
|
||||
|
||||
@@ -303,7 +340,11 @@ begin
|
||||
|
||||
boundIdentifier := TBoundIdentifierNode.Create(Node.Identifier, address);
|
||||
|
||||
Result := TAst.VarDecl(boundIdentifier, initializer);
|
||||
// Check if the analysis pass marked this declaration as being captured by a closure.
|
||||
isBoxed := (FBoxedDeclarations <> nil) and FBoxedDeclarations.Contains(Node);
|
||||
|
||||
// Always create a bound declaration node, passing the IsBoxed flag.
|
||||
Result := TBoundVariableDeclarationNode.Create(boundIdentifier, initializer, isBoxed);
|
||||
end;
|
||||
|
||||
function TAstBinder.TransformAssignment(const Node: IAssignmentNode): IAssignmentNode;
|
||||
|
||||
Reference in New Issue
Block a user