TDataValue as new global variant type
This commit is contained in:
@@ -13,7 +13,6 @@ uses
|
||||
Myc.Ast;
|
||||
|
||||
type
|
||||
// TEvaluatorVisitor is the base implementation for evaluating an AST.
|
||||
TEvaluatorVisitor = class(TInterfacedObject, IAstVisitor)
|
||||
private
|
||||
FScope: IExecutionScope;
|
||||
@@ -40,7 +39,6 @@ type
|
||||
function VisitSeriesLength(const Node: ISeriesLengthNode): TDataValue; virtual;
|
||||
end;
|
||||
|
||||
// TDebugEvaluatorVisitor now overrides all visit methods for full tracing
|
||||
TDebugEvaluatorVisitor = class(TEvaluatorVisitor)
|
||||
private
|
||||
FLog: TStrings;
|
||||
@@ -73,7 +71,6 @@ type
|
||||
function VisitSeriesLength(const Node: ISeriesLengthNode): TDataValue; override;
|
||||
end;
|
||||
|
||||
// Registers all native core functions in the given scope.
|
||||
procedure RegisterNativeFunctions(const AScope: IExecutionScope);
|
||||
|
||||
implementation
|
||||
@@ -87,10 +84,18 @@ uses
|
||||
Myc.Data.Scalar.JSON;
|
||||
|
||||
type
|
||||
// The signature for a native Delphi function callable from the script.
|
||||
TNativeFunction = function(const Args: TArray<TDataValue>): TDataValue;
|
||||
|
||||
TClosureValue = class(TInterfacedObject, TDataValue.ICallable)
|
||||
TClosure = class(TInterfacedObject)
|
||||
function GetArity: Integer; virtual; abstract;
|
||||
function Invoke(
|
||||
const AVisitor: IAstVisitor;
|
||||
const ASelf: TDataValue;
|
||||
const AArgNodes: TList<IAstNode>
|
||||
): TDataValue; virtual; abstract;
|
||||
end;
|
||||
|
||||
TClosureValue = class(TClosure)
|
||||
private
|
||||
FLambdaNode: ILambdaExpressionNode;
|
||||
FClosureScope: IExecutionScope;
|
||||
@@ -101,22 +106,18 @@ type
|
||||
const AClosureScope: IExecutionScope;
|
||||
const AUpvalues: TArray<IValueCell>
|
||||
);
|
||||
// ICallable implementation (the generic, slower path)
|
||||
function GetArity: Integer;
|
||||
function Invoke(const AVisitor: IInterface; const ASelf: TDataValue; const AArgs: TArray<TDataValue>): TDataValue;
|
||||
// Fast Path
|
||||
function InvokeFast(const AVisitor: IAstVisitor; const ASelf: TDataValue; const AArgNodes: TList<IAstNode>): TDataValue;
|
||||
function GetArity: Integer; override;
|
||||
function Invoke(const AVisitor: IAstVisitor; const ASelf: TDataValue; const AArgNodes: TList<IAstNode>): TDataValue; override;
|
||||
end;
|
||||
|
||||
TNativeClosure = class(TInterfacedObject, TDataValue.ICallable)
|
||||
TNativeClosure = class(TClosure)
|
||||
private
|
||||
FMethod: TNativeFunction;
|
||||
FArity: Integer;
|
||||
public
|
||||
constructor Create(const AMethod: TNativeFunction; AArity: Integer);
|
||||
// ICallable implementation
|
||||
function GetArity: Integer;
|
||||
function Invoke(const AVisitor: IInterface; const ASelf: TDataValue; const AArgs: TArray<TDataValue>): TDataValue;
|
||||
function GetArity: Integer; override;
|
||||
function Invoke(const AVisitor: IAstVisitor; const ASelf: TDataValue; const AArgNodes: TList<IAstNode>): TDataValue; override;
|
||||
end;
|
||||
|
||||
// --- Native Functions Implementation ---
|
||||
@@ -169,15 +170,16 @@ begin
|
||||
Result := Length(FLambdaNode.Parameters);
|
||||
end;
|
||||
|
||||
// This is the generic, slightly slower path, kept for compatibility with ICallable.
|
||||
function TClosureValue.Invoke(const AVisitor: IInterface; const ASelf: TDataValue; const AArgs: TArray<TDataValue>): TDataValue;
|
||||
function TClosureValue.Invoke(const AVisitor: IAstVisitor; const ASelf: TDataValue; const AArgNodes: TList<IAstNode>): TDataValue;
|
||||
var
|
||||
i: Integer;
|
||||
descriptor: IScopeDescriptor;
|
||||
callScope: IExecutionScope;
|
||||
adr: TResolvedAddress;
|
||||
callVisitor: IAstVisitor;
|
||||
begin
|
||||
if (AArgNodes.Count <> GetArity) then
|
||||
raise EArgumentException.CreateFmt('Argument count mismatch: expected %d, got %d', [GetArity, AArgNodes.Count]);
|
||||
|
||||
descriptor := FLambdaNode.ScopeDescriptor;
|
||||
if not Assigned(descriptor) then
|
||||
raise EParserError.Create('Lambda has no scope descriptor. Did the binder run?');
|
||||
@@ -189,52 +191,12 @@ begin
|
||||
adr.SlotIndex := 0;
|
||||
callScope[adr].Value := ASelf;
|
||||
|
||||
for i := 0 to High(AArgs) do
|
||||
begin
|
||||
adr.SlotIndex := FLambdaNode.Parameters[i].Address.SlotIndex;
|
||||
callScope[adr].Value := AArgs[i];
|
||||
end;
|
||||
|
||||
callVisitor := (AVisitor as TEvaluatorVisitor).CreateVisitorForScope(callScope);
|
||||
Result := FLambdaNode.Body.Accept(callVisitor);
|
||||
end;
|
||||
|
||||
// This is the new, optimized method that evaluates argument nodes directly.
|
||||
function TClosureValue.InvokeFast(const AVisitor: IAstVisitor; const ASelf: TDataValue; const AArgNodes: TList<IAstNode>): TDataValue;
|
||||
var
|
||||
i: Integer;
|
||||
descriptor: IScopeDescriptor;
|
||||
callScope: IExecutionScope;
|
||||
adr: TResolvedAddress;
|
||||
begin
|
||||
// Arity check
|
||||
if (AArgNodes.Count <> Length(FLambdaNode.Parameters)) then
|
||||
raise EArgumentException
|
||||
.CreateFmt('Argument count mismatch: expected %d, got %d', [Length(FLambdaNode.Parameters), AArgNodes.Count]);
|
||||
|
||||
descriptor := FLambdaNode.ScopeDescriptor;
|
||||
if not Assigned(descriptor) then
|
||||
raise EParserError.Create('Lambda has no scope descriptor. Did the binder run?');
|
||||
|
||||
// Create the scope for the call directly
|
||||
callScope := TExecutionScope.Create(FClosureScope, descriptor, FUpvalues);
|
||||
|
||||
adr.Kind := akLocalOrParent;
|
||||
adr.ScopeDepth := 0;
|
||||
|
||||
// Set the 'Self' variable
|
||||
adr.SlotIndex := 0;
|
||||
callScope[adr].Value := ASelf;
|
||||
|
||||
// Evaluate arguments DIRECTLY into the new scope (no temporary array!)
|
||||
for i := 0 to AArgNodes.Count - 1 do
|
||||
begin
|
||||
adr.SlotIndex := FLambdaNode.Parameters[i].Address.SlotIndex;
|
||||
// Evaluate in CALLER'S scope (AVisitor), place in CALLEE'S scope (callScope)
|
||||
callScope[adr].Value := AArgNodes[i].Accept(AVisitor);
|
||||
end;
|
||||
|
||||
// Execute the body with a new visitor for the new scope
|
||||
Result := FLambdaNode.Body.Accept((AVisitor as TEvaluatorVisitor).CreateVisitorForScope(callScope));
|
||||
end;
|
||||
|
||||
@@ -252,14 +214,20 @@ begin
|
||||
Result := FArity;
|
||||
end;
|
||||
|
||||
function TNativeClosure.Invoke(const AVisitor: IInterface; const ASelf: TDataValue; const AArgs: TArray<TDataValue>): TDataValue;
|
||||
function TNativeClosure.Invoke(const AVisitor: IAstVisitor; const ASelf: TDataValue; const AArgNodes: TList<IAstNode>): TDataValue;
|
||||
var
|
||||
argValues: TArray<TDataValue>;
|
||||
i: Integer;
|
||||
begin
|
||||
// Arity check
|
||||
if (FArity <> -1) and (Length(AArgs) <> FArity) then
|
||||
raise EArgumentException.CreateFmt('Argument count mismatch: expected %d, got %d', [FArity, Length(AArgs)]);
|
||||
if (FArity <> -1) and (AArgNodes.Count <> FArity) then
|
||||
raise EArgumentException.CreateFmt('Argument count mismatch: expected %d, got %d', [FArity, AArgNodes.Count]);
|
||||
|
||||
// AVisitor and ASelf are ignored for native calls.
|
||||
Result := FMethod(AArgs);
|
||||
// Evaluate argument nodes to get values for the native function
|
||||
SetLength(argValues, AArgNodes.Count);
|
||||
for i := 0 to AArgNodes.Count - 1 do
|
||||
argValues[i] := AArgNodes[i].Accept(AVisitor);
|
||||
|
||||
Result := FMethod(argValues);
|
||||
end;
|
||||
|
||||
{ TEvaluatorVisitor }
|
||||
@@ -296,37 +264,17 @@ end;
|
||||
function TEvaluatorVisitor.VisitFunctionCall(const Node: IFunctionCallNode): TDataValue;
|
||||
var
|
||||
calleeValue: TDataValue;
|
||||
callable: TDataValue.ICallable;
|
||||
i: Integer;
|
||||
begin
|
||||
calleeValue := Node.Callee.Accept(Self);
|
||||
|
||||
if calleeValue.Kind <> vkCallable then
|
||||
raise EArgumentException.Create('Expression is not a callable value.');
|
||||
// Check if the value holds an interface and if that interface supports our invocation contract.
|
||||
if (calleeValue.Kind <> vkInterface) or not (calleeValue.AsInterface is TClosure) then
|
||||
raise EArgumentException.Create('Expression is not invokable in this context.');
|
||||
|
||||
callable := calleeValue.AsCallable;
|
||||
|
||||
if callable is TClosureValue then
|
||||
begin
|
||||
// "Fast Path": Call the optimized method directly.
|
||||
Result := (callable as TClosureValue).InvokeFast(Self, calleeValue, Node.Arguments);
|
||||
end
|
||||
else
|
||||
begin
|
||||
// "Generic Path": For other ICallable types (e.g., TNativeClosure).
|
||||
// Evaluate arguments into a temporary array first.
|
||||
var argValues: TArray<TDataValue>;
|
||||
SetLength(argValues, Node.Arguments.Count);
|
||||
for i := 0 to Node.Arguments.Count - 1 do
|
||||
argValues[i] := Node.Arguments[i].Accept(Self);
|
||||
|
||||
// Call the generic Invoke method.
|
||||
Result := callable.Invoke(Self, calleeValue, argValues);
|
||||
end;
|
||||
// "Tell, Don't Ask": Simply tell the object to invoke itself.
|
||||
Result := (calleeValue.AsInterface as TClosure).Invoke(Self, calleeValue, Node.Arguments);
|
||||
end;
|
||||
|
||||
{ TEvaluatorVisitor }
|
||||
|
||||
function TEvaluatorVisitor.VisitAddSeriesItem(const Node: IAddSeriesItemNode): TDataValue;
|
||||
var
|
||||
itemValue, lookbackValue, seriesVar: TDataValue;
|
||||
@@ -531,7 +479,7 @@ begin
|
||||
for i := 0 to High(sourceAddresses) do
|
||||
capturedCells[i] := FScope.GetCell(sourceAddresses[i]);
|
||||
|
||||
Result := TClosureValue.Create(Node, FScope, capturedCells);
|
||||
Result := TClosureValue.Create(Node, FScope, capturedCells) as IInterface;
|
||||
end;
|
||||
|
||||
function TEvaluatorVisitor.VisitBinaryExpression(const Node: IBinaryExpressionNode): TDataValue;
|
||||
|
||||
Reference in New Issue
Block a user