Ast series indexer

This commit is contained in:
Michael Schimmel
2025-09-02 16:58:52 +02:00
parent 375e64411b
commit a83bbf4f54
11 changed files with 734 additions and 79 deletions
+154 -22
View File
@@ -31,6 +31,7 @@ type
function VisitBlockExpression(const Node: IBlockExpressionNode): TAstValue; virtual;
function VisitVariableDeclaration(const Node: IVariableDeclarationNode): TAstValue; virtual;
function VisitAssignment(const Node: IAssignmentNode): TAstValue; virtual;
function VisitIndexer(const Node: IIndexerNode): TAstValue; virtual;
end;
// TDebugEvaluatorVisitor now overrides all visit methods for full tracing
@@ -59,17 +60,25 @@ type
function VisitBlockExpression(const Node: IBlockExpressionNode): TAstValue; override;
function VisitVariableDeclaration(const Node: IVariableDeclarationNode): TAstValue; override;
function VisitAssignment(const Node: IAssignmentNode): TAstValue; override;
function VisitIndexer(const Node: IIndexerNode): TAstValue; override;
end;
// Registers all native core functions in the given scope.
procedure RegisterNativeFunctions(const AScope: IExecutionScope);
implementation
uses
Myc.Data.Decimal,
Myc.Ast.Scope,
Myc.Ast.Printer;
Myc.Ast.Printer,
Myc.Ast.RttiUtils; // Added for JsonToRecordDefinition
type
// Concrete implementation of the IEvaluatorClosure interface, private to this unit.
// The signature for a native Delphi function callable from the script.
TNativeFunction = function(const Args: TArray<TAstValue>): TAstValue;
// Concrete implementation of the IEvaluatorClosure interface for script-defined functions.
TClosureValue = class(TInterfacedObject, IEvaluatorClosure)
private
FBody: IExpressionNode;
@@ -82,6 +91,50 @@ type
constructor Create(ABody: IExpressionNode; const AParameters: TArray<IIdentifierNode>; const AClosureScope: IExecutionScope);
end;
// Concrete implementation of IEvaluatorClosure for native Delphi functions.
TNativeClosure = class(TInterfacedObject, IEvaluatorClosure)
private
FMethod: TNativeFunction;
function GetBody: IExpressionNode;
function GetParameters: TArray<IIdentifierNode>;
function GetClosureScope: IExecutionScope;
public
constructor Create(const AMethod: TNativeFunction);
property Method: TNativeFunction read FMethod;
end;
// --- Native Functions Implementation ---
function NativeCreateRecordSeries(const Args: TArray<TAstValue>): TAstValue;
var
jsonDef: string;
recordDef: TScalarRecordDefinition;
series: TScalarRecordSeries;
begin
if (Length(Args) <> 1) or (Args[0].Kind <> avkText) then
raise EArgumentException.Create('CreateRecordSeries requires one string argument.');
jsonDef := Args[0].AsText;
recordDef := TRttiAstHelper.JsonToRecordDefinition(jsonDef);
if Length(recordDef.Fields) = 0 then
raise EArgumentException.Create('Failed to parse record definition from JSON.');
series := TScalarRecordSeries.Create(recordDef);
Result := TAstValue.FromRecordSeries(series);
end;
// --- Registration Procedure ---
procedure RegisterNativeFunctions(const AScope: IExecutionScope);
var
closure: IEvaluatorClosure;
begin
// Register CreateRecordSeries
closure := TNativeClosure.Create(NativeCreateRecordSeries);
AScope.SetValue('CreateRecordSeries', TAstValue.FromClosure(closure));
end;
{ TClosureValue }
constructor TClosureValue.Create(ABody: IExpressionNode; const AParameters: TArray<IIdentifierNode>; const AClosureScope: IExecutionScope);
@@ -107,6 +160,29 @@ begin
Result := FParameters;
end;
{ TNativeClosure }
constructor TNativeClosure.Create(const AMethod: TNativeFunction);
begin
inherited Create;
FMethod := AMethod;
end;
function TNativeClosure.GetBody: IExpressionNode;
begin
Result := nil;
end;
function TNativeClosure.GetClosureScope: IExecutionScope;
begin
Result := nil;
end;
function TNativeClosure.GetParameters: TArray<IIdentifierNode>;
begin
Result := nil;
end;
{ TEvaluatorVisitor }
constructor TEvaluatorVisitor.Create(const AScope: IExecutionScope);
@@ -169,6 +245,40 @@ begin
raise EArgumentException.CreateFmt('Identifier not found: "%s"', [Node.Name]);
end;
function TEvaluatorVisitor.VisitIndexer(const Node: IIndexerNode): TAstValue;
var
baseValue, indexValue: TAstValue;
series: TScalarRecordSeries;
rec: TScalarRecord;
index: Int64;
indexScalar: TScalar;
begin
baseValue := Node.Base.Accept(Self);
indexValue := Node.Index.Accept(Self);
if (baseValue.Kind <> avkRecordSeries) then
raise EArgumentException.Create('Indexer `[]` can only be applied to a RecordSeries.');
if (indexValue.Kind <> avkScalar) then
raise EArgumentException.Create('Indexer `[]` requires a scalar integer argument.');
indexScalar := indexValue.AsScalar;
case indexScalar.Kind of
skInteger: index := indexScalar.Value.AsInteger;
skInt64: index := indexScalar.Value.AsInt64;
else
raise EArgumentException.Create('Indexer `[]` requires an integer type argument.');
end;
series := baseValue.AsRecordSeries;
if (index < 0) or (index >= series.TotalCount) then
raise EArgumentException.CreateFmt('Index %d is out of bounds for series with %d elements.', [index, series.TotalCount]);
rec := series.Items[Integer(index)];
Result := TAstValue.FromRecord(rec);
end;
function TEvaluatorVisitor.VisitVariableDeclaration(const Node: IVariableDeclarationNode): TAstValue;
var
varName: string;
@@ -192,41 +302,51 @@ end;
function TEvaluatorVisitor.VisitFunctionCall(const Node: IFunctionCallNode): TAstValue;
var
calleeValue: TAstValue;
arguments: TList<IExpressionNode>;
closure: IEvaluatorClosure;
i: Integer;
argValues: TArray<TAstValue>;
callScope: IExecutionScope;
innerVisitor: IAstVisitor;
i: Integer;
argValue: TAstValue;
begin
calleeValue := Node.Callee.Accept(Self);
arguments := Node.Arguments;
if calleeValue.Kind <> avkClosure then
raise EArgumentException.Create('Expression is not a callable closure.');
closure := calleeValue.AsClosure;
if (arguments.Count <> Length(closure.Parameters)) then
raise EArgumentException.CreateFmt('Argument count mismatch: expected %d, got %d', [Length(closure.Parameters), arguments.Count]);
callScope := TExecutionScope.Create(closure.ClosureScope);
for i := 0 to arguments.Count - 1 do
// Distinguish between native and script-defined closures.
if closure is TNativeClosure then
begin
argValue := arguments[i].Accept(Self);
callScope.SetValue(closure.Parameters[i].Name, argValue);
end;
// Native function call
SetLength(argValues, Node.Arguments.Count);
for i := 0 to Node.Arguments.Count - 1 do
begin
argValues[i] := Node.Arguments[i].Accept(Self);
end;
Result := (closure as TNativeClosure).Method(argValues);
end
else if closure is TClosureValue then
begin
// Script function call (original logic)
if (Node.Arguments.Count <> Length(closure.Parameters)) then
raise EArgumentException
.CreateFmt('Argument count mismatch: expected %d, got %d', [Length(closure.Parameters), Node.Arguments.Count]);
innerVisitor := Self.CreateVisitorForScope(callScope);
callScope := TExecutionScope.Create(closure.ClosureScope);
// Introduce 'Result' variable for imperative-style assignments within the closure.
callScope.SetValue('Result', TAstValue.Void);
for i := 0 to Node.Arguments.Count - 1 do
begin
argValues := [Node.Arguments[i].Accept(Self)];
callScope.SetValue(closure.Parameters[i].Name, argValues[0]);
end;
// The return value of the function is the value of its body expression.
// This supports both functional-style returns (direct value) and
// imperative-style returns (via an assignment, which also returns the value).
Result := closure.Body.Accept(innerVisitor);
innerVisitor := Self.CreateVisitorForScope(callScope);
callScope.SetValue('Result', TAstValue.Void);
Result := closure.Body.Accept(innerVisitor);
end
else
raise EArgumentException.Create('Unknown closure implementation type.');
end;
function TEvaluatorVisitor.VisitBinaryExpression(const Node: IBinaryExpressionNode): TAstValue;
@@ -475,6 +595,18 @@ begin
AppendLine(Format('} -> %s', [Result.ToString]));
end;
function TDebugEvaluatorVisitor.VisitIndexer(const Node: IIndexerNode): TAstValue;
begin
AppendLine('Indexer {');
Indent;
try
Result := inherited VisitIndexer(Node);
finally
Unindent;
end;
AppendLine(Format('} -> %s', [Result.ToString]));
end;
function TDebugEvaluatorVisitor.VisitTernaryExpression(const Node: ITernaryExpressionNode): TAstValue;
begin
AppendLine('TernaryExpr{');
+50 -5
View File
@@ -20,8 +20,8 @@ type
function ToString: string;
end;
// Added avkText to represent a string value.
TAstValueKind = (avkUndefined, avkScalar, avkClosure, avkText);
// Added avkRecord to represent a TScalarRecord value.
TAstValueKind = (avkUndefined, avkScalar, avkClosure, avkText, avkRecordSeries, avkRecord);
// --- Forward Declarations to break cycles ---
IExecutionScope = interface;
@@ -40,6 +40,8 @@ type
IBlockExpressionNode = interface;
IVariableDeclarationNode = interface;
IAssignmentNode = interface;
// Added forward declaration for the new indexer node.
IIndexerNode = interface;
IEvaluatorClosure = interface;
// --- Concrete Type Definitions ---
@@ -59,7 +61,7 @@ type
private
type
IVal<T> = interface
['{70210CAF-0857-4BF1-8254-B8239A155A02}']
['{7D5AD675-A008-4007-B1A4-CF7A05749510}'] // needed, do not remove
function GetValue: T;
property Value: T read GetValue;
end;
@@ -84,9 +86,13 @@ type
class function FromScalar(const AValue: TScalar): TAstValue; static;
class function FromClosure(const AValue: IEvaluatorClosure): TAstValue; static;
class function FromText(const AValue: String): TAstValue; static;
class function FromRecordSeries(const AValue: TScalarRecordSeries): TAstValue; static;
class function FromRecord(const AValue: TScalarRecord): TAstValue; static;
function AsScalar: TScalar;
function AsClosure: IEvaluatorClosure;
function AsText: String;
function AsRecordSeries: TScalarRecordSeries;
function AsRecord: TScalarRecord;
function ToString: String;
property IsVoid: Boolean read GetIsVoid;
property Kind: TAstValueKind read GetKind;
@@ -105,19 +111,18 @@ type
end;
IAstVisitor = interface
['{A58B0A8E-F438-4217-A964-6E35624A9A4A}']
function VisitConstant(const Node: IConstantNode): TAstValue;
function VisitIdentifier(const Node: IIdentifierNode): TAstValue;
function VisitBinaryExpression(const Node: IBinaryExpressionNode): TAstValue;
function VisitUnaryExpression(const Node: IUnaryExpressionNode): TAstValue;
function VisitIfExpression(const Node: IIfExpressionNode): TAstValue;
// Added visitor method for the new ternary expression node.
function VisitTernaryExpression(const Node: ITernaryExpressionNode): TAstValue;
function VisitLambdaExpression(const Node: ILambdaExpressionNode): TAstValue;
function VisitFunctionCall(const Node: IFunctionCallNode): TAstValue;
function VisitBlockExpression(const Node: IBlockExpressionNode): TAstValue;
function VisitVariableDeclaration(const Node: IVariableDeclarationNode): TAstValue;
function VisitAssignment(const Node: IAssignmentNode): TAstValue;
function VisitIndexer(const Node: IIndexerNode): TAstValue;
end;
IAstNode = interface(IInterface)
@@ -228,6 +233,16 @@ type
property Value: IExpressionNode read GetValue;
end;
// Represents an index access expression (e.g., series[index]).
IIndexerNode = interface(IExpressionNode)
{$region 'private'}
function GetBase: IExpressionNode;
function GetIndex: IExpressionNode;
{$endregion}
property Base: IExpressionNode read GetBase;
property Index: IExpressionNode read GetIndex;
end;
implementation
uses
@@ -261,6 +276,20 @@ begin
Result := IEvaluatorClosure(FInterface);
end;
function TAstValue.AsRecord: TScalarRecord;
begin
if (FKind <> avkRecord) then
raise EInvalidCast.Create('Cannot read value as Record.');
Result := (FInterface as IVal<TScalarRecord>).Value;
end;
function TAstValue.AsRecordSeries: TScalarRecordSeries;
begin
if (FKind <> avkRecordSeries) then
raise EInvalidCast.Create('Cannot read value as RecordSeries.');
Result := (FInterface as IVal<TScalarRecordSeries>).Value;
end;
function TAstValue.AsScalar: TScalar;
begin
if (FKind <> avkScalar) then
@@ -283,6 +312,20 @@ begin
Result.FScalar := Default(TScalar);
end;
class function TAstValue.FromRecord(const AValue: TScalarRecord): TAstValue;
begin
Result.FKind := avkRecord;
Result.FInterface := TVal<TScalarRecord>.Create(AValue);
Result.FScalar := Default(TScalar);
end;
class function TAstValue.FromRecordSeries(const AValue: TScalarRecordSeries): TAstValue;
begin
Result.FKind := avkRecordSeries;
Result.FInterface := TVal<TScalarRecordSeries>.Create(AValue);
Result.FScalar := Default(TScalar);
end;
class function TAstValue.FromScalar(const AValue: TScalar): TAstValue;
begin
Result.FKind := avkScalar;
@@ -313,6 +356,8 @@ begin
avkScalar: Result := FScalar.ToString;
avkClosure: Result := '<closure>';
avkText: Result := AsText;
avkRecordSeries: Result := '<record_series>';
avkRecord: Result := '<record>';
avkUndefined: Result := '<void>';
else
Result := '[Unknown AstValue]';
+19 -2
View File
@@ -33,6 +33,7 @@ type
function VisitBlockExpression(const Node: IBlockExpressionNode): TAstValue;
function VisitVariableDeclaration(const Node: IVariableDeclarationNode): TAstValue;
function VisitAssignment(const Node: IAssignmentNode): TAstValue;
function VisitIndexer(const Node: IIndexerNode): TAstValue;
end;
implementation
@@ -64,12 +65,12 @@ end;
procedure TPrettyPrintVisitor.Indent;
begin
inc(FIndentLevel, 2);
inc(FIndentLevel, 4);
end;
procedure TPrettyPrintVisitor.Unindent;
begin
dec(FIndentLevel, 2);
dec(FIndentLevel, 4);
end;
procedure TPrettyPrintVisitor.AppendLine(const S: string);
@@ -225,4 +226,20 @@ begin
Result := TAstValue.Void;
end;
function TPrettyPrintVisitor.VisitIndexer(const Node: IIndexerNode): TAstValue;
begin
AppendLine('Indexer');
Indent;
AppendLine('Base:');
Indent;
Node.Base.Accept(Self);
Unindent;
AppendLine('Index:');
Indent;
Node.Index.Accept(Self);
Unindent;
Unindent;
Result := TAstValue.Void;
end;
end.
+44
View File
@@ -33,6 +33,8 @@ type
class function VarDecl(const AIdentifier: IIdentifierNode; AInitializer: IExpressionNode): IVariableDeclarationNode; static;
class function Assign(const AIdentifier: IIdentifierNode; const AValue: IExpressionNode): IAssignmentNode; static;
class function AssignResult(const AValue: IExpressionNode): IAssignmentNode; static;
// Added factory for the new indexer node.
class function Indexer(const ABase: IExpressionNode; const AIndex: IExpressionNode): IIndexerNode; static;
end;
implementation
@@ -183,6 +185,19 @@ type
function Accept(const Visitor: IAstVisitor): TAstValue; override;
end;
{ TIndexerNode }
// Concrete implementation for the indexer node.
TIndexerNode = class(TAstNode, IIndexerNode)
private
FBase: IExpressionNode;
FIndex: IExpressionNode;
function GetBase: IExpressionNode;
function GetIndex: IExpressionNode;
public
constructor Create(const ABase: IExpressionNode; const AIndex: IExpressionNode);
function Accept(const Visitor: IAstVisitor): TAstValue; override;
end;
{ TConstantNode }
constructor TConstantNode.Create(AValue: TScalar);
@@ -467,6 +482,30 @@ begin
Result := FValue;
end;
{ TIndexerNode }
constructor TIndexerNode.Create(const ABase, AIndex: IExpressionNode);
begin
inherited Create;
FBase := ABase;
FIndex := AIndex;
end;
function TIndexerNode.Accept(const Visitor: IAstVisitor): TAstValue;
begin
Result := Visitor.VisitIndexer(Self);
end;
function TIndexerNode.GetBase: IExpressionNode;
begin
Result := FBase;
end;
function TIndexerNode.GetIndex: IExpressionNode;
begin
Result := FIndex;
end;
{ TAst }
class function TAst.Assign(const AIdentifier: IIdentifierNode; const AValue: IExpressionNode): IAssignmentNode;
@@ -509,6 +548,11 @@ begin
Result := TIfExpressionNode.Create(ACondition, AThenBranch, AElseBranch);
end;
class function TAst.Indexer(const ABase, AIndex: IExpressionNode): IIndexerNode;
begin
Result := TIndexerNode.Create(ABase, AIndex);
end;
class function TAst.TernaryExpr(const ACondition, AThenBranch, AElseBranch: IExpressionNode): ITernaryExpressionNode;
begin
Result := TTernaryExpressionNode.Create(ACondition, AThenBranch, AElseBranch);