766 lines
27 KiB
ObjectPascal
766 lines
27 KiB
ObjectPascal
unit Myc.Ast.Evaluator;
|
|
|
|
interface
|
|
|
|
uses
|
|
System.SysUtils,
|
|
System.Classes,
|
|
System.Generics.Collections,
|
|
Myc.Data.Scalar,
|
|
Myc.Data.Value,
|
|
Myc.Data.Keyword,
|
|
Myc.Ast.Nodes,
|
|
Myc.Ast.Scope,
|
|
Myc.Ast.Visitor;
|
|
|
|
type
|
|
// Exception for runtime errors during script evaluation
|
|
EEvaluatorException = class(EAstException);
|
|
|
|
// The standard AST evaluator for production use.
|
|
TEvaluatorVisitor = class(TAstVisitor<TDataValue>, IEvaluatorVisitor)
|
|
private
|
|
FScope: IExecutionScope;
|
|
|
|
protected
|
|
// --- Core Logic Implementation (Legacy Virtual Overrides) ---
|
|
// These are kept as virtual methods so TDebugEvaluatorVisitor can still override them.
|
|
function VisitConstant(const Node: IConstantNode): TDataValue; override;
|
|
function VisitIdentifier(const Node: IIdentifierNode): TDataValue; override;
|
|
function VisitKeyword(const Node: IKeywordNode): TDataValue; override;
|
|
|
|
function VisitParameterList(const Node: IParameterList): TDataValue; override;
|
|
function VisitArgumentList(const Node: IArgumentList): TDataValue; override;
|
|
function VisitExpressionList(const Node: IExpressionList): TDataValue; override;
|
|
function VisitRecordFieldList(const Node: IRecordFieldList): TDataValue; override;
|
|
function VisitRecordField(const Node: IRecordFieldNode): TDataValue; override;
|
|
|
|
function VisitIfExpression(const Node: IIfExpressionNode): TDataValue; override;
|
|
function VisitCondExpression(const Node: ICondExpressionNode): TDataValue; override;
|
|
function VisitLambdaExpression(const Node: ILambdaExpressionNode): TDataValue; override;
|
|
function VisitFunctionCall(const Node: IFunctionCallNode): TDataValue; override;
|
|
function VisitMacroExpansionNode(const Node: IMacroExpansionNode): TDataValue; override;
|
|
function VisitBlockExpression(const Node: IBlockExpressionNode): TDataValue; override;
|
|
function VisitVariableDeclaration(const Node: IVariableDeclarationNode): TDataValue; override;
|
|
function VisitAssignment(const Node: IAssignmentNode): TDataValue; override;
|
|
function VisitMacroDefinition(const Node: IMacroDefinitionNode): TDataValue; override;
|
|
function VisitQuasiquote(const Node: IQuasiquoteNode): TDataValue; override;
|
|
function VisitUnquote(const Node: IUnquoteNode): TDataValue; override;
|
|
function VisitUnquoteSplicing(const Node: IUnquoteSplicingNode): TDataValue; override;
|
|
function VisitIndexer(const Node: IIndexerNode): TDataValue; override;
|
|
function VisitMemberAccess(const Node: IMemberAccessNode): TDataValue; override;
|
|
function VisitRecordLiteral(const Node: IRecordLiteralNode): TDataValue; override;
|
|
function VisitCreateSeries(const Node: ICreateSeriesNode): TDataValue; override;
|
|
function VisitAddSeriesItem(const Node: IAddSeriesItemNode): TDataValue; override;
|
|
function VisitSeriesLength(const Node: ISeriesLengthNode): TDataValue; override;
|
|
function VisitRecurNode(const Node: IRecurNode): TDataValue; override;
|
|
function VisitNop(const Node: INopNode): TDataValue; override;
|
|
|
|
function VisitPipeInput(const Node: IPipeInputNode): TDataValue; override;
|
|
function VisitPipeSelectorList(const Node: IPipeSelectorList): TDataValue; override;
|
|
function VisitPipeInputList(const Node: IPipeInputList): TDataValue; override;
|
|
function VisitPipe(const Node: IPipeNode): TDataValue; override;
|
|
|
|
function IsTruthy(const AValue: TDataValue): Boolean; inline;
|
|
|
|
// Returns a closure that can create the correct type of visitor for a lambda's body.
|
|
function CreateVisitorFactory: TEvaluatorFactory; virtual;
|
|
|
|
property Scope: IExecutionScope read FScope;
|
|
public
|
|
constructor Create(const AScope: IExecutionScope);
|
|
|
|
// Executes an AST with proper TCO handling. This is the main entry point.
|
|
function Execute(const RootNode: IAstNode): TDataValue;
|
|
class procedure HandleTCO(var ResultValue: TDataValue); static;
|
|
end;
|
|
|
|
implementation
|
|
|
|
uses
|
|
System.TypInfo,
|
|
System.Generics.Defaults,
|
|
Myc.Ast,
|
|
Myc.Data.Decimal,
|
|
Myc.Data.Series,
|
|
Myc.Data.Stream,
|
|
Myc.Data.Stream.Pipes,
|
|
Myc.Data.Scalar.JSON,
|
|
Myc.Ast.Types;
|
|
|
|
// Helper type for TCO via trampolining.
|
|
type
|
|
TThunk = record
|
|
Callee: TDataValue;
|
|
Args: TArray<TDataValue>;
|
|
Recur: Boolean;
|
|
constructor Create(const ACallee: TDataValue; const AArgs: TArray<TDataValue>; ARecur: Boolean);
|
|
end;
|
|
|
|
constructor TThunk.Create(const ACallee: TDataValue; const AArgs: TArray<TDataValue>; ARecur: Boolean);
|
|
begin
|
|
Callee := ACallee;
|
|
Args := AArgs;
|
|
Recur := ARecur;
|
|
end;
|
|
|
|
{ TEvaluatorVisitor }
|
|
|
|
constructor TEvaluatorVisitor.Create(const AScope: IExecutionScope);
|
|
begin
|
|
FScope := AScope;
|
|
// Base constructor calls SetupHandlers automatically
|
|
inherited Create;
|
|
Assert(Assigned(AScope));
|
|
end;
|
|
|
|
function TEvaluatorVisitor.Execute(const RootNode: IAstNode): TDataValue;
|
|
begin
|
|
if not Assigned(RootNode) then
|
|
exit(TDataValue.Void);
|
|
|
|
try
|
|
// Call the central dispatch method from TAstVisitor<T>
|
|
Result := Visit(RootNode);
|
|
HandleTCO(Result);
|
|
except
|
|
on E: EAstException do
|
|
raise; // Already a compiler/runtime exception, pass through
|
|
on E: Exception do
|
|
// Wrap unexpected RTL exceptions (e.g. EDivByZero, EConvertError)
|
|
raise EEvaluatorException.Create('Runtime Error: ' + E.Message);
|
|
end;
|
|
end;
|
|
|
|
function TEvaluatorVisitor.CreateVisitorFactory: TEvaluatorFactory;
|
|
begin
|
|
// The production visitor returns a factory that creates another production visitor.
|
|
Result := function(const AScope: IExecutionScope): IEvaluatorVisitor begin Result := TEvaluatorVisitor.Create(AScope); end;
|
|
end;
|
|
|
|
class procedure TEvaluatorVisitor.HandleTCO(var ResultValue: TDataValue);
|
|
begin
|
|
// This is the central trampoline loop for Tail Call Optimization.
|
|
try
|
|
while ResultValue.Kind = vkGeneric do
|
|
begin
|
|
var thunk := ResultValue.AsGeneric<TThunk>;
|
|
var callee := thunk.Callee.AsMethod();
|
|
ResultValue := callee(thunk.Args);
|
|
end;
|
|
except
|
|
on E: EAstException do
|
|
raise;
|
|
on E: Exception do
|
|
raise EEvaluatorException.Create('Runtime Error (TCO): ' + E.Message);
|
|
end;
|
|
end;
|
|
|
|
function TEvaluatorVisitor.IsTruthy(const AValue: TDataValue): Boolean;
|
|
begin
|
|
// Other types (Text, Series, etc.) are considered "false" in a boolean context
|
|
if (AValue.Kind <> vkScalar) then
|
|
exit(false);
|
|
|
|
case AValue.AsScalar.Kind of
|
|
TScalar.TKind.Ordinal: Result := AValue.AsScalar.Value.AsInt64 <> 0;
|
|
TScalar.TKind.Float: Result := AValue.AsScalar.Value.AsDouble <> 0.0;
|
|
TScalar.TKind.Keyword: Result := AValue.AsScalar.Value.AsInt64 <> 0;
|
|
TScalar.TKind.Boolean: Result := AValue.AsScalar.Value.AsInt64 <> 0;
|
|
TScalar.TKind.DateTime: Result := AValue.AsScalar.Value.AsDouble <> 0.0;
|
|
else
|
|
Result := false;
|
|
end;
|
|
end;
|
|
|
|
// --- Visits Implementation ---
|
|
|
|
function TEvaluatorVisitor.VisitConstant(const Node: IConstantNode): TDataValue;
|
|
begin
|
|
Result := Node.Value;
|
|
end;
|
|
|
|
function TEvaluatorVisitor.VisitKeyword(const Node: IKeywordNode): TDataValue;
|
|
begin
|
|
Result := TDataValue(TScalar.FromKeyword(Node.Value));
|
|
end;
|
|
|
|
function TEvaluatorVisitor.VisitIdentifier(const Node: IIdentifierNode): TDataValue;
|
|
begin
|
|
Result := FScope[Node.Address];
|
|
end;
|
|
|
|
function TEvaluatorVisitor.VisitLambdaExpression(const Node: ILambdaExpressionNode): TDataValue;
|
|
var
|
|
capturedCells: TArray<IValueCell>;
|
|
i: Integer;
|
|
closureScope: IExecutionScope;
|
|
visitorFactory: TEvaluatorFactory;
|
|
begin
|
|
// 1. Capture Upvalues
|
|
if Node.Upvalues <> nil then
|
|
begin
|
|
SetLength(capturedCells, Length(Node.Upvalues));
|
|
for i := 0 to High(Node.Upvalues) do
|
|
capturedCells[i] := FScope.Capture(Node.Upvalues[i]);
|
|
end
|
|
else
|
|
capturedCells := nil;
|
|
|
|
// 2. Determine Parent Scope for Closure
|
|
if Node.HasNestedLambdas then
|
|
closureScope := FScope
|
|
else
|
|
closureScope := nil;
|
|
|
|
// 3. Prepare Visitor Factory
|
|
visitorFactory := CreateVisitorFactory();
|
|
|
|
// 4. Capture Metadata for Closure
|
|
var descriptor := Node.Descriptor;
|
|
var params := Node.Parameters;
|
|
var cNode: ILambdaExpressionNode := Node;
|
|
|
|
var [unsafe] closure: TDataValue.TFunc;
|
|
closure :=
|
|
function(const ArgValues: TArray<TDataValue>): TDataValue
|
|
var
|
|
lambdaScope: IExecutionScope;
|
|
bodyVisitor: IAstVisitor;
|
|
i: Integer;
|
|
adr: TResolvedAddress;
|
|
begin
|
|
if (Length(ArgValues) <> params.Count) then
|
|
raise EEvaluatorException.CreateFmt('Argument count mismatch: expected %d, got %d', [params.Count, Length(ArgValues)]);
|
|
|
|
// Create the new execution scope for this function call.
|
|
lambdaScope := TScope.CreateScope(closureScope, descriptor, capturedCells);
|
|
|
|
// Capture the closure itself in slot 0 for 'recur' to find it (if needed).
|
|
adr.Kind := akLocalOrParent;
|
|
adr.ScopeDepth := 0;
|
|
adr.SlotIndex := 0;
|
|
lambdaScope[adr] := TDataValue(closure);
|
|
|
|
// Populate the scope with the actual parameters passed to the function.
|
|
for i := 0 to params.Count - 1 do
|
|
begin
|
|
adr := params[i].Address;
|
|
Assert(adr.ScopeDepth = 0);
|
|
lambdaScope[adr] := ArgValues[i];
|
|
end;
|
|
|
|
// Create a visitor with the new scope and execute the lambda's body.
|
|
// Use the generic Visit method for dispatch
|
|
bodyVisitor := visitorFactory(lambdaScope);
|
|
Result := bodyVisitor.Visit(cNode.Body);
|
|
end;
|
|
|
|
Result := TDataValue(closure);
|
|
end;
|
|
|
|
function TEvaluatorVisitor.VisitMacroDefinition(const Node: IMacroDefinitionNode): TDataValue;
|
|
begin
|
|
Result := TDataValue.Void;
|
|
end;
|
|
|
|
function TEvaluatorVisitor.VisitQuasiquote(const Node: IQuasiquoteNode): TDataValue;
|
|
begin
|
|
raise EEvaluatorException.Create('Quasiquote nodes are a compile-time construct and cannot be evaluated at runtime.');
|
|
end;
|
|
|
|
function TEvaluatorVisitor.VisitUnquote(const Node: IUnquoteNode): TDataValue;
|
|
begin
|
|
raise EEvaluatorException.Create('Unquote nodes are a compile-time construct and cannot be evaluated at runtime.');
|
|
end;
|
|
|
|
function TEvaluatorVisitor.VisitUnquoteSplicing(const Node: IUnquoteSplicingNode): TDataValue;
|
|
begin
|
|
raise EEvaluatorException.Create('Unquote-splicing nodes are a compile-time construct and cannot be evaluated at runtime.');
|
|
end;
|
|
|
|
function TEvaluatorVisitor.VisitFunctionCall(const Node: IFunctionCallNode): TDataValue;
|
|
var
|
|
calleeValue: TDataValue;
|
|
argValues: TArray<TDataValue>;
|
|
i: Integer;
|
|
argList: IArgumentList;
|
|
begin
|
|
if Assigned(Node.StaticTarget) then
|
|
begin
|
|
// --- Static Path (Optimized) ---
|
|
argList := Node.Arguments;
|
|
SetLength(argValues, argList.Count);
|
|
for i := 0 to argList.Count - 1 do
|
|
argValues[i] := Visit(argList[i]); // Use central dispatch
|
|
|
|
// Call static target. Exceptions here are caught by Execute/HandleTCO.
|
|
Result := Node.StaticTarget(argValues);
|
|
HandleTCO(Result);
|
|
end
|
|
else
|
|
begin
|
|
// --- Dynamic Path (Default) ---
|
|
calleeValue := Visit(Node.Callee);
|
|
if calleeValue.Kind <> vkMethod then
|
|
raise EEvaluatorException.Create('Expression is not invokable in this context.');
|
|
|
|
argList := Node.Arguments;
|
|
SetLength(argValues, argList.Count);
|
|
for i := 0 to argList.Count - 1 do
|
|
argValues[i] := Visit(argList[i]);
|
|
|
|
if Node.IsTailCall then
|
|
begin
|
|
Result := TDataValue.FromGeneric<TThunk>(TThunk.Create(calleeValue, argValues, false));
|
|
end
|
|
else
|
|
begin
|
|
Result := (calleeValue.AsMethod)(argValues);
|
|
HandleTCO(Result);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TEvaluatorVisitor.VisitMacroExpansionNode(const Node: IMacroExpansionNode): TDataValue;
|
|
begin
|
|
Result := Visit(Node.ExpandedBody);
|
|
end;
|
|
|
|
function TEvaluatorVisitor.VisitRecurNode(const Node: IRecurNode): TDataValue;
|
|
var
|
|
argValues: TArray<TDataValue>;
|
|
calleeAddress: TResolvedAddress;
|
|
calleeValue: TDataValue;
|
|
i: Integer;
|
|
begin
|
|
SetLength(argValues, Node.Arguments.Count);
|
|
for i := 0 to Node.Arguments.Count - 1 do
|
|
argValues[i] := Visit(Node.Arguments[i]);
|
|
|
|
calleeAddress.Kind := akLocalOrParent;
|
|
calleeAddress.ScopeDepth := 0;
|
|
calleeAddress.SlotIndex := 0;
|
|
calleeValue := FScope[calleeAddress];
|
|
|
|
Result := TDataValue.FromGeneric<TThunk>(TThunk.Create(calleeValue, argValues, true));
|
|
end;
|
|
|
|
function TEvaluatorVisitor.VisitAddSeriesItem(const Node: IAddSeriesItemNode): TDataValue;
|
|
var
|
|
itemValue, lookbackValue, seriesVar: TDataValue;
|
|
lookback: Int64;
|
|
begin
|
|
seriesVar := FScope[Node.Series.Address];
|
|
itemValue := Visit(Node.Value);
|
|
lookback := -1;
|
|
|
|
if Assigned(Node.Lookback) then
|
|
begin
|
|
lookbackValue := Visit(Node.Lookback);
|
|
if (lookbackValue.Kind <> vkScalar) or (lookbackValue.AsScalar.Kind <> TScalar.TKind.Ordinal) then
|
|
raise EEvaluatorException.Create('Lookback parameter must be an integer.');
|
|
lookback := lookbackValue.AsScalar.Value.AsInt64;
|
|
end;
|
|
|
|
case seriesVar.Kind of
|
|
vkRecordSeries:
|
|
begin
|
|
if (itemValue.Kind <> vkScalarRecord) then
|
|
raise EEvaluatorException.Create('Can only add record values to a TScalarRecordSeries.');
|
|
|
|
seriesVar.AsRecordSeries.Add(itemValue.AsScalarRecord, lookback);
|
|
end;
|
|
else
|
|
raise EEvaluatorException.Create('"add" operation is only supported for series types.');
|
|
end;
|
|
Result := TDataValue.Void;
|
|
end;
|
|
|
|
function TEvaluatorVisitor.VisitAssignment(const Node: IAssignmentNode): TDataValue;
|
|
begin
|
|
if Node.Target.Kind <> akIdentifier then
|
|
raise EEvaluatorException.Create('Runtime Error: Assignment target must be an identifier.');
|
|
|
|
Result := Visit(Node.Value);
|
|
FScope[Node.Target.AsIdentifier.Address] := Result;
|
|
end;
|
|
|
|
function TEvaluatorVisitor.VisitCreateSeries(const Node: ICreateSeriesNode): TDataValue;
|
|
var
|
|
def: string;
|
|
begin
|
|
def := Node.Definition.Trim;
|
|
try
|
|
if def.StartsWith('[') then
|
|
begin
|
|
var recordDef := TRttiAstHelper.JsonToRecordDefinition(def);
|
|
if recordDef.Count = 0 then
|
|
raise EEvaluatorException.Create('Failed to parse record definition from JSON array.');
|
|
var recordSeries := TScalarRecordSeries.Create(recordDef);
|
|
Result := TDataValue.FromRecordSeries(recordSeries);
|
|
end
|
|
else
|
|
begin
|
|
var scalarKind := TScalar.StringToKind(def);
|
|
Result := TDataValue.FromSeries(TScalarSeries.Create(scalarKind));
|
|
end;
|
|
except
|
|
on E: EAstException do
|
|
raise;
|
|
on E: Exception do
|
|
raise EEvaluatorException.Create('Invalid series definition: ' + E.Message);
|
|
end;
|
|
end;
|
|
|
|
function TEvaluatorVisitor.VisitIndexer(const Node: IIndexerNode): TDataValue;
|
|
var
|
|
baseValue, indexValue: TDataValue;
|
|
index: Int64;
|
|
series: ISeries;
|
|
recSeries: IScalarRecordSeries;
|
|
i, fieldCount: Integer;
|
|
values: TArray<TScalar.TValue>;
|
|
key: IKeyword;
|
|
memberSeries: ISeries;
|
|
scalarValue: TScalar;
|
|
begin
|
|
baseValue := Visit(Node.Base);
|
|
|
|
if baseValue.IsVoid then
|
|
exit(TDataValue.Void);
|
|
|
|
indexValue := Visit(Node.Index);
|
|
|
|
if (indexValue.Kind <> vkScalar) or (indexValue.AsScalar.Kind <> TScalar.TKind.Ordinal) then
|
|
raise EEvaluatorException.Create('Indexer `[]` requires an integer argument.');
|
|
|
|
index := indexValue.AsScalar.Value.AsInt64;
|
|
|
|
case baseValue.Kind of
|
|
vkSeries:
|
|
begin
|
|
series := baseValue.AsSeries;
|
|
if (index < 0) or (index >= series.TotalCount) then
|
|
raise EEvaluatorException.CreateFmt('Index %d is out of bounds for series with %d elements.', [index, series.TotalCount]);
|
|
Result := TDataValue(series.Items[Integer(index)]);
|
|
end;
|
|
vkRecordSeries:
|
|
begin
|
|
recSeries := baseValue.AsRecordSeries;
|
|
|
|
fieldCount := recSeries.Def.Count;
|
|
SetLength(values, fieldCount);
|
|
|
|
for i := 0 to fieldCount - 1 do
|
|
begin
|
|
key := recSeries.Def.Items[i].Key;
|
|
memberSeries := recSeries.Fields[key];
|
|
|
|
if (index < 0) or (index >= memberSeries.TotalCount) then
|
|
raise EEvaluatorException
|
|
.CreateFmt('Index %d is out of bounds for series with %d elements.', [index, memberSeries.TotalCount]);
|
|
|
|
scalarValue := memberSeries[index];
|
|
values[i] := scalarValue.Value;
|
|
end;
|
|
|
|
Result := TDataValue.FromScalarRecord(TScalarRecord.Create(recSeries.Def, values));
|
|
end;
|
|
else
|
|
raise EEvaluatorException.Create('Indexer `[]` is not supported for this value type.');
|
|
end;
|
|
end;
|
|
|
|
function TEvaluatorVisitor.VisitMemberAccess(const Node: IMemberAccessNode): TDataValue;
|
|
var
|
|
baseValue: TDataValue;
|
|
begin
|
|
baseValue := Visit(Node.Base);
|
|
|
|
if baseValue.IsVoid then
|
|
exit(TDataValue.Void);
|
|
|
|
case baseValue.Kind of
|
|
vkRecordSeries: Result := TDataValue.FromSeries(baseValue.AsRecordSeries.Fields[Node.Member.Value]);
|
|
vkScalarRecord: Result := TDataValue(baseValue.AsScalarRecord.Fields[Node.Member.Value]);
|
|
vkGenericRecord: Result := TDataValue(baseValue.AsGenericRecord.Fields[Node.Member.Value]);
|
|
vkStream: Result := TDataValue.FromSeries(baseValue.AsStream.Series.Fields[Node.Member.Value]);
|
|
else
|
|
raise EEvaluatorException.Create('Member access operator `.` is not supported for this value type.');
|
|
end;
|
|
end;
|
|
|
|
function TEvaluatorVisitor.VisitRecordLiteral(const Node: IRecordLiteralNode): TDataValue;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
if Assigned(Node.GenericDefinition) then
|
|
begin
|
|
var genFields: TArray<TPair<IKeyword, TDataValue>>;
|
|
SetLength(genFields, Node.Fields.Count);
|
|
|
|
for i := 0 to Node.Fields.Count - 1 do
|
|
begin
|
|
var field := Node.Fields[i];
|
|
genFields[i] := TPair<IKeyword, TDataValue>.Create(field.Key.Value, Visit(field.Value));
|
|
end;
|
|
|
|
Result := TDataValue.FromGenericRecord(TKeywordMapping<TDataValue>.Create(genFields));
|
|
end
|
|
else if Assigned(Node.ScalarDefinition) then
|
|
begin
|
|
var values: TArray<TScalar.TValue>;
|
|
SetLength(values, Node.Fields.Count);
|
|
|
|
for i := 0 to Node.Fields.Count - 1 do
|
|
begin
|
|
var field := Node.Fields[i];
|
|
var valData := Visit(field.Value);
|
|
values[i] := valData.AsScalar.Value;
|
|
end;
|
|
|
|
Result := TDataValue.FromScalarRecord(TScalarRecord.Create(Node.ScalarDefinition, values));
|
|
end
|
|
else
|
|
raise EEvaluatorException.Create('RecordLiteral has no definition (Binder/TypeChecker failure).');
|
|
end;
|
|
|
|
function TEvaluatorVisitor.VisitVariableDeclaration(const Node: IVariableDeclarationNode): TDataValue;
|
|
var
|
|
address: TResolvedAddress;
|
|
begin
|
|
if Node.Target.Kind <> akIdentifier then
|
|
raise EEvaluatorException.Create('Runtime Error: Variable declaration target must be an identifier.');
|
|
|
|
if Assigned(Node.Initializer) then
|
|
Result := Visit(Node.Initializer)
|
|
else
|
|
Result := TDataValue.Void;
|
|
|
|
address := Node.Target.AsIdentifier.Address;
|
|
|
|
if Node.IsBoxed then
|
|
begin
|
|
Assert(address.ScopeDepth = 0);
|
|
FScope.DefineBoxed(address.SlotIndex, Result);
|
|
end
|
|
else
|
|
begin
|
|
FScope[address] := Result;
|
|
end;
|
|
end;
|
|
|
|
function TEvaluatorVisitor.VisitIfExpression(const Node: IIfExpressionNode): TDataValue;
|
|
begin
|
|
if IsTruthy(Visit(Node.Condition)) then
|
|
Result := Visit(Node.ThenBranch)
|
|
else if Assigned(Node.ElseBranch) then
|
|
Result := Visit(Node.ElseBranch)
|
|
else
|
|
Result := TDataValue.Void;
|
|
end;
|
|
|
|
function TEvaluatorVisitor.VisitCondExpression(const Node: ICondExpressionNode): TDataValue;
|
|
var
|
|
pair: TCondPair;
|
|
begin
|
|
for pair in Node.Pairs do
|
|
begin
|
|
if IsTruthy(Visit(pair.Condition)) then
|
|
begin
|
|
Result := Visit(pair.Branch);
|
|
Exit;
|
|
end;
|
|
end;
|
|
// Else branch is mandatory in AST (though can be Nop/Void in source if omitted and normalized)
|
|
Result := Visit(Node.ElseBranch);
|
|
end;
|
|
|
|
function TEvaluatorVisitor.VisitBlockExpression(const Node: IBlockExpressionNode): TDataValue;
|
|
begin
|
|
// Delegate to ExpressionList visitor via central Visit dispatch (default handler maps to VisitExpressionList)
|
|
Result := Visit(Node.Expressions);
|
|
end;
|
|
|
|
function TEvaluatorVisitor.VisitExpressionList(const Node: IExpressionList): TDataValue;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
Result := TDataValue.Void;
|
|
for i := 0 to Node.Count - 1 do
|
|
Result := Visit(Node[i]);
|
|
end;
|
|
|
|
function TEvaluatorVisitor.VisitNop(const Node: INopNode): TDataValue;
|
|
begin
|
|
Result := TDataValue.Void;
|
|
end;
|
|
|
|
function TEvaluatorVisitor.VisitSeriesLength(const Node: ISeriesLengthNode): TDataValue;
|
|
var
|
|
seriesValue: TDataValue;
|
|
len: Int64;
|
|
begin
|
|
seriesValue := FScope[Node.Series.Address];
|
|
|
|
case seriesValue.Kind of
|
|
vkSeries: len := seriesValue.AsSeries.Count;
|
|
vkRecordSeries: len := seriesValue.AsRecordSeries.RecordCount;
|
|
else
|
|
raise EEvaluatorException
|
|
.CreateFmt('Cannot get length of type %s.', [GetEnumName(TypeInfo(TDataValueKind), Ord(seriesValue.Kind))]);
|
|
end;
|
|
|
|
Result := TDataValue(TScalar.FromInt64(len));
|
|
end;
|
|
|
|
// --- List Visitor Stubs (Default to Void if not implemented specifically) ---
|
|
|
|
function TEvaluatorVisitor.VisitParameterList(const Node: IParameterList): TDataValue;
|
|
begin
|
|
Result := TDataValue.Void;
|
|
end;
|
|
|
|
function TEvaluatorVisitor.VisitArgumentList(const Node: IArgumentList): TDataValue;
|
|
begin
|
|
Result := TDataValue.Void;
|
|
end;
|
|
|
|
function TEvaluatorVisitor.VisitRecordFieldList(const Node: IRecordFieldList): TDataValue;
|
|
begin
|
|
Result := TDataValue.Void;
|
|
end;
|
|
|
|
function TEvaluatorVisitor.VisitRecordField(const Node: IRecordFieldNode): TDataValue;
|
|
begin
|
|
Result := TDataValue.Void;
|
|
end;
|
|
|
|
function TEvaluatorVisitor.VisitPipeInput(const Node: IPipeInputNode): TDataValue;
|
|
begin
|
|
Result := TDataValue.Void;
|
|
end;
|
|
|
|
function TEvaluatorVisitor.VisitPipeSelectorList(const Node: IPipeSelectorList): TDataValue;
|
|
begin
|
|
Result := TDataValue.Void;
|
|
end;
|
|
|
|
function TEvaluatorVisitor.VisitPipeInputList(const Node: IPipeInputList): TDataValue;
|
|
begin
|
|
Result := TDataValue.Void;
|
|
end;
|
|
|
|
// --- Pipe Implementation ---
|
|
|
|
function TEvaluatorVisitor.VisitPipe(const Node: IPipeNode): TDataValue;
|
|
var
|
|
i: Integer;
|
|
inputNode: IPipeInputNode;
|
|
sources: TArray<IStream>;
|
|
config: TPipeConfig;
|
|
inputVal: TDataValue;
|
|
sourceSeries: IScalarRecordSeries;
|
|
outputDef: IScalarRecordDefinition;
|
|
lambdaFunc: TDataValue.TFunc;
|
|
pipeAdapter: TPipeStream.TPipeLambda;
|
|
begin
|
|
SetLength(sources, Node.Inputs.Count);
|
|
SetLength(config, Node.Inputs.Count);
|
|
|
|
// 1. Resolve Inputs & Build Config
|
|
for i := 0 to Node.Inputs.Count - 1 do
|
|
begin
|
|
inputNode := Node.Inputs[i];
|
|
inputVal := FScope[inputNode.StreamSource.Address];
|
|
|
|
// STRICT CHECK: Only vkStream is allowed.
|
|
if inputVal.Kind = vkStream then
|
|
begin
|
|
sources[i] := inputVal.AsStream;
|
|
end
|
|
else
|
|
begin
|
|
raise EEvaluatorException
|
|
.CreateFmt('Variable "%s" must be a Stream. Found type "%s".', [inputNode.StreamSource.Name, inputVal.Kind.ToString]);
|
|
end;
|
|
|
|
sourceSeries := sources[i].Series;
|
|
var srcDef := sourceSeries.Def;
|
|
var selectors := inputNode.Selectors;
|
|
|
|
SetLength(config[i], selectors.Count);
|
|
for var k := 0 to selectors.Count - 1 do
|
|
begin
|
|
var key := selectors[k].Value;
|
|
var idx := srcDef.IndexOf(key);
|
|
// Runtime safety check (TypeChecker usually handles this)
|
|
if idx < 0 then
|
|
raise EEvaluatorException.CreateFmt('Field ":%s" missing in stream "%s".', [key.Name, inputNode.StreamSource.Name]);
|
|
|
|
var fieldKind := srcDef.Items[idx].Value;
|
|
config[i][k] := TScalarRecordField.Create(key, fieldKind);
|
|
end;
|
|
end;
|
|
|
|
// 2. Get Output Definition
|
|
if (Node.StaticType = nil) or (Node.StaticType.Kind <> stRecordSeries) then
|
|
raise EEvaluatorException.Create(
|
|
'Internal Error: Pipe requires a RecordSeries static type definition. Lambda must return a Record.');
|
|
|
|
outputDef := Node.StaticType.Definition;
|
|
|
|
// 3. Compile Lambda
|
|
lambdaFunc := Visit(Node.Transformation).AsMethod();
|
|
|
|
// 4. Create Adapter
|
|
pipeAdapter :=
|
|
function(const SourceSeries: array of ISeries; out Results: array of TScalar.TValue): Boolean
|
|
var
|
|
scriptArgs: TArray<TDataValue>;
|
|
scriptResult: TDataValue;
|
|
k: Integer;
|
|
resRec: IScalarRecord;
|
|
begin
|
|
SetLength(scriptArgs, Length(SourceSeries));
|
|
// Pass Scalars (Current Items)
|
|
for k := 0 to High(SourceSeries) do
|
|
scriptArgs[k] := TDataValue(SourceSeries[k].Items[0]);
|
|
|
|
scriptResult := lambdaFunc(scriptArgs);
|
|
|
|
// Void -> Filtered out (no emit)
|
|
if scriptResult.IsVoid then
|
|
exit(False);
|
|
|
|
// STRICT CHECK: Only ScalarRecord allowed
|
|
if scriptResult.Kind = vkScalarRecord then
|
|
begin
|
|
resRec := scriptResult.AsScalarRecord;
|
|
|
|
// Integrity check: Runtime Record must match Static Definition
|
|
if resRec.Def.Count <> Length(Results) then
|
|
raise EEvaluatorException.Create('Pipe lambda returned record with incorrect field count/layout.');
|
|
|
|
for k := 0 to resRec.Def.Count - 1 do
|
|
Results[k] := resRec.Items[k].Value.Value;
|
|
end
|
|
else
|
|
begin
|
|
// Scalars are forbidden
|
|
raise EEvaluatorException
|
|
.CreateFmt('Pipe lambda returned invalid type "%s". Must return a Record or Void.', [scriptResult.Kind.ToString]);
|
|
end;
|
|
|
|
Result := True;
|
|
end;
|
|
|
|
var pipe := TPipeStream.Create(config, outputDef, sources, pipeAdapter);
|
|
|
|
// Result is a Stream
|
|
Result := TDataValue.FromStream(pipe);
|
|
end;
|
|
|
|
end.
|