Closure upvalues

This commit is contained in:
Michael Schimmel
2025-09-04 23:59:33 +02:00
parent 4a8075fecf
commit aa3c218f44
5 changed files with 351 additions and 56 deletions
+51 -7
View File
@@ -88,19 +88,25 @@ type
// The signature for a native Delphi function callable from the script.
TNativeFunction = function(const Args: TArray<TAstValue>): TAstValue;
// Concrete implementation of the TAstValue.IClosure interface for script-defined functions.
TClosureValue = class(TInterfacedObject, TAstValue.IClosure)
private
FBody: IExpressionNode;
FClosureScope: IExecutionScope;
FLambdaNode: ILambdaExpressionNode;
FUpvalues: TArray<IValueCell>;
function GetBody: IExpressionNode;
function GetParameters: TArray<IIdentifierNode>;
function GetClosureScope: IExecutionScope;
function GetUpvalues: TArray<IValueCell>;
public
constructor Create(const ALambdaNode: ILambdaExpressionNode; const AClosureScope: IExecutionScope);
constructor Create(
const ALambdaNode: ILambdaExpressionNode;
const AClosureScope: IExecutionScope;
const AUpvalues: TArray<IValueCell>
);
property ClosureScope: IExecutionScope read GetClosureScope;
property LambdaNode: ILambdaExpressionNode read FLambdaNode;
property Upvalues: TArray<IValueCell> read FUpvalues;
end;
// Concrete implementation of TAstValue.IClosure for native Delphi functions.
@@ -110,6 +116,7 @@ type
function GetBody: IExpressionNode;
function GetParameters: TArray<IIdentifierNode>;
function GetClosureScope: IExecutionScope;
function GetUpvalues: TArray<IValueCell>;
public
constructor Create(const AMethod: TNativeFunction);
property Method: TNativeFunction read FMethod;
@@ -205,12 +212,17 @@ end;
{ TClosureValue }
constructor TClosureValue.Create(const ALambdaNode: ILambdaExpressionNode; const AClosureScope: IExecutionScope);
constructor TClosureValue.Create(
const ALambdaNode: ILambdaExpressionNode;
const AClosureScope: IExecutionScope;
const AUpvalues: TArray<IValueCell>
);
begin
inherited Create;
FLambdaNode := ALambdaNode;
FBody := ALambdaNode.Body;
FClosureScope := AClosureScope;
FUpvalues := AUpvalues; // Store the captured cells
end;
function TClosureValue.GetBody: IExpressionNode;
@@ -228,6 +240,11 @@ begin
Result := FLambdaNode.Parameters;
end;
function TClosureValue.GetUpvalues: TArray<IValueCell>;
begin
Result := FUpvalues;
end;
{ TNativeClosure }
constructor TNativeClosure.Create(const AMethod: TNativeFunction);
@@ -251,6 +268,10 @@ begin
Result := nil;
end;
function TNativeClosure.GetUpvalues: TArray<IValueCell>;
begin
end;
{ TEvaluatorVisitor }
constructor TEvaluatorVisitor.Create(const AScope: IExecutionScope);
@@ -475,8 +496,27 @@ begin
end;
function TEvaluatorVisitor.VisitLambdaExpression(const Node: ILambdaExpressionNode): TAstValue;
var
capturedCells: TArray<IValueCell>;
i: Integer;
sourceAddr: TResolvedAddress;
sourceAddresses: TArray<TResolvedAddress>;
begin
Result := TAstValue.FromClosure(TClosureValue.Create(Node, FScope));
// The binder has identified the upvalues. Capture the cells from the current scope
// using the original source addresses provided by the binder.
sourceAddresses := Node.UpvalueSourceAddresses;
SetLength(capturedCells, Length(sourceAddresses));
for i := 0 to High(sourceAddresses) do
begin
sourceAddr := sourceAddresses[i];
// The binder gives the depth relative to the lambda's
// inner scope. The evaluator is in the outer scope, so we adjust the
// depth by -1 to find the cell in the current context.
dec(sourceAddr.ScopeDepth);
capturedCells[i] := FScope.GetCell(sourceAddr);
end;
Result := TAstValue.FromClosure(TClosureValue.Create(Node, FScope, capturedCells));
end;
function TEvaluatorVisitor.VisitFunctionCall(const Node: IFunctionCallNode): TAstValue;
@@ -510,17 +550,21 @@ begin
if not Assigned(descriptor) then
raise EParserError.Create('Lambda has no scope descriptor. Did the binder run?');
var callScope := descriptor.CreateScope(closure.ClosureScope);
// Create the execution scope for the call, providing the captured upvalues.
var callScope := TExecutionScope.Create(closure.ClosureScope, descriptor, (closure as TClosureValue).Upvalues) as IExecutionScope;
var adr: TResolvedAddress;
adr.Kind := akLocalOrParent;
adr.ScopeDepth := 0;
// Slot 0 is 'Self'.
adr.SlotIndex := 0;
callScope[adr] := calleeValue;
callScope.Values[adr] := calleeValue;
for i := 0 to Node.Arguments.Count - 1 do
begin
adr.SlotIndex := closure.Parameters[i].Address.SlotIndex;
callScope[adr] := Node.Arguments[i].Accept(Self);
callScope.Values[adr] := Node.Arguments[i].Accept(Self);
end;
Result := closure.Body.Accept(CreateVisitorForScope(callScope));