TDataValue as new global variant type

This commit is contained in:
Michael Schimmel
2025-09-12 13:20:00 +02:00
parent 646ffe92bb
commit d37862186c
5 changed files with 80 additions and 111 deletions
+1 -1
View File
@@ -4,7 +4,7 @@
<ProjectVersion>20.3</ProjectVersion> <ProjectVersion>20.3</ProjectVersion>
<FrameworkType>FMX</FrameworkType> <FrameworkType>FMX</FrameworkType>
<Base>True</Base> <Base>True</Base>
<Config Condition="'$(Config)'==''">Release</Config> <Config Condition="'$(Config)'==''">Debug</Config>
<Platform Condition="'$(Platform)'==''">Win64</Platform> <Platform Condition="'$(Platform)'==''">Win64</Platform>
<ProjectName Condition="'$(ProjectName)'==''">ASTPlayground</ProjectName> <ProjectName Condition="'$(ProjectName)'==''">ASTPlayground</ProjectName>
<TargetedPlatforms>2</TargetedPlatforms> <TargetedPlatforms>2</TargetedPlatforms>
+37 -89
View File
@@ -13,7 +13,6 @@ uses
Myc.Ast; Myc.Ast;
type type
// TEvaluatorVisitor is the base implementation for evaluating an AST.
TEvaluatorVisitor = class(TInterfacedObject, IAstVisitor) TEvaluatorVisitor = class(TInterfacedObject, IAstVisitor)
private private
FScope: IExecutionScope; FScope: IExecutionScope;
@@ -40,7 +39,6 @@ type
function VisitSeriesLength(const Node: ISeriesLengthNode): TDataValue; virtual; function VisitSeriesLength(const Node: ISeriesLengthNode): TDataValue; virtual;
end; end;
// TDebugEvaluatorVisitor now overrides all visit methods for full tracing
TDebugEvaluatorVisitor = class(TEvaluatorVisitor) TDebugEvaluatorVisitor = class(TEvaluatorVisitor)
private private
FLog: TStrings; FLog: TStrings;
@@ -73,7 +71,6 @@ type
function VisitSeriesLength(const Node: ISeriesLengthNode): TDataValue; override; function VisitSeriesLength(const Node: ISeriesLengthNode): TDataValue; override;
end; end;
// Registers all native core functions in the given scope.
procedure RegisterNativeFunctions(const AScope: IExecutionScope); procedure RegisterNativeFunctions(const AScope: IExecutionScope);
implementation implementation
@@ -87,10 +84,18 @@ uses
Myc.Data.Scalar.JSON; Myc.Data.Scalar.JSON;
type type
// The signature for a native Delphi function callable from the script.
TNativeFunction = function(const Args: TArray<TDataValue>): TDataValue; 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 private
FLambdaNode: ILambdaExpressionNode; FLambdaNode: ILambdaExpressionNode;
FClosureScope: IExecutionScope; FClosureScope: IExecutionScope;
@@ -101,22 +106,18 @@ type
const AClosureScope: IExecutionScope; const AClosureScope: IExecutionScope;
const AUpvalues: TArray<IValueCell> const AUpvalues: TArray<IValueCell>
); );
// ICallable implementation (the generic, slower path) function GetArity: Integer; override;
function GetArity: Integer; function Invoke(const AVisitor: IAstVisitor; const ASelf: TDataValue; const AArgNodes: TList<IAstNode>): TDataValue; override;
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;
end; end;
TNativeClosure = class(TInterfacedObject, TDataValue.ICallable) TNativeClosure = class(TClosure)
private private
FMethod: TNativeFunction; FMethod: TNativeFunction;
FArity: Integer; FArity: Integer;
public public
constructor Create(const AMethod: TNativeFunction; AArity: Integer); constructor Create(const AMethod: TNativeFunction; AArity: Integer);
// ICallable implementation function GetArity: Integer; override;
function GetArity: Integer; function Invoke(const AVisitor: IAstVisitor; const ASelf: TDataValue; const AArgNodes: TList<IAstNode>): TDataValue; override;
function Invoke(const AVisitor: IInterface; const ASelf: TDataValue; const AArgs: TArray<TDataValue>): TDataValue;
end; end;
// --- Native Functions Implementation --- // --- Native Functions Implementation ---
@@ -169,15 +170,16 @@ begin
Result := Length(FLambdaNode.Parameters); Result := Length(FLambdaNode.Parameters);
end; end;
// This is the generic, slightly slower path, kept for compatibility with ICallable. function TClosureValue.Invoke(const AVisitor: IAstVisitor; const ASelf: TDataValue; const AArgNodes: TList<IAstNode>): TDataValue;
function TClosureValue.Invoke(const AVisitor: IInterface; const ASelf: TDataValue; const AArgs: TArray<TDataValue>): TDataValue;
var var
i: Integer; i: Integer;
descriptor: IScopeDescriptor; descriptor: IScopeDescriptor;
callScope: IExecutionScope; callScope: IExecutionScope;
adr: TResolvedAddress; adr: TResolvedAddress;
callVisitor: IAstVisitor;
begin begin
if (AArgNodes.Count <> GetArity) then
raise EArgumentException.CreateFmt('Argument count mismatch: expected %d, got %d', [GetArity, AArgNodes.Count]);
descriptor := FLambdaNode.ScopeDescriptor; descriptor := FLambdaNode.ScopeDescriptor;
if not Assigned(descriptor) then if not Assigned(descriptor) then
raise EParserError.Create('Lambda has no scope descriptor. Did the binder run?'); raise EParserError.Create('Lambda has no scope descriptor. Did the binder run?');
@@ -189,52 +191,12 @@ begin
adr.SlotIndex := 0; adr.SlotIndex := 0;
callScope[adr].Value := ASelf; 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 for i := 0 to AArgNodes.Count - 1 do
begin begin
adr.SlotIndex := FLambdaNode.Parameters[i].Address.SlotIndex; 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); callScope[adr].Value := AArgNodes[i].Accept(AVisitor);
end; end;
// Execute the body with a new visitor for the new scope
Result := FLambdaNode.Body.Accept((AVisitor as TEvaluatorVisitor).CreateVisitorForScope(callScope)); Result := FLambdaNode.Body.Accept((AVisitor as TEvaluatorVisitor).CreateVisitorForScope(callScope));
end; end;
@@ -252,14 +214,20 @@ begin
Result := FArity; Result := FArity;
end; 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 begin
// Arity check if (FArity <> -1) and (AArgNodes.Count <> FArity) then
if (FArity <> -1) and (Length(AArgs) <> FArity) then raise EArgumentException.CreateFmt('Argument count mismatch: expected %d, got %d', [FArity, AArgNodes.Count]);
raise EArgumentException.CreateFmt('Argument count mismatch: expected %d, got %d', [FArity, Length(AArgs)]);
// AVisitor and ASelf are ignored for native calls. // Evaluate argument nodes to get values for the native function
Result := FMethod(AArgs); SetLength(argValues, AArgNodes.Count);
for i := 0 to AArgNodes.Count - 1 do
argValues[i] := AArgNodes[i].Accept(AVisitor);
Result := FMethod(argValues);
end; end;
{ TEvaluatorVisitor } { TEvaluatorVisitor }
@@ -296,36 +264,16 @@ end;
function TEvaluatorVisitor.VisitFunctionCall(const Node: IFunctionCallNode): TDataValue; function TEvaluatorVisitor.VisitFunctionCall(const Node: IFunctionCallNode): TDataValue;
var var
calleeValue: TDataValue; calleeValue: TDataValue;
callable: TDataValue.ICallable;
i: Integer;
begin begin
calleeValue := Node.Callee.Accept(Self); calleeValue := Node.Callee.Accept(Self);
if calleeValue.Kind <> vkCallable then // Check if the value holds an interface and if that interface supports our invocation contract.
raise EArgumentException.Create('Expression is not a callable value.'); if (calleeValue.Kind <> vkInterface) or not (calleeValue.AsInterface is TClosure) then
raise EArgumentException.Create('Expression is not invokable in this context.');
callable := calleeValue.AsCallable; // "Tell, Don't Ask": Simply tell the object to invoke itself.
Result := (calleeValue.AsInterface as TClosure).Invoke(Self, calleeValue, Node.Arguments);
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; end;
end;
{ TEvaluatorVisitor }
function TEvaluatorVisitor.VisitAddSeriesItem(const Node: IAddSeriesItemNode): TDataValue; function TEvaluatorVisitor.VisitAddSeriesItem(const Node: IAddSeriesItemNode): TDataValue;
var var
@@ -531,7 +479,7 @@ begin
for i := 0 to High(sourceAddresses) do for i := 0 to High(sourceAddresses) do
capturedCells[i] := FScope.GetCell(sourceAddresses[i]); capturedCells[i] := FScope.GetCell(sourceAddresses[i]);
Result := TClosureValue.Create(Node, FScope, capturedCells); Result := TClosureValue.Create(Node, FScope, capturedCells) as IInterface;
end; end;
function TEvaluatorVisitor.VisitBinaryExpression(const Node: IBinaryExpressionNode): TDataValue; function TEvaluatorVisitor.VisitBinaryExpression(const Node: IBinaryExpressionNode): TDataValue;
+1 -1
View File
@@ -1,4 +1,4 @@
unit Myc.Data.Types; unit Myc.Data.Types deprecated;
interface interface
+40 -20
View File
@@ -9,22 +9,15 @@ uses
Myc.Data.Decimal; Myc.Data.Decimal;
type type
TDataValueKind = (vkVoid, vkScalar, vkCallable, vkText, vkSeries, vkRecordSeries, vkRecord, vkMemberSeries); TDataValueKind = (vkVoid, vkScalar, vkInterface, vkText, vkSeries, vkRecordSeries, vkRecord, vkMemberSeries, vkGeneric);
TDataValue = record TDataValue = record
public public
type type
ICallable = interface
function GetArity: Integer;
function Invoke(const AVisitor: IInterface; const ASelf: TDataValue; const AArgs: TArray<TDataValue>): TDataValue;
property Arity: Integer read GetArity;
end;
TVal<T> = class(TInterfacedObject) TVal<T> = class(TInterfacedObject)
Value: T; Value: T;
constructor Create(const AValue: T); constructor Create(const AValue: T);
end; end;
private private
var var
FKind: TDataValueKind; FKind: TDataValueKind;
@@ -37,8 +30,11 @@ type
class function Void: TDataValue; inline; static; class function Void: TDataValue; inline; static;
class operator Implicit(const AValue: TScalar): TDataValue; overload; inline; class operator Implicit(const AValue: TScalar): TDataValue; overload; inline;
class operator Implicit(const AValue: ICallable): TDataValue; overload; inline;
class operator Implicit(const AValue: String): TDataValue; overload; inline; class operator Implicit(const AValue: String): TDataValue; overload; inline;
class operator Implicit(const AValue: IInterface): TDataValue; overload; inline;
class function From<T: record>(const [ref] AValue: T): TDataValue; static; inline;
function AsVal<T: record>: TVal<T>; inline;
class function FromSeries(const [ref] AValue: TScalarSeries): TDataValue; static; inline; class function FromSeries(const [ref] AValue: TScalarSeries): TDataValue; static; inline;
class function FromRecordSeries(const [ref] AValue: TScalarRecordSeries): TDataValue; static; inline; class function FromRecordSeries(const [ref] AValue: TScalarRecordSeries): TDataValue; static; inline;
@@ -46,13 +42,15 @@ type
class function FromMemberSeries(const [ref] AValue: TScalarMemberSeries): TDataValue; static; inline; class function FromMemberSeries(const [ref] AValue: TScalarMemberSeries): TDataValue; static; inline;
function AsScalar: TScalar; inline; function AsScalar: TScalar; inline;
function AsCallable: ICallable; inline; function AsInterface: IInterface; inline;
function AsText: String; inline; function AsText: String; inline;
function AsRecordSeries: TVal<TScalarRecordSeries>; inline; function AsRecordSeries: TVal<TScalarRecordSeries>; inline;
function AsRecord: TVal<TScalarRecord>; inline; function AsRecord: TVal<TScalarRecord>; inline;
function AsMemberSeries: TVal<TScalarMemberSeries>; inline; function AsMemberSeries: TVal<TScalarMemberSeries>; inline;
function AsSeries: TVal<TScalarSeries>; inline; function AsSeries: TVal<TScalarSeries>; inline;
procedure SetVoid;
function ToString: String; function ToString: String;
property IsVoid: Boolean read GetIsVoid; property IsVoid: Boolean read GetIsVoid;
property Kind: TDataValueKind read GetKind; property Kind: TDataValueKind read GetKind;
@@ -60,6 +58,9 @@ type
implementation implementation
uses
TypInfo;
{ TDataValue.TVal<T> } { TDataValue.TVal<T> }
constructor TDataValue.TVal<T>.Create(const AValue: T); constructor TDataValue.TVal<T>.Create(const AValue: T);
@@ -72,15 +73,15 @@ end;
class operator TDataValue.Initialize(out Dest: TDataValue); class operator TDataValue.Initialize(out Dest: TDataValue);
begin begin
Dest.FKind := vkVoid; // Geändert Dest.FKind := vkVoid;
Dest.FInterface := nil; Dest.FInterface := nil;
end; end;
function TDataValue.AsCallable: ICallable; function TDataValue.AsInterface: IInterface;
begin begin
if (FKind <> vkCallable) then if (FKind <> vkInterface) then
raise EInvalidCast.Create('Cannot read value as a Callable.'); raise EInvalidCast.Create('Cannot read value as an Interface.');
Result := ICallable(FInterface); Result := FInterface;
end; end;
function TDataValue.AsMemberSeries: TVal<TScalarMemberSeries>; function TDataValue.AsMemberSeries: TVal<TScalarMemberSeries>;
@@ -90,6 +91,13 @@ begin
Result := TVal<TScalarMemberSeries>(FInterface); Result := TVal<TScalarMemberSeries>(FInterface);
end; end;
function TDataValue.AsVal<T>: TVal<T>;
begin
if (FKind <> vkGeneric) then
raise EInvalidCast.Create('Cannot read value as ' + PTypeInfo(TypeInfo(T)).Name + '.');
Result := TVal<T>(FInterface);
end;
function TDataValue.AsRecord: TVal<TScalarRecord>; function TDataValue.AsRecord: TVal<TScalarRecord>;
begin begin
if (FKind <> vkRecord) then if (FKind <> vkRecord) then
@@ -125,6 +133,12 @@ begin
Result := (FInterface as TVal<String>).Value; Result := (FInterface as TVal<String>).Value;
end; end;
class function TDataValue.From<T>(const [ref] AValue: T): TDataValue;
begin
Result.FKind := vkGeneric;
Result.FInterface := TVal<T>.Create(AValue);
end;
class function TDataValue.FromMemberSeries(const [ref] AValue: TScalarMemberSeries): TDataValue; class function TDataValue.FromMemberSeries(const [ref] AValue: TScalarMemberSeries): TDataValue;
begin begin
Result.FKind := vkMemberSeries; Result.FKind := vkMemberSeries;
@@ -153,9 +167,9 @@ begin
Result.FScalar := Default(TScalar); Result.FScalar := Default(TScalar);
end; end;
class operator TDataValue.Implicit(const AValue: ICallable): TDataValue; class operator TDataValue.Implicit(const AValue: IInterface): TDataValue;
begin begin
Result.FKind := vkCallable; Result.FKind := vkInterface;
Result.FInterface := AValue; Result.FInterface := AValue;
Result.FScalar := Default(TScalar); Result.FScalar := Default(TScalar);
end; end;
@@ -176,7 +190,7 @@ end;
function TDataValue.GetIsVoid: Boolean; function TDataValue.GetIsVoid: Boolean;
begin begin
Result := FKind = vkVoid; // Geändert Result := FKind = vkVoid;
end; end;
function TDataValue.GetKind: TDataValueKind; function TDataValue.GetKind: TDataValueKind;
@@ -184,17 +198,23 @@ begin
Result := FKind; Result := FKind;
end; end;
procedure TDataValue.SetVoid;
begin
FKind := vkVoid;
FInterface := nil;
end;
function TDataValue.ToString: String; function TDataValue.ToString: String;
begin begin
case FKind of case FKind of
vkScalar: Result := FScalar.ToString; vkScalar: Result := FScalar.ToString;
vkCallable: Result := '<callable>'; vkInterface: Result := '<interface>';
vkText: Result := AsText; vkText: Result := AsText;
vkSeries: Result := '<series>'; vkSeries: Result := '<series>';
vkRecordSeries: Result := '<record_series>'; vkRecordSeries: Result := '<record_series>';
vkRecord: Result := '<record>'; vkRecord: Result := '<record>';
vkMemberSeries: Result := '<member_series>'; vkMemberSeries: Result := '<member_series>';
vkVoid: Result := '<void>'; // Geändert vkVoid: Result := '<void>';
else else
Result := '[Unknown DataValue]'; Result := '[Unknown DataValue]';
end; end;
+1
View File
@@ -9,6 +9,7 @@ uses
Myc.Data.Pipeline, Myc.Data.Pipeline,
Myc.Data.Series, Myc.Data.Series,
Myc.Data.Types, Myc.Data.Types,
Myc.Data.Value,
Myc.Trade.Types, Myc.Trade.Types,
Myc.Trade.Indicators; Myc.Trade.Indicators;