This commit is contained in:
Michael Schimmel
2026-01-04 18:48:04 +01:00
parent a4afae6f39
commit 991b998cb1
17 changed files with 534 additions and 102 deletions
+17
View File
@@ -55,6 +55,8 @@ type
function VisitMacroExpansionNode(const Node: IAstNode): Boolean;
function VisitNop(const Node: IAstNode): Boolean;
function VisitTuple(const Node: IAstNode): Boolean;
// Pipe Support (Structural Check)
function VisitPipeInput(const Node: IAstNode): Boolean;
function VisitPipeSelectorList(const Node: IAstNode): Boolean;
@@ -132,6 +134,8 @@ begin
Register(akRecur, VisitRecurNode);
Register(akNop, VisitNop);
Register(akTuple, VisitTuple);
// Pipes
Register(akPipeInput, VisitPipeInput);
Register(akPipeSelectorList, VisitPipeSelectorList);
@@ -378,4 +382,17 @@ begin
Result := IsNodePure(P.Inputs) and IsNodePure(P.Transformation);
end;
function TPurityAnalyzer.VisitTuple(const Node: IAstNode): Boolean;
var
item: IAstNode;
begin
// Ein Tupel ist rein, wenn alle Elemente rein sind.
for item in Node.AsTuple.Elements do
begin
if not IsNodePure(item) then
exit(False);
end;
Result := True;
end;
end.
+36
View File
@@ -38,6 +38,7 @@ type
function VisitRecurNode(const Node: IAstNode): IAstNode;
function VisitMacroExpansionNode(const Node: IAstNode): IAstNode;
function VisitFunctionCall(const Node: IAstNode): IAstNode;
function VisitTuple(const Node: IAstNode): IAstNode;
protected
procedure SetupHandlers; override;
@@ -81,6 +82,7 @@ begin
Register(akRecur, VisitRecurNode);
Register(akMacroExpansion, VisitMacroExpansionNode);
Register(akFunctionCall, VisitFunctionCall);
Register(akTuple, VisitTuple);
end;
class function TAstTCO.Optimize(const RootNode: IAstNode): IAstNode;
@@ -314,4 +316,38 @@ begin
Result := TAst.FunctionCall(Node.Identity, newCallee, newArgsList, C.StaticType, isTailCall, C.StaticTarget, C.IsTargetPure);
end;
function TAstTCO.VisitTuple(const Node: IAstNode): IAstNode;
var
T: ITupleNode;
newElements: TArray<IAstNode>;
i: Integer;
hasChanged: Boolean;
savedNextIsTail: Boolean;
begin
T := Node.AsTuple;
savedNextIsTail := FNextIsTail; // Zustand sichern (wahrscheinlich irrelevant, aber sauber)
// Elemente in einem Tupel sind NIEMALS in Tail-Position
FNextIsTail := False;
hasChanged := False;
SetLength(newElements, Length(T.Elements));
try
for i := 0 to High(T.Elements) do
begin
newElements[i] := Accept(T.Elements[i]);
if newElements[i] <> T.Elements[i] then
hasChanged := True;
end;
finally
FNextIsTail := savedNextIsTail;
end;
if hasChanged then
Result := TAst.Tuple(Node.Identity, newElements, T.StaticType)
else
Result := Node;
end;
end.
+135 -48
View File
@@ -70,6 +70,7 @@ type
function VisitRecordLiteral(const Node: IAstNode): IAstNode;
function VisitConstant(const Node: IAstNode): IAstNode;
function VisitKeyword(const Node: IAstNode): IAstNode;
function VisitTuple(const Node: IAstNode): IAstNode;
// Pipe Support
function VisitPipeInput(const Node: IAstNode): IAstNode;
@@ -238,6 +239,7 @@ begin
Register(akRecordLiteral, VisitRecordLiteral);
Register(akConstant, VisitConstant);
Register(akKeyword, VisitKeyword);
Register(akTuple, VisitTuple);
// Pipe Support
Register(akPipeInput, VisitPipeInput);
@@ -292,7 +294,6 @@ end;
function TTypeChecker.VisitConstant(const Node: IAstNode): IAstNode;
begin
// Base implementation already returns Node, but here we explicitly confirm identity for clarity
Result := Node;
end;
@@ -301,6 +302,98 @@ begin
Result := Node;
end;
function TTypeChecker.VisitTuple(const Node: IAstNode): IAstNode;
var
T: ITupleNode;
newElements: TArray<IAstNode>;
elementTypes: TArray<IStaticType>;
i: Integer;
// Inference variables
firstType: IStaticType;
isHomogeneous: Boolean;
commonDim: TArray<Integer>;
newDim: TArray<Integer>;
finalType: IStaticType;
begin
T := Node.AsTuple;
var count := Length(T.Elements);
SetLength(newElements, count);
SetLength(elementTypes, count);
// 1. Visit Children
// Recursively type-check all elements first to determine their static types.
for i := 0 to count - 1 do
begin
newElements[i] := Accept(T.Elements[i]);
elementTypes[i] := newElements[i].AsTypedNode.StaticType;
end;
// 2. Inference Logic: Tuple vs. Vector vs. Matrix
if count = 0 then
begin
// Empty Tuple -> stTuple (safest fallback, effectively Void)
finalType := TTypes.CreateTuple([]);
end
else
begin
firstType := elementTypes[0];
isHomogeneous := True;
// Check for Homogeneity (Exact type equality of all elements)
for i := 1 to count - 1 do
begin
if not firstType.IsEqual(elementTypes[i]) then
begin
isHomogeneous := False;
break;
end;
end;
if isHomogeneous then
begin
// It is at least a Vector.
// Check if it should be promoted to a Matrix (i.e., elements are Vectors or Matrices).
if firstType.Kind = stVector then
begin
// Vector of Vectors -> Matrix (2D)
// New Dimensions = [OuterCount, InnerCount]
newDim := [count, firstType.AsVector.Count];
finalType := TTypes.CreateMatrix(firstType.AsVector.ElementType, newDim);
end
else if firstType.Kind = stMatrix then
begin
// Vector of Matrices -> Higher dimensional Matrix (N+1)
// New Dimensions = [OuterCount, Dim0, Dim1...]
commonDim := firstType.AsMatrix.Dimensions;
SetLength(newDim, Length(commonDim) + 1);
newDim[0] := count;
for i := 0 to High(commonDim) do
newDim[i + 1] := commonDim[i];
finalType := TTypes.CreateMatrix(firstType.AsMatrix.ElementType, newDim);
end
else
begin
// Base case: Homogeneous Scalars/Records -> Vector (1D)
finalType := TTypes.CreateVector(firstType, count);
end;
end
else
begin
// Heterogeneous types -> Standard Tuple
finalType := TTypes.CreateTuple(elementTypes);
end;
end;
// 3. Return the new node with the inferred type definition
Result := TAst.Tuple(Node.Identity, newElements, finalType);
end;
function TTypeChecker.VisitIdentifier(const Node: IAstNode): IAstNode;
var
I: IIdentifierNode;
@@ -328,12 +421,6 @@ var
args: IArgumentList;
begin
R := Node.AsRecur;
// Use inherited to transform arguments, then reconstruct with type Void
// Note: inherited VisitRecurNode returns IAstNode which is a RecurNode.
// We can call Accept on arguments list directly to avoid intermediate node creation if desired,
// but relying on inherited logic keeps it consistent.
// Efficient approach: Accept the arguments list directly (it's a child).
args := Accept(R.Arguments).AsArgumentList;
Result := TAst.Recur(Node.Identity, args, TTypes.Void);
end;
@@ -422,17 +509,37 @@ var
newBlock: IBlockExpressionNode;
blockType: IStaticType;
exprs: IExpressionList;
i: Integer;
begin
// Inherited logic transforms all expressions in the list
newBlock := inherited VisitBlockExpression(Node).AsBlockExpression;
// 1. Transform children via inherited logic
var transformedNode := inherited VisitBlockExpression(Node);
// Safety check: Did the transformer return a node?
if not Assigned(transformedNode) then
raise ECompilationFailed.Create([TCompilerError.Create(elError, 'Internal Error: Block transformation returned nil.', Node)]);
newBlock := transformedNode.AsBlockExpression;
exprs := newBlock.Expressions;
// 2. Validate expressions
if exprs.Count > 0 then
blockType := exprs[exprs.Count - 1].AsTypedNode.StaticType
else
blockType := TTypes.Void;
begin
// Check for NIL entries which indicate failed transformation of children
for i := 0 to exprs.Count - 1 do
begin
if exprs[i] = nil then
raise ECompilationFailed.Create(
[TCompilerError.Create(elError, Format('Internal Error: Block expression #%d transformed to nil.', [i]), Node)]);
end;
// The type of the block is the type of the last expression
blockType := exprs[exprs.Count - 1].AsTypedNode.StaticType;
end
else
begin
blockType := TTypes.Void;
end;
// Return new block with calculated type
Result := TAst.Block(Node.Identity, exprs, blockType);
end;
@@ -451,7 +558,6 @@ var
begin
L := Node.AsLambdaExpression;
// 1. Resolve Upvalue Types
var upvalueAddrs := L.Upvalues;
SetLength(upvalueTypes, Length(upvalueAddrs));
@@ -466,7 +572,6 @@ begin
upvalueTypes[i] := FCurrentContext.LookupType(lookupAddr);
end;
// 2. Enter New Scope
FCurrentContext := TTypeContext.Create(FCurrentContext, L.Layout, upvalueTypes, nil);
try
SetLength(newParams, L.Parameters.Count);
@@ -475,8 +580,6 @@ begin
for i := 0 to L.Parameters.Count - 1 do
begin
paramIdent := L.Parameters[i];
// Check if there is already a type assigned (e.g. injected by Pipe Visitor)
injectedType := paramIdent.AsTypedNode.StaticType;
if injectedType.Kind = stUnknown then
@@ -517,10 +620,7 @@ var
bestSig: IMethodSignature;
match: Boolean;
begin
// Use inherited to visit Callee and Arguments first
newCall := inherited VisitFunctionCall(Node).AsFunctionCall;
// Now analyze types on the transformed children
var newCallee := newCall.Callee;
var newArgs := newCall.Arguments;
@@ -570,7 +670,6 @@ begin
if Assigned(FLog) then
FLog.AddError(Format('Cannot invoke type %s as a function.', [calleeType.ToString]), Node);
// Return new node with Types
Result := TAst.FunctionCall(Node.Identity, newCallee, newArgs, retType, newCall.IsTailCall, nil, False);
end;
@@ -605,7 +704,18 @@ begin
if baseType.Kind = stSeries then
elemType := baseType.AsSeries.ElementType
else if baseType.Kind = stRecordSeries then
elemType := TTypes.CreateRecord(baseType.AsRecord.Definition);
elemType := TTypes.CreateRecord(baseType.AsRecord.Definition)
else if baseType.Kind = stTuple then
begin
// NEW: Tuple Indexing (requires constant index for strong typing!)
if (newIndex.Kind = akConstant) and (newIndex.AsConstant.Value.Kind = vkScalar) then
begin
var idx := newIndex.AsConstant.Value.AsScalar.Value.AsInt64;
var tpl := baseType.AsTuple;
if (idx >= 0) and (idx < tpl.Count) then
elemType := tpl.Elements[Integer(idx)];
end;
end;
elemType := ApplyOptionality(elemType, isOpt);
Result := TAst.Indexer(Node.Identity, newBase, newIndex, elemType);
@@ -655,10 +765,8 @@ var
newFieldList: IRecordFieldList;
begin
R := Node.AsRecordLiteral;
// Transform fields using inherited recursion
newFieldList := inherited VisitRecordFieldList(R.Fields).AsRecordFieldList;
// Now analyze the transformed fields
var count := newFieldList.Count;
SetLength(fieldTypes, count);
SetLength(scalarFieldTypes, count);
@@ -670,7 +778,6 @@ begin
key := field.Key.Value;
valType := field.Value.AsTypedNode.StaticType;
// Check if strict scalar (no optionals allowed in packed ScalarRecord)
if (valType.Kind in [stOrdinal, stFloat, stBoolean, stDateTime, stKeyword]) and (not valType.IsOptional) then
begin
var kind: TScalar.TKind;
@@ -693,7 +800,6 @@ begin
fieldTypes[i] := TPair<IKeyword, IStaticType>.Create(key, valType);
end;
// Build Definition
var scalarDef: IScalarRecordDefinition := nil;
var genericDef: IGenericRecordDefinition := nil;
var resultType: IStaticType;
@@ -720,9 +826,8 @@ var
begin
C := Node.AsCreateSeries;
def := C.Definition;
// Simple heuristic for type
if def.StartsWith('[') then
elemType := TTypes.CreateRecord(nil) // Placeholder, normally parses JSON
elemType := TTypes.CreateRecord(nil)
else
elemType := TTypes.FromScalarKind(TScalar.StringToKind(def));
@@ -750,11 +855,9 @@ var
sourceType: IStaticType;
begin
P := Node.AsPipeInput;
// Transform source identifier (resolves type)
newSource := Accept(P.StreamSource).AsIdentifier;
sourceType := newSource.AsTypedNode.StaticType;
// 1. Verify Source is a Series-compatible type
if (sourceType.Kind <> stUnknown) then
begin
if not ((sourceType.Kind = stSeries) or (sourceType.Kind = stRecordSeries)) then
@@ -767,7 +870,6 @@ begin
end
else if (sourceType.Kind = stRecordSeries) then
begin
// 2. Verify Selectors exist in Record Definition
var def := sourceType.AsRecord.Definition;
for var sel in P.Selectors do
begin
@@ -780,7 +882,6 @@ begin
end;
end;
// Reuse selectors (they are just keywords, no type checking needed)
Result := TAst.PipeInput(newSource, P.Selectors, Node.Identity.Location);
end;
@@ -800,22 +901,18 @@ begin
SetLength(newInputs, P.Inputs.Count);
paramTypes := TList<IStaticType>.Create;
try
// 1. Visit Inputs and collect types for Lambda parameters
for i := 0 to P.Inputs.Count - 1 do
begin
// Recurse on inputs to resolve their sources
inputNode := Accept(P.Inputs[i]).AsPipeInput;
newInputs[i] := inputNode;
streamType := inputNode.StreamSource.AsTypedNode.StaticType;
// Flatten logic: One lambda param per selector
for var sel in inputNode.Selectors do
begin
inferredType := TTypes.Unknown;
if streamType.Kind = stRecordSeries then
begin
// Extract field type from definition
var def := streamType.AsRecord.Definition;
var idx := def.IndexOf(sel.Value);
if idx >= 0 then
@@ -823,18 +920,16 @@ begin
end
else if streamType.Kind = stSeries then
begin
// If simple series, use its element type
if Assigned(streamType.AsSeries.ElementType) then
inferredType := streamType.AsSeries.ElementType
else
inferredType := TTypes.Ordinal; // Fallback default
inferredType := TTypes.Ordinal;
end;
paramTypes.Add(inferredType);
end;
end;
// 2. Prepare Lambda with Inferred Types
lambda := P.Transformation;
if lambda.Parameters.Count <> paramTypes.Count then
@@ -857,11 +952,9 @@ begin
if k < paramTypes.Count then paramTypes[k]
else TTypes.Unknown;
// Create new identifier with the inferred type!
newParams[k] := TAst.Identifier(oldP.Identity.AsNamed, oldP.Address, typeToInject);
end;
// Recreate Lambda NODE with Typed Parameters
var preTypedLambda :=
TAst.LambdaExpr(
lambda.Identity,
@@ -872,24 +965,20 @@ begin
lambda.Upvalues,
lambda.HasNestedLambdas,
lambda.IsPure,
TTypes.Unknown // Will be recalculated in Accept
TTypes.Unknown
);
// 3. Visit the Lambda (This will now type-check the Body using the types we just injected)
var typedLambda := Accept(preTypedLambda).AsLambdaExpression;
// 4. Infer Pipe Return Type & Validate Strictness
var lambdaRetType := typedLambda.AsTypedNode.StaticType.AsMethod.Signatures[0].ReturnType;
var pipeType: IStaticType;
if lambdaRetType.Kind = stRecord then
begin
// Valid: Record -> RecordSeries
pipeType := TTypes.CreateRecordSeries(lambdaRetType.AsRecord.Definition);
end
else
begin
// STRICT CHECK: Scalars are NOT allowed.
if (lambdaRetType.Kind <> stUnknown) and (lambdaRetType.Kind <> stVoid) then
begin
if Assigned(FLog) then
@@ -898,8 +987,6 @@ begin
lambda
);
end;
// If Void or Unknown, or Error case:
pipeType := TTypes.Unknown;
end;
+8
View File
@@ -151,6 +151,14 @@ begin
akLambdaExpression: Result := 'FN';
akRecordLiteral: Result := 'RECORD';
akPipe: Result := 'PIPE';
// NEW:
akIndexer: Result := 'INDEXER';
akMemberAccess: Result := 'MEMBER';
akTuple: Result := 'TUPLE';
akCreateSeries: Result := 'NEW-SERIES';
akAddSeriesItem: Result := 'ADD-ITEM';
else
Result := ''; // Silence leaf nodes like Constants and Keywords to reduce noise
end;
+20
View File
@@ -62,6 +62,8 @@ type
function VisitRecordFieldList(const Node: IAstNode): TVoid;
function VisitRecordField(const Node: IAstNode): TVoid;
function VisitTuple(const Node: IAstNode): TVoid;
// Pipe Visitors
function VisitPipeInput(const Node: IAstNode): TVoid;
function VisitPipeSelectorList(const Node: IAstNode): TVoid;
@@ -143,6 +145,8 @@ begin
Register(akRecur, VisitRecurNode);
Register(akNop, VisitNop);
Register(akTuple, VisitTuple);
Register(akPipeInput, VisitPipeInput);
Register(akPipeSelectorList, VisitPipeSelectorList);
Register(akPipeInputList, VisitPipeInputList);
@@ -625,4 +629,20 @@ begin
Unindent;
end;
function TAstDumper.VisitTuple(const Node: IAstNode): TVoid;
var
T: ITupleNode;
i: Integer;
begin
T := Node.AsTuple;
LogFmt('Tuple (%d elements)', [Length(T.Elements)], Node);
Indent;
for i := 0 to High(T.Elements) do
begin
LogFmt('Element %d:', [i]);
Visit(T.Elements[i]);
end;
Unindent;
end;
end.
+35 -5
View File
@@ -21,13 +21,16 @@ type
private
FScope: IExecutionScope;
protected
// Haupt-Dispatch-Methode
function Visit(const Node: IAstNode): TDataValue; virtual;
function CreateVisitorFactory: TEvaluatorFactory; virtual;
// Nur noch die für die Ausführung relevanten Methoden
// Besuchermethoden
function VisitConstant(const N: IConstantNode): TDataValue; virtual;
function VisitIdentifier(const N: IIdentifierNode): TDataValue; virtual;
function VisitKeyword(const N: IKeywordNode): TDataValue; virtual;
function VisitTuple(const N: ITupleNode): TDataValue; virtual; // <--- WICHTIG
function VisitIfExpression(const N: IIfExpressionNode): TDataValue; virtual;
function VisitCondExpression(const N: ICondExpressionNode): TDataValue; virtual;
function VisitLambdaExpression(const N: ILambdaExpressionNode): TDataValue; virtual;
@@ -89,10 +92,12 @@ end;
function TEvaluatorVisitor.Visit(const Node: IAstNode): TDataValue;
begin
// Der Hot-Path: Direkter Dispatch ohne Umweg über ungenutzte Methoden.
// HIER fehlte wahrscheinlich akTuple!
case Node.Kind of
akConstant: Result := VisitConstant(Node.AsConstant);
akIdentifier: Result := VisitIdentifier(Node.AsIdentifier);
akKeyword: Result := VisitKeyword(Node.AsKeyword);
akTuple: Result := VisitTuple(Node.AsTuple); // <--- KORREKTUR
akIfExpression: Result := VisitIfExpression(Node.AsIfExpression);
akCondExpression: Result := VisitCondExpression(Node.AsCondExpression);
akLambdaExpression: Result := VisitLambdaExpression(Node.AsLambdaExpression);
@@ -173,6 +178,21 @@ begin
Result := FScope[N.Address];
end;
function TEvaluatorVisitor.VisitTuple(const N: ITupleNode): TDataValue;
var
elements: TArray<TDataValue>;
i: Integer;
nodes: TArray<IAstNode>;
begin
nodes := N.Elements;
SetLength(elements, Length(nodes));
for i := 0 to High(nodes) do
elements[i] := Visit(nodes[i]);
// Uses the implicit operator: TArray<TDataValue> -> TDataValue (vkTuple)
Result := elements;
end;
function TEvaluatorVisitor.VisitLambdaExpression(const N: ILambdaExpressionNode): TDataValue;
var
capturedCells: TArray<IValueCell>;
@@ -334,9 +354,10 @@ begin
if base.IsVoid then
exit(TDataValue.Void);
idx := Visit(N.Index);
if (base.Kind = vkSeries) then
Result := TDataValue(base.AsSeries.Items[Integer(idx.AsScalar.Value.AsInt64)])
else if (base.Kind = vkRecordSeries) then
case base.Kind of
vkSeries: Result := TDataValue(base.AsSeries.Items[Integer(idx.AsScalar.Value.AsInt64)]);
vkRecordSeries:
begin
var rs := base.AsRecordSeries;
var vals: TArray<TScalar.TValue>;
@@ -345,9 +366,18 @@ begin
for var k := 0 to rs.Def.Count - 1 do
vals[k] := rs.Fields[rs.Def.Keywords[k]].Items[Integer(i64)].Value;
Result := TScalarRecord.Create(rs.Def, vals);
end
end;
vkTuple: // <--- WICHTIG FÜR (get x 3)
begin
var tpl := base.AsTuple;
var i := idx.AsScalar.Value.AsInt64;
if (i < 0) or (i >= tpl.Count) then
raise EEvaluatorException.Create('Tuple index out of bounds');
Result := tpl.Items[Integer(i)];
end;
else
raise EEvaluatorException.Create('Indexer error');
end;
end;
function TEvaluatorVisitor.VisitMemberAccess(const N: IMemberAccessNode): TDataValue;
+40
View File
@@ -51,6 +51,7 @@ type
function JsonToAddSeriesItemNode(const AObj: TJSONObject): IAddSeriesItemNode;
function JsonToSeriesLengthNode(const AObj: TJSONObject): ISeriesLengthNode;
function JsonToNopNode(const AObj: TJSONObject): INopNode;
function JsonToTupleNode(const AObj: TJSONObject): ITupleNode;
strict private
// Serialization Visitors (IAstNode signature)
@@ -85,6 +86,8 @@ type
function VisitSeriesLength(const Node: IAstNode): TJSONObject;
function VisitNop(const Node: IAstNode): TJSONObject;
function VisitTuple(const Node: IAstNode): TJSONObject;
function VisitPipeInput(const Node: IAstNode): TJSONObject;
function VisitPipeSelectorList(const Node: IAstNode): TJSONObject;
function VisitPipeInputList(const Node: IAstNode): TJSONObject;
@@ -144,6 +147,8 @@ begin
Register(akRecur, VisitRecurNode);
Register(akNop, VisitNop);
Register(akTuple, VisitTuple);
Register(akPipeInput, VisitPipeInput);
Register(akPipeSelectorList, VisitPipeSelectorList);
Register(akPipeInputList, VisitPipeInputList);
@@ -531,6 +536,24 @@ begin
Result.AddPair('NodeType', TJSONString.Create('Nop'));
end;
function TJsonAstConverter.VisitTuple(const Node: IAstNode): TJSONObject;
var
T: ITupleNode;
elemsArray: TJSONArray;
i: Integer;
begin
T := Node.AsTuple;
Result := TJSONObject.Create;
Result.AddPair('NodeType', TJSONString.Create('Tuple'));
elemsArray := TJSONArray.Create;
for i := 0 to High(T.Elements) do
begin
elemsArray.Add(Visit(T.Elements[i]));
end;
Result.AddPair('Elements', elemsArray);
end;
function TJsonAstConverter.VisitParameterList(const Node: IAstNode): TJSONObject;
begin
Result := nil;
@@ -548,6 +571,21 @@ begin
Result := nil;
end;
function TJsonAstConverter.JsonToTupleNode(const AObj: TJSONObject): ITupleNode;
var
elems: TArray<IAstNode>;
elemsArray: TJSONArray;
i: Integer;
begin
elemsArray := AObj.GetValue<TJSONArray>('Elements');
SetLength(elems, elemsArray.Count);
for i := 0 to elemsArray.Count - 1 do
begin
elems[i] := JsonToNode(elemsArray.Items[i]);
end;
Result := TAst.Tuple(elems); // Factory handles identity/type defaults
end;
function TJsonAstConverter.VisitPipeInput(const Node: IAstNode): TJSONObject;
begin
raise ENotSupportedException.Create('JSON Pipe Serialization not implemented.');
@@ -660,6 +698,8 @@ begin
Result := JsonToSeriesLengthNode(obj)
else if nodeType = 'Nop' then
Result := JsonToNopNode(obj)
else if nodeType = 'Tuple' then
Result := JsonToTupleNode(obj)
else
raise ENotSupportedException.CreateFmt('Unsupported NodeType "%s".', [nodeType]);
end;
+52 -13
View File
@@ -64,12 +64,6 @@ type
property Errors: TArray<TCompilerError> read FErrors;
end;
// Exception for the new visitor registry dispatch
ENoHandlerException = class(EAstException)
public
constructor Create(const KindName: string);
end;
TCompiledFunction = record
public
Func: TDataValue.TFunc;
@@ -94,6 +88,7 @@ type
IExpressionList = interface;
IRecordFieldList = interface;
IRecordFieldNode = interface;
ITupleNode = interface;
// Control Flow & Structure
IIfExpressionNode = interface;
@@ -134,6 +129,9 @@ type
akExpressionList,
akRecordFieldList,
akRecordField,
// Tuple
akTuple,
// Control Flow
akIfExpression,
akCondExpression,
akLambdaExpression,
@@ -195,6 +193,7 @@ type
function AsExpressionList: IExpressionList;
function AsRecordFieldList: IRecordFieldList;
function AsRecordField: IRecordFieldNode;
function AsTuple: ITupleNode;
function AsIfExpression: IIfExpressionNode;
function AsCondExpression: ICondExpressionNode;
@@ -298,6 +297,13 @@ type
property Value: IKeyword read GetValue;
end;
ITupleNode = interface(IAstTypedNode)
{$region 'private'}
function GetElements: TArray<IAstNode>;
{$endregion}
property Elements: TArray<IAstNode> read GetElements;
end;
IIfExpressionNode = interface(IAstTypedNode)
{$region 'private'}
function GetCondition: IAstNode;
@@ -543,6 +549,7 @@ type
function AsExpressionList: IExpressionList; virtual;
function AsRecordFieldList: IRecordFieldList; virtual;
function AsRecordField: IRecordFieldNode; virtual;
function AsTuple: ITupleNode; virtual;
function AsIfExpression: IIfExpressionNode; virtual;
function AsCondExpression: ICondExpressionNode; virtual;
@@ -696,6 +703,17 @@ type
function AsKeyword: IKeywordNode; override;
end;
TTupleNode = class(TAstTypedNode, ITupleNode)
private
FElements: TArray<IAstNode>;
function GetElements: TArray<IAstNode>;
protected
function GetKind: TAstNodeKind; override;
public
constructor Create(const AElements: TArray<IAstNode>; const AIdentity: IAstIdentity; const AStaticType: IStaticType);
function AsTuple: ITupleNode; override;
end;
TCreateSeriesNode = class(TAstTypedNode, ICreateSeriesNode)
private
FDefIdentity: IDefinitionIdentity;
@@ -1207,13 +1225,6 @@ begin
end;
end;
{ ENoHandlerException }
constructor ENoHandlerException.Create(const KindName: string);
begin
inherited CreateFmt('No visitor handler registered for AST node kind: %s', [KindName]);
end;
{ TAstNode }
constructor TAstNode.Create(const AIdentity: IAstIdentity);
@@ -1280,6 +1291,11 @@ begin
Result := nil;
end;
function TAstNode.AsTuple: ITupleNode;
begin
Result := nil;
end;
function TAstNode.AsIfExpression: IIfExpressionNode;
begin
Result := nil;
@@ -1608,6 +1624,29 @@ begin
Result := FKeywordIdentity.Value;
end;
{ TTupleNode }
constructor TTupleNode.Create(const AElements: TArray<IAstNode>; const AIdentity: IAstIdentity; const AStaticType: IStaticType);
begin
inherited Create(AStaticType, AIdentity);
FElements := AElements;
end;
function TTupleNode.AsTuple: ITupleNode;
begin
Result := Self;
end;
function TTupleNode.GetElements: TArray<IAstNode>;
begin
Result := FElements;
end;
function TTupleNode.GetKind: TAstNodeKind;
begin
Result := akTuple;
end;
{ TCreateSeriesNode }
constructor TCreateSeriesNode.Create(const AIdentity: IDefinitionIdentity; const AStaticType: IStaticType);
+39
View File
@@ -39,6 +39,7 @@ type
function VisitIndexer(const Node: IAstNode): IAstNode;
function VisitMemberAccess(const Node: IAstNode): IAstNode;
function VisitAddSeriesItem(const Node: IAstNode): IAstNode;
function VisitTuple(const Node: IAstNode): IAstNode;
protected
procedure SetupHandlers; override;
@@ -81,6 +82,8 @@ begin
Register(akIndexer, VisitIndexer);
Register(akMemberAccess, VisitMemberAccess);
Register(akAddSeriesItem, VisitAddSeriesItem);
Register(akTuple, VisitTuple);
end;
class function TAstNodeRemover.TryRemove(const RootNode, TargetNode: IAstNode; out NewRootNode: IAstNode): Boolean;
@@ -429,4 +432,40 @@ begin
Result := TAst.AddSeriesItem(Node.Identity, newSeries.AsIdentifier, newValue, newLookback, A.StaticType);
end;
function TAstNodeRemover.VisitTuple(const Node: IAstNode): IAstNode;
var
T: ITupleNode;
newElements: TArray<IAstNode>;
list: TList<IAstNode>;
item, transformed: IAstNode;
i: Integer;
hasChanged: Boolean;
begin
T := Node.AsTuple;
list := TList<IAstNode>.Create;
hasChanged := False;
try
for i := 0 to High(T.Elements) do
begin
item := T.Elements[i];
transformed := Accept(item);
if transformed <> item then
hasChanged := True;
if Assigned(transformed) then
list.Add(transformed)
else
hasChanged := True; // Item removed
end;
if not hasChanged then
Exit(Node);
Result := TAst.Tuple(Node.Identity, list.ToArray, T.StaticType);
finally
list.Free;
end;
end;
end.
+23 -17
View File
@@ -437,8 +437,8 @@ begin
items.Add(ParseExpression.Node);
end;
var id := TIdentities.List('[', ']', ' ', startLoc);
Result := TArgumentList.Create(items.ToArray, id);
// CHANGED: Use Tuple factory instead of ArgumentList
Result := TAst.Tuple(items.ToArray, startLoc);
finally
items.Free;
end;
@@ -447,15 +447,19 @@ end;
function TParser.ExtractIdentifiers(const Node: IAstNode; const Context: string): TArray<IIdentifierNode>;
var
list: IArgumentList;
list: TArray<IAstNode>;
i: Integer;
begin
if Node.Kind <> akArgumentList then
// Support both Tuple (new syntax [...]) and ArgumentList (internal legacy)
if Node.Kind = akTuple then
list := Node.AsTuple.Elements
else if Node.Kind = akArgumentList then
list := Node.AsArgumentList.ToArray
else
ErrorFmt('%s expects a vector [...]', [Context]);
list := Node.AsArgumentList;
SetLength(Result, list.Count);
for i := 0 to list.Count - 1 do
SetLength(Result, Length(list));
for i := 0 to High(list) do
begin
if list[i].Kind <> akIdentifier then
ErrorFmt('%s vector must contain only identifiers.', [Context]);
@@ -536,16 +540,18 @@ begin
if Length(tailNodes) <> 2 then
Error('Syntax Error: ''pipe'' requires [inputs] vector and (fn) transformation.');
if tailNodes[0].Kind <> akArgumentList then
// Input vector is now a TupleNode
if tailNodes[0].Kind <> akTuple then
Error('Syntax Error: ''pipe'' first argument must be a vector [...].');
var rawInputs := tailNodes[0].AsArgumentList;
if (rawInputs.Count mod 2) <> 0 then
var rawInputs := tailNodes[0].AsTuple.Elements;
if (Length(rawInputs) mod 2) <> 0 then
Error('Syntax Error: Pipe inputs must be pairs of Identifier and Selector Vector (e.g. [btc [:Close]]).');
var pipeInputs := TList<IPipeInputNode>.Create;
try
var k := 0;
while k < rawInputs.Count do
while k < Length(rawInputs) do
begin
if rawInputs[k].Kind <> akIdentifier then
Error('Syntax Error: Pipe input stream must be an identifier.');
@@ -554,16 +560,16 @@ begin
var streamId := rawInputs[k].AsIdentifier;
var selKeywords: TArray<IKeywordNode>;
// Strict format: Selector must be a vector [...] containing keywords
if selNode.Kind <> akArgumentList then
// Strict format: Selector must be a vector [...] (TupleNode) containing keywords
if selNode.Kind <> akTuple then
Error('Syntax Error: Pipe selector must be a vector of keywords (e.g. [:Close :High]).');
var selList := selNode.AsArgumentList;
if selList.Count = 0 then
var selList := selNode.AsTuple.Elements;
if Length(selList) = 0 then
Error('Syntax Error: Selector list cannot be empty.');
SetLength(selKeywords, selList.Count);
for var m := 0 to selList.Count - 1 do
SetLength(selKeywords, Length(selList));
for var m := 0 to High(selList) do
begin
if selList[m].Kind <> akKeyword then
Error('Syntax Error: Selector vector must contain only keywords.');
+5 -5
View File
@@ -558,7 +558,7 @@ end;
function TSeriesType.ToString: string;
begin
Result := 'Series<' + FElementType.ToString + '>';
Result := inherited + '<' + FElementType.ToString + '>';
end;
// --- Tuple ---
@@ -651,14 +651,14 @@ var
begin
sb := TStringBuilder.Create;
try
sb.Append('(');
sb.Append('[');
for i := 0 to High(FElements) do
begin
sb.Append(FElements[i].ToString);
if i < High(FElements) then
sb.Append(', ');
end;
sb.Append(')');
sb.Append(']');
Result := sb.ToString;
finally
sb.Free;
@@ -740,7 +740,7 @@ end;
function TVectorType.ToString: string;
begin
Result := Format('[%s; %d]', [FElementType.ToString, FCount]);
Result := inherited + Format('[%s; %d]', [FElementType.ToString, FCount]);
end;
// --- Matrix ---
@@ -846,7 +846,7 @@ begin
sb.Append('x');
end;
sb.Append(']');
Result := sb.ToString;
Result := inherited + sb.ToString;
finally
sb.Free;
end;
+46 -2
View File
@@ -83,6 +83,9 @@ type
function VisitSeriesLength(const N: IAstNode): IAstNode;
function VisitRecurNode(const N: IAstNode): IAstNode;
// Tuples
function VisitTuple(const N: IAstNode): IAstNode;
// Pipes
function VisitPipeInput(const N: IAstNode): IAstNode;
function VisitPipeSelectorList(const N: IAstNode): IAstNode;
@@ -125,6 +128,8 @@ type
function WalkSeriesLength(const N: IAstNode): TVoid;
function WalkRecurNode(const N: IAstNode): TVoid;
function WalkTuple(const N: IAstNode): TVoid;
function WalkPipeInput(const N: IAstNode): TVoid;
function WalkPipeSelectorList(const N: IAstNode): TVoid;
function WalkPipeInputList(const N: IAstNode): TVoid;
@@ -160,8 +165,8 @@ end;
function TAstVisitor<T>.DefaultSentinel(const Node: IAstNode): T;
begin
// Controlled crash for missing handlers.
raise ENoHandlerException.Create(Node.Kind.ToString);
Assert(false, 'No visitor handler registered for AST node kind: ' + Node.Kind.ToString + ' in class ' + ClassName);
exit(Default(T));
end;
procedure TAstVisitor<T>.SetupHandlers;
@@ -235,6 +240,8 @@ begin
Register(akSeriesLength, VisitSeriesLength);
Register(akRecur, VisitRecurNode);
Register(akTuple, VisitTuple);
Register(akPipeInput, VisitPipeInput);
Register(akPipeSelectorList, VisitPipeSelectorList);
Register(akPipeInputList, VisitPipeInputList);
@@ -716,6 +723,33 @@ begin
Result := TAst.Pipe(N.Identity, inp, trans, P.StaticType);
end;
function TAstTransformer.VisitTuple(const N: IAstNode): IAstNode;
var
T: ITupleNode;
hasChanged: Boolean;
newElements: TArray<IAstNode>;
item, newItem: IAstNode;
i: Integer;
begin
T := N.AsTuple;
hasChanged := False;
SetLength(newElements, Length(T.Elements));
for i := 0 to High(T.Elements) do
begin
item := T.Elements[i];
newItem := Visit(item);
newElements[i] := newItem;
if item <> newItem then
hasChanged := True;
end;
if not hasChanged then
Result := N
else
Result := TAst.Tuple(N.Identity, newElements, N.AsTypedNode.StaticType);
end;
{ TAstVisitor (Walker) }
procedure TAstVisitor.SetupHandlers;
@@ -756,6 +790,8 @@ begin
Register(akSeriesLength, WalkSeriesLength);
Register(akRecur, WalkRecurNode);
Register(akTuple, WalkTuple);
// Pipes
Register(akPipeInput, WalkPipeInput);
Register(akPipeSelectorList, WalkPipeSelectorList);
@@ -937,4 +973,12 @@ begin
Visit(P.Transformation);
end;
function TAstVisitor.WalkTuple(const N: IAstNode): TVoid;
var
item: IAstNode;
begin
for item in N.AsTuple.Elements do
Visit(item);
end;
end.
+25 -4
View File
@@ -32,7 +32,6 @@ type
ARegisterLibraries: Boolean = False
): IExecutionScope; static;
// ... (Keep existing methods: Identifier, Constant, Keyword, CreateSeries, Nop, IfExpr, CondExpr, LambdaExpr, MacroDef, Quotes, Call, etc.) ...
// [Creation] Raw Name -> New INamedIdentity
class function Identifier(const AName: string; const Loc: ISourceLocation = nil): IIdentifierNode; overload; static;
class function Identifier(
@@ -48,6 +47,13 @@ type
class function Keyword(const AName: string; const Loc: ISourceLocation = nil): IKeywordNode; overload; static;
class function Keyword(const Identity: IKeywordIdentity): IKeywordNode; overload; static;
class function Tuple(const Elements: TArray<IAstNode>; const Loc: ISourceLocation = nil): ITupleNode; overload; static;
class function Tuple(
const Identity: IAstIdentity;
const Elements: TArray<IAstNode>;
const AStaticType: IStaticType = nil
): ITupleNode; overload; static;
class function CreateSeries(const ADefinition: String; const Loc: ISourceLocation = nil): ICreateSeriesNode; overload; static;
class function CreateSeries(
const Identity: IDefinitionIdentity;
@@ -261,12 +267,10 @@ type
const AStaticType: IStaticType = nil
): ISeriesLengthNode; overload; static;
// --- PIPES (Updated) ---
// --- PIPES ---
// Helper: Create a SelectorList from an array of KeywordNodes
class function PipeSelectorList(const AKeywords: TArray<IKeywordNode>; const Loc: ISourceLocation = nil): IPipeSelectorList; static;
// Updated PipeInput: Accepts IPipeSelectorList instead of single Keyword
class function PipeInput(
const AStream: IIdentifierNode;
const ASelectors: IPipeSelectorList;
@@ -389,6 +393,23 @@ begin
Result := TKeywordNode.Create(Identity);
end;
class function TAst.Tuple(const Elements: TArray<IAstNode>; const Loc: ISourceLocation): ITupleNode;
begin
var id := TIdentities.List('[', ']', ' ', Loc);
Result := TTupleNode.Create(Elements, id, TTypes.Unknown);
end;
class function TAst.Tuple(const Identity: IAstIdentity; const Elements: TArray<IAstNode>; const AStaticType: IStaticType): ITupleNode;
begin
Result :=
TTupleNode.Create(
Elements,
Identity,
if AStaticType <> nil then AStaticType
else TTypes.Unknown
);
end;
class function TAst.CreateSeries(const ADefinition: String; const Loc: ISourceLocation): ICreateSeriesNode;
begin
var id := TIdentities.Definition(ADefinition, Loc);
+1
View File
@@ -228,6 +228,7 @@ end;
procedure TVisualNode.AddChild(Child: TVisualNode);
begin
Assert(Child <> nil);
if Child.Parent <> nil then
Child.Parent.RemoveChild(Child);
+9
View File
@@ -45,6 +45,9 @@ type
function VisitRecordFieldList(const Node: IAstNode): TAstViewNode;
function VisitRecordField(const Node: IAstNode): TAstViewNode;
// Tuple
function VisitTuple(const Node: IAstNode): TAstViewNode;
// Control Flow
function VisitIfExpression(const Node: IAstNode): TAstViewNode;
function VisitCondExpression(const Node: IAstNode): TAstViewNode;
@@ -138,6 +141,7 @@ begin
Register(akExpressionList, VisitExpressionList);
Register(akRecordFieldList, VisitRecordFieldList);
Register(akRecordField, VisitRecordField);
Register(akTuple, VisitTuple); // <--- NEW
// Control Flow
Register(akIfExpression, VisitIfExpression);
@@ -261,6 +265,11 @@ begin
Result := TAstViewNode.Create(Self, TRecordFieldHandler.Create(Node.AsRecordField));
end;
function TAstVisualizer.VisitTuple(const Node: IAstNode): TAstViewNode;
begin
Result := TAstViewNode.Create(Self, TNopNodeHandler.Create(Node.AsNop));
end;
// Control Flow
function TAstVisualizer.VisitBlockExpression(const Node: IAstNode): TAstViewNode;
+28
View File
@@ -12,6 +12,34 @@ type
property Count: Integer read GetCount;
end;
// Concrete generic implementation
TTupleImpl<T> = class(TInterfacedObject, ITuple<T>)
private
FItems: TArray<T>;
function GetItems(Idx: Integer): T;
function GetCount: Integer;
public
constructor Create(const AItems: TArray<T>);
end;
implementation
{ TTupleImpl<T> }
constructor TTupleImpl<T>.Create(const AItems: TArray<T>);
begin
inherited Create;
FItems := AItems;
end;
function TTupleImpl<T>.GetCount: Integer;
begin
Result := Length(FItems);
end;
function TTupleImpl<T>.GetItems(Idx: Integer): T;
begin
Result := FItems[Idx];
end;
end.
+7
View File
@@ -76,6 +76,7 @@ type
class operator Implicit(const AValue: String): TDataValue; overload; inline;
class operator Implicit(const AValue: TDataValue.TFunc): TDataValue; overload; inline;
class operator Implicit(const AValue: ITuple<TDataValue>): TDataValue; overload; inline;
class operator Implicit(const AValue: TArray<TDataValue>): TDataValue; overload; inline;
class operator Implicit(const AValue: IKeywordMapping<TScalar>): TDataValue; overload; inline;
class operator Implicit(const AValue: IKeywordMapping<TDataValue>): TDataValue; overload; inline;
class operator Implicit(const AValue: IWriteableScalarRecordSeries): TDataValue; overload; inline;
@@ -507,6 +508,12 @@ begin
Result.FInterface := AValue;
end;
class operator TDataValue.Implicit(const AValue: TArray<TDataValue>): TDataValue;
begin
Result.FKind := vkTuple;
Result.FInterface := TTupleImpl<TDataValue>.Create(AValue) as ITuple<TDataValue>;
end;
{ TMapSeries }
constructor TMapSeries.Create(const ASourceSeries: ISeries; const AMapperFunc: TDataValue.TFunc);