Pipe Parameter is now a Tuple

This commit is contained in:
Michael Schimmel
2026-01-05 00:13:51 +01:00
parent 242ec9a56e
commit 264314cd93
15 changed files with 322 additions and 1244 deletions
+2 -31
View File
@@ -55,9 +55,6 @@ type
function VisitTuple(const Node: IAstNode): Boolean;
// Pipe Support
function VisitPipeInput(const Node: IAstNode): Boolean;
function VisitPipeSelectorList(const Node: IAstNode): Boolean;
function VisitPipeInputList(const Node: IAstNode): Boolean;
function VisitPipe(const Node: IAstNode): Boolean;
// Helper to check if a node is pure (handling nil gracefully)
@@ -131,9 +128,6 @@ begin
Register(akTuple, VisitTuple);
// Pipes
Register(akPipeInput, VisitPipeInput);
Register(akPipeSelectorList, VisitPipeSelectorList);
Register(akPipeInputList, VisitPipeInputList);
Register(akPipe, VisitPipe);
end;
@@ -328,34 +322,11 @@ end;
// --- Pipe Support ---
function TPurityAnalyzer.VisitPipeInput(const Node: IAstNode): Boolean;
begin
var P := Node.AsPipeInput;
// Input node itself is declarative.
// But we check its parts just in case.
Result := IsNodePure(P.StreamSource) and IsNodePure(P.Selectors);
end;
function TPurityAnalyzer.VisitPipeSelectorList(const Node: IAstNode): Boolean;
begin
for var item in Node.AsPipeSelectorList do
if not IsNodePure(item) then
exit(False);
Result := True;
end;
function TPurityAnalyzer.VisitPipeInputList(const Node: IAstNode): Boolean;
begin
for var item in Node.AsPipeInputList do
if not IsNodePure(item) then
exit(False);
Result := True;
end;
function TPurityAnalyzer.VisitPipe(const Node: IAstNode): Boolean;
begin
var P := Node.AsPipe;
// A pipe is pure if its input definitions are pure AND its transformation lambda is pure.
// A pipe is pure if its inputs are pure AND its transformation lambda is pure.
// P.Inputs is an ITupleNode (recursive Tuple of Tuples), so VisitTuple handles it.
Result := IsNodePure(P.Inputs) and IsNodePure(P.Transformation);
end;
+111 -92
View File
@@ -73,7 +73,6 @@ type
function VisitTuple(const Node: IAstNode): IAstNode;
// Pipe Support
function VisitPipeInput(const Node: IAstNode): IAstNode;
function VisitPipe(const Node: IAstNode): IAstNode;
protected
@@ -242,7 +241,6 @@ begin
Register(akTuple, VisitTuple);
// Pipe Support
Register(akPipeInput, VisitPipeInput);
Register(akPipe, VisitPipe);
end;
@@ -328,8 +326,6 @@ begin
for i := 0 to count - 1 do
begin
newElements[i] := Accept(T.Items[i]);
// FIX: Ensure the node is typed before accessing StaticType.
// Structural nodes (like RecordField) are NOT typed and might crash or return garbage.
if newElements[i].IsTyped then
elementTypes[i] := newElements[i].AsTypedNode.StaticType
else
@@ -347,9 +343,6 @@ begin
firstType := elementTypes[0];
isHomogeneous := True;
// Check for Homogeneity (Exact type equality of all elements)
// Note: If any element is Unknown, we assume Heterogeneous (Tuple) unless all are unknown?
// For now, Unknown breaks homogeneity.
for i := 1 to count - 1 do
begin
if not firstType.IsEqual(elementTypes[i]) then
@@ -361,20 +354,15 @@ begin
if isHomogeneous and (firstType.Kind <> stUnknown) 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...]
// Vector of Matrices -> Higher dimensional Matrix
commonDim := firstType.AsMatrix.Dimensions;
SetLength(newDim, Length(commonDim) + 1);
@@ -518,30 +506,22 @@ var
exprs: ITupleNode;
i: Integer;
begin
// 1. Transform children via inherited logic
// This delegates to TAstTransformer.VisitBlockExpression, which calls Visit(B.Expressions).
// B.Expressions is a Tuple, so it routes to TTypeChecker.VisitTuple.
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
begin
// Check for NIL entries which indicate failed transformation of children
for i := 0 to exprs.Count - 1 do
begin
if exprs.Items[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.Items[exprs.Count - 1].AsTypedNode.StaticType;
end
else
@@ -783,12 +763,6 @@ var
newFields: ITupleNode;
begin
R := Node.AsRecordLiteral;
// FIX: Do NOT use inherited VisitRecordLiteral() here, because it expects an IRecordLiteralNode
// but R.Fields is an ITupleNode. Using inherited would dispatch to VisitRecordLiteral with wrong type
// or trigger recursive confusion.
// Instead, directly visit the fields Tuple. This routes to TTypeChecker.VisitTuple.
newFields := Visit(R.Fields).AsTuple;
var count := newFields.Count;
@@ -798,10 +772,6 @@ begin
for i := 0 to count - 1 do
begin
// Note: Elements of the fields tuple are RecordFieldNodes.
// RecordFieldNodes are NOT Typed (IAstTypedNode), they are structural.
// So we cannot check .StaticType on the field itself.
// We must check the .Value of the field.
var field := newFields.Items[i].AsRecordField;
key := field.Key.Value;
valType := field.Value.AsTypedNode.StaticType;
@@ -876,89 +846,133 @@ end;
// PIPE IMPLEMENTATION (Type Checking)
// =============================================================================
function TTypeChecker.VisitPipeInput(const Node: IAstNode): IAstNode;
var
P: IPipeInputNode;
newSource: IIdentifierNode;
sourceType: IStaticType;
begin
P := Node.AsPipeInput;
newSource := Accept(P.StreamSource).AsIdentifier;
sourceType := newSource.AsTypedNode.StaticType;
if (sourceType.Kind <> stUnknown) then
begin
if not ((sourceType.Kind = stSeries) or (sourceType.Kind = stRecordSeries)) then
begin
if Assigned(FLog) then
FLog.AddError(
Format('Pipe input "%s" must be a Series or RecordSeries, but got %s.', [newSource.Name, sourceType.ToString]),
Node
);
end
else if (sourceType.Kind = stRecordSeries) then
begin
var def := sourceType.AsRecord.Definition;
for var sel in P.Selectors do
begin
if def.IndexOf(sel.Value) < 0 then
begin
if Assigned(FLog) then
FLog.AddError(Format('Field ":%s" not found in stream "%s".', [sel.Value.Name, newSource.Name]), sel);
end;
end;
end;
end;
Result := TAst.PipeInput(newSource, P.Selectors, Node.Identity.Location);
end;
function TTypeChecker.VisitPipe(const Node: IAstNode): IAstNode;
var
P: IPipeNode;
rawInputs: ITupleNode;
i, k: Integer;
inputNode: IPipeInputNode;
newInputs: TArray<IPipeInputNode>;
streamType: IStaticType;
newInputs: TList<IAstNode>;
paramTypes: TList<IStaticType>;
lambda: ILambdaExpressionNode;
newParams: TArray<IIdentifierNode>;
newParamsAsNodes: TArray<IAstNode>;
inferredType: IStaticType;
// Iteration vars
inputPair: IAstNode;
rawTuple: ITupleNode;
streamNode: IIdentifierNode;
selectorsNode: ITupleNode;
newSelectors: TList<IAstNode>;
streamType: IStaticType;
begin
P := Node.AsPipe;
SetLength(newInputs, P.Inputs.Count);
rawInputs := P.Inputs; // This is the Tuple of Tuples [[id [sel]] ...]
newInputs := TList<IAstNode>.Create;
paramTypes := TList<IStaticType>.Create;
try
for i := 0 to P.Inputs.Count - 1 do
// 1. Iterate over input definitions
for i := 0 to rawInputs.Count - 1 do
begin
inputNode := Accept(P.Inputs[i]).AsPipeInput;
newInputs[i] := inputNode;
inputPair := rawInputs.Items[i];
streamType := inputNode.StreamSource.AsTypedNode.StaticType;
for var sel in inputNode.Selectors do
// Validate Structure: [StreamSource, [Selectors]]
if inputPair.Kind <> akTuple then
begin
inferredType := TTypes.Unknown;
if streamType.Kind = stRecordSeries then
if Assigned(FLog) then
FLog.AddError('Pipe input must be a vector: [Source [Selectors]].', inputPair);
continue;
end;
rawTuple := inputPair.AsTuple;
if rawTuple.Count <> 2 then
begin
if Assigned(FLog) then
FLog.AddError('Pipe input vector must have exactly 2 elements.', inputPair);
continue;
end;
// Element 0: Stream Identifier
if rawTuple.Items[0].Kind <> akIdentifier then
begin
if Assigned(FLog) then
FLog.AddError('Pipe source must be an identifier.', rawTuple.Items[0]);
continue;
end;
// Resolve Stream Identifier (Type Binding)
streamNode := Accept(rawTuple.Items[0]).AsIdentifier;
streamType := streamNode.StaticType;
// Validate Stream Type
if (streamType.Kind <> stUnknown) then
begin
if not ((streamType.Kind = stSeries) or (streamType.Kind = stRecordSeries)) then
begin
var def := streamType.AsRecord.Definition;
var idx := def.IndexOf(sel.Value);
if idx >= 0 then
inferredType := TTypes.FromScalarKind(def[idx]);
end
else if streamType.Kind = stSeries then
if Assigned(FLog) then
FLog.AddError(
Format('Pipe input "%s" must be a Series or RecordSeries, but got %s.', [streamNode.Name, streamType.ToString]),
streamNode
);
end;
end;
// Element 1: Selector Vector
if rawTuple.Items[1].Kind <> akTuple then
begin
if Assigned(FLog) then
FLog.AddError('Pipe selectors must be a vector of keywords.', rawTuple.Items[1]);
continue;
end;
selectorsNode := rawTuple.Items[1].AsTuple;
newSelectors := TList<IAstNode>.Create;
try
// Iterate Selectors
for k := 0 to selectorsNode.Count - 1 do
begin
if Assigned(streamType.AsSeries.ElementType) then
inferredType := streamType.AsSeries.ElementType
else
inferredType := TTypes.Ordinal;
var selItem := selectorsNode.Items[k];
if selItem.Kind <> akKeyword then
begin
if Assigned(FLog) then
FLog.AddError('Selector must be a keyword.', selItem);
continue;
end;
var kw := selItem.AsKeyword;
newSelectors.Add(kw); // Keywords don't need 'Accept' as they are constant, but we keep them structurally
// Infer Type for Lambda Parameter
inferredType := TTypes.Unknown;
if streamType.Kind = stRecordSeries then
begin
var def := streamType.AsRecord.Definition;
var idx := def.IndexOf(kw.Value);
if idx >= 0 then
inferredType := TTypes.FromScalarKind(def[idx])
else if Assigned(FLog) then
FLog.AddError(Format('Field ":%s" not found in stream "%s".', [kw.Value.Name, streamNode.Name]), selItem);
end
else if streamType.Kind = stSeries then
begin
if Assigned(streamType.AsSeries.ElementType) then
inferredType := streamType.AsSeries.ElementType
else
inferredType := TTypes.Ordinal;
end;
paramTypes.Add(inferredType);
end;
paramTypes.Add(inferredType);
// Reconstruct the typed Input Tuple [StreamIdent, [Selectors]]
var typedSelectorTuple := TAst.Tuple(selectorsNode.Identity, newSelectors.ToArray, TTypes.Unknown);
var typedInputPair := TAst.Tuple(rawTuple.Identity, [streamNode, typedSelectorTuple], TTypes.Unknown);
newInputs.Add(typedInputPair);
finally
newSelectors.Free;
end;
end;
// 2. Process Transformation Lambda
lambda := P.Transformation;
if lambda.Parameters.Count <> paramTypes.Count then
@@ -1004,6 +1018,7 @@ begin
var typedLambda := Accept(preTypedLambda).AsLambdaExpression;
// 3. Determine Result Type
var lambdaRetType := typedLambda.AsTypedNode.StaticType.AsMethod.Signatures[0].ReturnType;
var pipeType: IStaticType;
@@ -1024,8 +1039,12 @@ begin
pipeType := TTypes.Unknown;
end;
Result := TAst.Pipe(Node.Identity, TPipeInputList.Create(newInputs, P.Inputs.Identity), typedLambda, pipeType);
// Reconstruct the Pipe Node with typed Inputs tuple and typed Lambda
var typedInputsTuple := TAst.Tuple(rawInputs.Identity, newInputs.ToArray, TTypes.Unknown);
Result := TAst.Pipe(Node.Identity, typedInputsTuple, typedLambda, pipeType);
finally
newInputs.Free;
paramTypes.Free;
end;
end;
+18 -79
View File
@@ -55,14 +55,7 @@ type
function VisitAddSeriesItem(const Node: IAstNode): TVoid;
function VisitSeriesLength(const Node: IAstNode): TVoid;
function VisitNop(const Node: IAstNode): TVoid;
// Unified List Visitor
function VisitTuple(const Node: IAstNode): TVoid;
// Pipe Visitors (Specific lists kept for now)
function VisitPipeInput(const Node: IAstNode): TVoid;
function VisitPipeSelectorList(const Node: IAstNode): TVoid;
function VisitPipeInputList(const Node: IAstNode): TVoid;
function VisitPipe(const Node: IAstNode): TVoid;
protected
@@ -87,9 +80,7 @@ var
dumper: TAstDumper;
begin
if (not Assigned(Output)) or (not Assigned(RootNode)) then
begin
exit;
end;
Output.Clear;
dumper := TAstDumper.Create(Output);
@@ -112,12 +103,8 @@ begin
Register(akConstant, VisitConstant);
Register(akIdentifier, VisitIdentifier);
Register(akKeyword, VisitKeyword);
// Unified List
Register(akTuple, VisitTuple);
// Element
Register(akRecordField, VisitRecordField);
Register(akIfExpression, VisitIfExpression);
Register(akCondExpression, VisitCondExpression);
Register(akLambdaExpression, VisitLambdaExpression);
@@ -138,19 +125,13 @@ begin
Register(akSeriesLength, VisitSeriesLength);
Register(akRecur, VisitRecurNode);
Register(akNop, VisitNop);
Register(akPipeInput, VisitPipeInput);
Register(akPipeSelectorList, VisitPipeSelectorList);
Register(akPipeInputList, VisitPipeInputList);
Register(akPipe, VisitPipe);
end;
procedure TAstDumper.Execute(const RootNode: IAstNode);
begin
if Assigned(RootNode) then
begin
Visit(RootNode);
end;
end;
procedure TAstDumper.Indent;
@@ -165,21 +146,16 @@ end;
procedure TAstDumper.Log(const Text: string; const Node: IAstNode);
var
typeStr: string;
staticType: IStaticType;
begin
typeStr := '';
var typeStr := '';
if Assigned(Node) and Node.IsTyped then
begin
staticType := Node.AsTypedNode.StaticType;
if Assigned(staticType) then
begin
typeStr := Format(' <Type: %s>', [staticType.ToString]);
end
typeStr := Format(' <Type: %s>', [staticType.ToString])
else
begin
typeStr := ' <Type: nil>';
end;
end;
FOutput.Add(StringOfChar(' ', FIndent) + Text + typeStr);
end;
@@ -211,13 +187,9 @@ var
begin
I := Node.AsIdentifier;
if I.Address.Kind <> akUnresolved then
begin
LogFmt('Identifier: %s -> %s', [I.Name, FormatAddress(I.Address)], Node);
end
LogFmt('Identifier: %s -> %s', [I.Name, FormatAddress(I.Address)], Node)
else
begin
LogFmt('Identifier: %s (unbound)', [I.Name], Node);
end;
end;
function TAstDumper.VisitKeyword(const Node: IAstNode): TVoid;
@@ -270,7 +242,6 @@ end;
function TAstDumper.VisitLambdaExpression(const Node: IAstNode): TVoid;
var
E: ILambdaExpressionNode;
addr: TResolvedAddress;
symbols: TArray<string>;
layout: IScopeLayout;
slot: Integer;
@@ -306,17 +277,15 @@ begin
Log('Parameters:');
Indent;
Visit(E.Parameters); // Visits Tuple
Visit(E.Parameters);
Unindent;
if Length(E.Upvalues) > 0 then
begin
LogFmt('Captured Upvalues (%d):', [Length(E.Upvalues)]);
Indent;
for addr in E.Upvalues do
begin
for var addr in E.Upvalues do
Log(FormatAddress(addr));
end;
Unindent;
end;
@@ -364,7 +333,7 @@ begin
Log('Callee:');
Visit(C.Callee);
LogFmt('Arguments (%d):', [args.Count]);
Visit(args); // Visits Tuple
Visit(args);
Unindent;
end;
@@ -506,6 +475,17 @@ begin
Unindent;
end;
function TAstDumper.VisitRecordField(const Node: IAstNode): TVoid;
var
F: IRecordFieldNode;
begin
F := Node.AsRecordField;
LogFmt('Field :%s', [F.Key.Value.Name]);
Indent;
Visit(F.Value);
Unindent;
end;
function TAstDumper.VisitCreateSeries(const Node: IAstNode): TVoid;
begin
LogFmt('CreateSeries: %s', [Node.AsCreateSeries.Definition], Node);
@@ -543,8 +523,6 @@ begin
Log('Nop', Node);
end;
// --- Unified List Visitor ---
function TAstDumper.VisitTuple(const Node: IAstNode): TVoid;
var
T: ITupleNode;
@@ -563,45 +541,6 @@ begin
Unindent;
end;
function TAstDumper.VisitRecordField(const Node: IAstNode): TVoid;
var
F: IRecordFieldNode;
begin
F := Node.AsRecordField;
LogFmt('Field :%s', [F.Key.Value.Name]);
Indent;
Visit(F.Value);
Unindent;
end;
// --- Pipe Visitors ---
function TAstDumper.VisitPipeInput(const Node: IAstNode): TVoid;
var
P: IPipeInputNode;
begin
P := Node.AsPipeInput;
Log('PipeInput', Node);
Indent;
Log('Source:');
Visit(P.StreamSource);
Log('Selectors:');
Visit(P.Selectors);
Unindent;
end;
function TAstDumper.VisitPipeSelectorList(const Node: IAstNode): TVoid;
begin
for var item in Node.AsPipeSelectorList do
Visit(item);
end;
function TAstDumper.VisitPipeInputList(const Node: IAstNode): TVoid;
begin
for var item in Node.AsPipeInputList do
Visit(item);
end;
function TAstDumper.VisitPipe(const Node: IAstNode): TVoid;
var
P: IPipeNode;
@@ -609,7 +548,7 @@ begin
P := Node.AsPipe;
Log('Pipe', Node);
Indent;
Log('Inputs:');
Log('Inputs (Tuple of Tuples):');
Visit(P.Inputs);
Log('Transformation:');
Visit(P.Transformation);
+42 -11
View File
@@ -454,35 +454,61 @@ end;
function TEvaluatorVisitor.VisitPipe(const N: IPipeNode): TDataValue;
var
i: Integer;
inputNode: IPipeInputNode;
sources: TArray<IStream>;
config: TPipeConfig;
inputVal: TDataValue;
sourceSeries: IScalarRecordSeries;
lambdaFunc: TDataValue.TFunc;
inputsTuple: ITupleNode;
entryTuple: ITupleNode;
sourceId: IIdentifierNode;
selectorsTuple: ITupleNode;
begin
SetLength(sources, N.Inputs.Count);
SetLength(config, N.Inputs.Count);
for i := 0 to N.Inputs.Count - 1 do
inputsTuple := N.Inputs;
SetLength(sources, inputsTuple.Count);
SetLength(config, inputsTuple.Count);
for i := 0 to inputsTuple.Count - 1 do
begin
inputNode := N.Inputs.Items[i].AsPipeInput;
inputVal := FScope[inputNode.StreamSource.Address];
// Unpack: [Source, [Selectors]]
// Note: Structure is guaranteed by TypeChecker
entryTuple := inputsTuple.Items[i].AsTuple;
sourceId := entryTuple.Items[0].AsIdentifier;
selectorsTuple := entryTuple.Items[1].AsTuple;
// Resolve Source Stream from Scope
// The Binder has already linked sourceId to its variable address
inputVal := FScope[sourceId.Address];
if (inputVal.Kind <> vkStream) then
raise EEvaluatorException.Create('Stream expected');
raise EEvaluatorException.Create(Format('Variable "%s" is not a Stream.', [sourceId.Name]));
sources[i] := inputVal.AsStream;
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
// Build Selectors Config
SetLength(config[i], selectorsTuple.Count);
for var k := 0 to selectorsTuple.Count - 1 do
begin
var key := selectors.Items[k].AsKeyword.Value;
var key := selectorsTuple.Items[k].AsKeyword.Value;
var idx := srcDef.IndexOf(key);
// Index check already done in TypeChecker, but good for safety
if idx < 0 then
raise EEvaluatorException.Create(Format('Field :%s not found in stream.', [key.Name]));
config[i][k] := TScalarRecordField.Create(key, srcDef[idx]);
end;
end;
// Compile the transformation function
lambdaFunc := Visit(N.Transformation).AsMethod();
var outputDef := N.StaticType.AsRecord.Definition;
var pipeAdapter: TPipeStream.TPipeLambda :=
function(const S: array of ISeries; out R: array of TScalar.TValue): Boolean
var
@@ -493,14 +519,19 @@ begin
SetLength(args, Length(S));
for k := 0 to High(S) do
args[k] := S[k].Items[0];
res := lambdaFunc(args);
if res.IsVoid then
exit(False);
var rec := res.AsScalarRecord;
for k := 0 to rec.Count - 1 do
R[k] := rec.Items[k].Value;
Result := True;
end;
Result := TPipeStream.Create(config, outputDef, sources, pipeAdapter);
end;
+84 -257
View File
@@ -18,8 +18,6 @@ type
function Deserialize(const AJson: TJSONObject): IAstNode;
end;
// TJsonAstConverter implements the visitor pattern for serialization
// and uses factory functions for deserialization.
TJsonAstConverter = class(TAstVisitor<TJSONObject>, IJsonAstConverter)
private
procedure DataValueToJson(const AValue: TDataValue; const AParent: TJSONObject; const AName: string);
@@ -52,16 +50,15 @@ type
function JsonToSeriesLengthNode(const AObj: TJSONObject): ISeriesLengthNode;
function JsonToNopNode(const AObj: TJSONObject): INopNode;
function JsonToTupleNode(const AObj: TJSONObject): ITupleNode;
function JsonToPipeNode(const AObj: TJSONObject): IPipeNode;
strict private
// Serialization Visitors (IAstNode signature)
// Serialization Visitors
function VisitConstant(const Node: IAstNode): TJSONObject;
function VisitIdentifier(const Node: IAstNode): TJSONObject;
function VisitKeyword(const Node: IAstNode): TJSONObject;
function VisitTuple(const Node: IAstNode): TJSONObject;
function VisitRecordField(const Node: IAstNode): TJSONObject;
function VisitIfExpression(const Node: IAstNode): TJSONObject;
function VisitCondExpression(const Node: IAstNode): TJSONObject;
function VisitLambdaExpression(const Node: IAstNode): TJSONObject;
@@ -82,10 +79,6 @@ type
function VisitAddSeriesItem(const Node: IAstNode): TJSONObject;
function VisitSeriesLength(const Node: IAstNode): TJSONObject;
function VisitNop(const Node: IAstNode): TJSONObject;
function VisitPipeInput(const Node: IAstNode): TJSONObject;
function VisitPipeSelectorList(const Node: IAstNode): TJSONObject;
function VisitPipeInputList(const Node: IAstNode): TJSONObject;
function VisitPipe(const Node: IAstNode): TJSONObject;
protected
@@ -100,7 +93,8 @@ type
implementation
uses
Myc.Data.Keyword;
Myc.Data.Keyword,
Myc.Ast.Identities;
{ TJsonAstConverter }
@@ -114,10 +108,8 @@ begin
Register(akConstant, VisitConstant);
Register(akIdentifier, VisitIdentifier);
Register(akKeyword, VisitKeyword);
Register(akTuple, VisitTuple);
Register(akRecordField, VisitRecordField);
Register(akIfExpression, VisitIfExpression);
Register(akCondExpression, VisitCondExpression);
Register(akLambdaExpression, VisitLambdaExpression);
@@ -138,19 +130,13 @@ begin
Register(akSeriesLength, VisitSeriesLength);
Register(akRecur, VisitRecurNode);
Register(akNop, VisitNop);
Register(akPipeInput, VisitPipeInput);
Register(akPipeSelectorList, VisitPipeSelectorList);
Register(akPipeInputList, VisitPipeInputList);
Register(akPipe, VisitPipe);
end;
function TJsonAstConverter.Serialize(const RootNode: IAstNode): TJSONObject;
begin
if not Assigned(RootNode) then
begin
exit(nil);
end;
Result := Visit(RootNode);
end;
@@ -159,8 +145,6 @@ begin
Result := JsonToNode(AJson);
end;
{ Serialization Visitors }
procedure TJsonAstConverter.DataValueToJson(const AValue: TDataValue; const AParent: TJSONObject; const AName: string);
var
valObj, scalarObj: TJSONObject;
@@ -222,12 +206,9 @@ begin
T := Node.AsTuple;
Result := TJSONObject.Create;
Result.AddPair('NodeType', TJSONString.Create('Tuple'));
elemsArray := TJSONArray.Create;
for i := 0 to T.Count - 1 do
begin
elemsArray.Add(Visit(T.Items[i]));
end;
Result.AddPair('Elements', elemsArray);
end;
@@ -241,15 +222,10 @@ begin
Result.AddPair('NodeType', TJSONString.Create('IfExpr'));
Result.AddPair('Condition', Visit(E.Condition));
Result.AddPair('ThenBranch', Visit(E.ThenBranch));
if Assigned(E.ElseBranch) then
begin
elseObj := Visit(E.ElseBranch);
end
elseObj := Visit(E.ElseBranch)
else
begin
elseObj := TJSONNull.Create;
end;
Result.AddPair('ElseBranch', elseObj);
end;
@@ -270,16 +246,10 @@ begin
pairObj.AddPair('Branch', Visit(E.Pairs[i].Branch));
pairsArray.Add(pairObj);
end;
if Assigned(E.ElseBranch) then
begin
elseObj := Visit(E.ElseBranch);
end
elseObj := Visit(E.ElseBranch)
else
begin
elseObj := TJSONNull.Create;
end;
Result := TJSONObject.Create;
Result.AddPair('NodeType', TJSONString.Create('CondExpr'));
Result.AddPair('Pairs', pairsArray);
@@ -289,41 +259,23 @@ end;
function TJsonAstConverter.VisitLambdaExpression(const Node: IAstNode): TJSONObject;
var
L: ILambdaExpressionNode;
paramsArray: TJSONArray;
i: Integer;
begin
L := Node.AsLambdaExpression;
paramsArray := TJSONArray.Create;
// Iterate Tuple
for i := 0 to L.Parameters.Count - 1 do
begin
paramsArray.Add(Visit(L.Parameters.Items[i]));
end;
Result := TJSONObject.Create;
Result.AddPair('NodeType', TJSONString.Create('LambdaExpr'));
Result.AddPair('Parameters', paramsArray);
Result.AddPair('Parameters', Visit(L.Parameters));
Result.AddPair('Body', Visit(L.Body));
end;
function TJsonAstConverter.VisitMacroDefinition(const Node: IAstNode): TJSONObject;
var
M: IMacroDefinitionNode;
paramsArray: TJSONArray;
i: Integer;
begin
M := Node.AsMacroDefinition;
paramsArray := TJSONArray.Create;
// Iterate Tuple
for i := 0 to M.Parameters.Count - 1 do
begin
paramsArray.Add(Visit(M.Parameters.Items[i]));
end;
Result := TJSONObject.Create;
Result.AddPair('NodeType', TJSONString.Create('MacroDef'));
Result.AddPair('Name', Visit(M.Name));
Result.AddPair('Parameters', paramsArray);
Result.AddPair('Parameters', Visit(M.Parameters));
Result.AddPair('Body', Visit(M.Body));
end;
@@ -351,80 +303,38 @@ end;
function TJsonAstConverter.VisitFunctionCall(const Node: IAstNode): TJSONObject;
var
C: IFunctionCallNode;
argsArray: TJSONArray;
i: Integer;
begin
C := Node.AsFunctionCall;
argsArray := TJSONArray.Create;
// Iterate Tuple
for i := 0 to C.Arguments.Count - 1 do
begin
argsArray.Add(Visit(C.Arguments.Items[i]));
end;
Result := TJSONObject.Create;
Result.AddPair('NodeType', TJSONString.Create('FunctionCall'));
Result.AddPair('Callee', Visit(C.Callee));
Result.AddPair('Arguments', argsArray);
Result.AddPair('Arguments', Visit(C.Arguments));
end;
function TJsonAstConverter.VisitMacroExpansionNode(const Node: IAstNode): TJSONObject;
var
M: IMacroExpansionNode;
argsArray: TJSONArray;
i: Integer;
begin
M := Node.AsMacroExpansion;
argsArray := TJSONArray.Create;
// Iterate Tuple
for i := 0 to M.CallNode.Arguments.Count - 1 do
begin
argsArray.Add(Visit(M.CallNode.Arguments.Items[i]));
end;
Result := TJSONObject.Create;
Result.AddPair('NodeType', TJSONString.Create('MacroExpansion'));
Result.AddPair('Callee', Visit(M.CallNode.Callee));
Result.AddPair('Arguments', argsArray);
Result.AddPair('Arguments', Visit(M.CallNode.Arguments));
Result.AddPair('ExpandedBody', Visit(M.ExpandedBody));
end;
function TJsonAstConverter.VisitRecurNode(const Node: IAstNode): TJSONObject;
var
R: IRecurNode;
argsArray: TJSONArray;
i: Integer;
begin
R := Node.AsRecur;
argsArray := TJSONArray.Create;
// Iterate Tuple
for i := 0 to R.Arguments.Count - 1 do
begin
argsArray.Add(Visit(R.Arguments.Items[i]));
end;
Result := TJSONObject.Create;
Result.AddPair('NodeType', TJSONString.Create('Recur'));
Result.AddPair('Arguments', argsArray);
Result.AddPair('Arguments', Visit(Node.AsRecur.Arguments));
end;
function TJsonAstConverter.VisitBlockExpression(const Node: IAstNode): TJSONObject;
var
B: IBlockExpressionNode;
exprsArray: TJSONArray;
i: Integer;
begin
B := Node.AsBlockExpression;
exprsArray := TJSONArray.Create;
// Iterate Tuple
for i := 0 to B.Expressions.Count - 1 do
begin
exprsArray.Add(Visit(B.Expressions.Items[i]));
end;
Result := TJSONObject.Create;
Result.AddPair('NodeType', TJSONString.Create('Block'));
Result.AddPair('Expressions', exprsArray);
Result.AddPair('Expressions', Visit(Node.AsBlockExpression.Expressions));
end;
function TJsonAstConverter.VisitVariableDeclaration(const Node: IAstNode): TJSONObject;
@@ -434,14 +344,9 @@ var
begin
V := Node.AsVariableDeclaration;
if Assigned(V.Initializer) then
begin
initObj := Visit(V.Initializer);
end
initObj := Visit(V.Initializer)
else
begin
initObj := TJSONNull.Create;
end;
Result := TJSONObject.Create;
Result.AddPair('NodeType', TJSONString.Create('VarDecl'));
Result.AddPair('Identifier', Visit(V.Target));
@@ -482,21 +387,10 @@ begin
end;
function TJsonAstConverter.VisitRecordLiteral(const Node: IAstNode): TJSONObject;
var
R: IRecordLiteralNode;
fieldsArray: TJSONArray;
i: Integer;
begin
R := Node.AsRecordLiteral;
Result := TJSONObject.Create;
Result.AddPair('NodeType', TJSONString.Create('RecordLiteral'));
fieldsArray := TJSONArray.Create;
// Iterate Tuple
for i := 0 to R.Fields.Count - 1 do
begin
fieldsArray.Add(Visit(R.Fields.Items[i]));
end;
Result.AddPair('Fields', fieldsArray);
Result.AddPair('Fields', Visit(Node.AsRecordLiteral.Fields));
end;
function TJsonAstConverter.VisitRecordField(const Node: IAstNode): TJSONObject;
@@ -523,14 +417,9 @@ var
begin
A := Node.AsAddSeriesItem;
if Assigned(A.Lookback) then
begin
lookbackObj := Visit(A.Lookback);
end
lookbackObj := Visit(A.Lookback)
else
begin
lookbackObj := TJSONNull.Create;
end;
Result := TJSONObject.Create;
Result.AddPair('NodeType', TJSONString.Create('AddSeriesItem'));
Result.AddPair('Series', Visit(A.Series));
@@ -551,21 +440,15 @@ begin
Result.AddPair('NodeType', TJSONString.Create('Nop'));
end;
function TJsonAstConverter.VisitPipeInput(const Node: IAstNode): TJSONObject;
begin
raise ENotSupportedException.Create('JSON Pipe Serialization not implemented.');
end;
function TJsonAstConverter.VisitPipeSelectorList(const Node: IAstNode): TJSONObject;
begin
raise ENotSupportedException.Create('JSON Pipe Serialization not implemented.');
end;
function TJsonAstConverter.VisitPipeInputList(const Node: IAstNode): TJSONObject;
begin
raise ENotSupportedException.Create('JSON Pipe Serialization not implemented.');
end;
function TJsonAstConverter.VisitPipe(const Node: IAstNode): TJSONObject;
var
P: IPipeNode;
begin
raise ENotSupportedException.Create('JSON Pipe Serialization not implemented.');
P := Node.AsPipe;
Result := TJSONObject.Create;
Result.AddPair('NodeType', TJSONString.Create('Pipe'));
Result.AddPair('Inputs', Visit(P.Inputs));
Result.AddPair('Transformation', Visit(P.Transformation));
end;
{ Deserialization Handlers }
@@ -590,17 +473,11 @@ begin
end;
end
else if SameText(kindStr, 'Text') then
begin
Result := valObj.GetValue<string>('Value');
end
Result := valObj.GetValue<string>('Value')
else if SameText(kindStr, 'Void') then
begin
Result := TDataValue.Void;
end
Result := TDataValue.Void
else
begin
raise ENotSupportedException.Create('Unsupported TDataValue kind.');
end;
end;
function TJsonAstConverter.JsonToNode(const AJson: TJSONValue; const ExpectedType: string): IAstNode;
@@ -609,9 +486,7 @@ var
nodeType: string;
begin
if not (AJson is TJSONObject) then
begin
raise EInvalidCast.Create('Expected JSON object.');
end;
obj := AJson as TJSONObject;
nodeType := obj.GetValue<string>('NodeType');
@@ -665,6 +540,8 @@ begin
Result := JsonToNopNode(obj)
else if nodeType = 'Tuple' then
Result := JsonToTupleNode(obj)
else if nodeType = 'Pipe' then
Result := JsonToPipeNode(obj)
else
raise ENotSupportedException.CreateFmt('Unsupported NodeType "%s".', [nodeType]);
end;
@@ -689,14 +566,9 @@ var
elseNode: IAstNode;
begin
if AObj.GetValue('ElseBranch') is TJSONNull then
begin
elseNode := nil;
end
elseNode := nil
else
begin
elseNode := JsonToNode(AObj.GetValue('ElseBranch'));
end;
Result := TAst.IfExpr(JsonToNode(AObj.GetValue('Condition')), JsonToNode(AObj.GetValue('ThenBranch')), elseNode);
end;
@@ -710,23 +582,15 @@ begin
pairsArray := AObj.GetValue<TJSONArray>('Pairs');
SetLength(pairs, pairsArray.Count);
for i := 0 to pairsArray.Count - 1 do
begin
pairs[i] :=
TCondPair.Create(
JsonToNode((pairsArray.Items[i] as TJSONObject).GetValue('Condition')),
JsonToNode((pairsArray.Items[i] as TJSONObject).GetValue('Branch'))
);
end;
if AObj.GetValue('ElseBranch') is TJSONNull then
begin
elseNode := nil;
end
elseNode := nil
else
begin
elseNode := JsonToNode(AObj.GetValue('ElseBranch'));
end;
Result := TAst.CondExpr(pairs, elseNode);
end;
@@ -742,32 +606,37 @@ end;
function TJsonAstConverter.JsonToLambdaExprNode(const AObj: TJSONObject): ILambdaExpressionNode;
var
params: TArray<IIdentifierNode>;
paramArray: TJSONArray;
i: Integer;
identNodes: TList<IIdentifierNode>;
node: IAstNode;
begin
paramArray := AObj.GetValue<TJSONArray>('Parameters');
SetLength(params, paramArray.Count);
for i := 0 to paramArray.Count - 1 do
begin
params[i] := JsonToIdentifierNode(paramArray.Items[i] as TJSONObject);
identNodes := TList<IIdentifierNode>.Create;
try
for node in JsonToNode(AObj.GetValue('Parameters')).AsTuple.ToArray do
identNodes.Add(node.AsIdentifier);
Result := TAst.LambdaExpr(identNodes.ToArray, JsonToNode(AObj.GetValue('Body')));
finally
identNodes.Free;
end;
Result := TAst.LambdaExpr(params, JsonToNode(AObj.GetValue('Body')));
end;
function TJsonAstConverter.JsonToMacroDefNode(const AObj: TJSONObject): IMacroDefinitionNode;
var
params: TArray<IIdentifierNode>;
paramArray: TJSONArray;
i: Integer;
identNodes: TList<IIdentifierNode>;
node: IAstNode;
begin
paramArray := AObj.GetValue<TJSONArray>('Parameters');
SetLength(params, paramArray.Count);
for i := 0 to paramArray.Count - 1 do
begin
params[i] := JsonToIdentifierNode(paramArray.Items[i] as TJSONObject);
identNodes := TList<IIdentifierNode>.Create;
try
for node in JsonToNode(AObj.GetValue('Parameters')).AsTuple.ToArray do
identNodes.Add(node.AsIdentifier);
Result :=
TAst.MacroDef(
JsonToIdentifierNode(AObj.GetValue('Name') as TJSONObject),
identNodes.ToArray,
JsonToNode(AObj.GetValue('Body'))
);
finally
identNodes.Free;
end;
Result := TAst.MacroDef(JsonToIdentifierNode(AObj.GetValue('Name') as TJSONObject), params, JsonToNode(AObj.GetValue('Body')));
end;
function TJsonAstConverter.JsonToQuasiquoteNode(const AObj: TJSONObject): IQuasiquoteNode;
@@ -786,69 +655,29 @@ begin
end;
function TJsonAstConverter.JsonToFunctionCallNode(const AObj: TJSONObject): IFunctionCallNode;
var
args: TArray<IAstNode>;
argsArray: TJSONArray;
i: Integer;
begin
argsArray := AObj.GetValue<TJSONArray>('Arguments');
SetLength(args, argsArray.Count);
for i := 0 to argsArray.Count - 1 do
begin
args[i] := JsonToNode(argsArray.Items[i]);
end;
Result := TAst.FunctionCall(JsonToNode(AObj.GetValue('Callee')), args);
Result := TAst.FunctionCall(JsonToNode(AObj.GetValue('Callee')), JsonToNode(AObj.GetValue('Arguments')).AsTuple.ToArray);
end;
function TJsonAstConverter.JsonToMacroExpansionNode(const AObj: TJSONObject): IMacroExpansionNode;
var
callee, expandedBody: IAstNode;
args: TArray<IAstNode>;
argsArray: TJSONArray;
i: Integer;
tempCallNode: IFunctionCallNode;
argsTuple: ITupleNode;
begin
callee := JsonToNode(AObj.GetValue('Callee'));
expandedBody := JsonToNode(AObj.GetValue('ExpandedBody'));
argsArray := AObj.GetValue<TJSONArray>('Arguments');
SetLength(args, argsArray.Count);
for i := 0 to argsArray.Count - 1 do
begin
args[i] := JsonToNode(argsArray.Items[i]);
end;
tempCallNode := TAst.FunctionCall(callee, args);
Result := TAst.MacroExpansionNode(tempCallNode, expandedBody);
argsTuple := JsonToNode(AObj.GetValue('Arguments')).AsTuple;
Result := TAst.MacroExpansionNode(TAst.FunctionCall(callee, argsTuple.ToArray), expandedBody);
end;
function TJsonAstConverter.JsonToRecurNode(const AObj: TJSONObject): IRecurNode;
var
args: TArray<IAstNode>;
argsArray: TJSONArray;
i: Integer;
begin
argsArray := AObj.GetValue<TJSONArray>('Arguments');
SetLength(args, argsArray.Count);
for i := 0 to argsArray.Count - 1 do
begin
args[i] := JsonToNode(argsArray.Items[i]);
end;
Result := TAst.Recur(args);
Result := TAst.Recur(JsonToNode(AObj.GetValue('Arguments')).AsTuple.ToArray);
end;
function TJsonAstConverter.JsonToBlockNode(const AObj: TJSONObject): IBlockExpressionNode;
var
exprs: TArray<IAstNode>;
exprsArray: TJSONArray;
i: Integer;
begin
exprsArray := AObj.GetValue<TJSONArray>('Expressions');
SetLength(exprs, exprsArray.Count);
for i := 0 to exprsArray.Count - 1 do
begin
exprs[i] := JsonToNode(exprsArray.Items[i]);
end;
Result := TAst.Block(exprs);
Result := TAst.Block(JsonToNode(AObj.GetValue('Expressions')).AsTuple.ToArray);
end;
function TJsonAstConverter.JsonToVarDeclNode(const AObj: TJSONObject): IVariableDeclarationNode;
@@ -856,14 +685,9 @@ var
initNode: IAstNode;
begin
if AObj.GetValue('Initializer') is TJSONNull then
begin
initNode := nil;
end
initNode := nil
else
begin
initNode := JsonToNode(AObj.GetValue('Initializer'));
end;
Result := TAst.VarDecl(JsonToIdentifierNode(AObj.GetValue('Identifier') as TJSONObject), initNode);
end;
@@ -879,26 +703,27 @@ end;
function TJsonAstConverter.JsonToMemberAccessNode(const AObj: TJSONObject): IMemberAccessNode;
begin
Result := TAst.MemberAccess(JsonToNode(AObj.GetValue('Base')), JsonToKeywordNode(AObj.GetValue('Member') as TJSONObject));
Result := TAst.MemberAccess(JsonToNode(AObj.GetValue('Base')), JsonToKeywordNode(AObj.GetValue('Member') as TJSONObject).AsKeyword);
end;
function TJsonAstConverter.JsonToRecordLiteralNode(const AObj: TJSONObject): IRecordLiteralNode;
var
fields: TArray<IRecordFieldNode>;
fieldsArray: TJSONArray;
fields: TList<IRecordFieldNode>;
i: Integer;
begin
fieldsArray := AObj.GetValue<TJSONArray>('Fields');
SetLength(fields, fieldsArray.Count);
for i := 0 to fieldsArray.Count - 1 do
begin
fields[i] :=
TAst.RecordField(
TAst.Keyword((fieldsArray.Items[i] as TJSONObject).GetValue<string>('Name')),
JsonToNode((fieldsArray.Items[i] as TJSONObject).GetValue('Value'))
);
fields := TList<IRecordFieldNode>.Create;
try
for i := 0 to fieldsArray.Count - 1 do
begin
var fieldObj := fieldsArray.Items[i] as TJSONObject;
fields.Add(TAst.RecordField(TAst.Keyword(fieldObj.GetValue<string>('Name')), JsonToNode(fieldObj.GetValue('Value'))));
end;
Result := TAst.RecordLiteral(fields.ToArray);
finally
fields.Free;
end;
Result := TAst.RecordLiteral(fields);
end;
function TJsonAstConverter.JsonToCreateSeriesNode(const AObj: TJSONObject): ICreateSeriesNode;
@@ -911,14 +736,9 @@ var
lookNode: IAstNode;
begin
if AObj.GetValue('Lookback') is TJSONNull then
begin
lookNode := nil;
end
lookNode := nil
else
begin
lookNode := JsonToNode(AObj.GetValue('Lookback'));
end;
Result :=
TAst.AddSeriesItem(JsonToIdentifierNode(AObj.GetValue('Series') as TJSONObject), JsonToNode(AObj.GetValue('Value')), lookNode);
end;
@@ -935,17 +755,24 @@ end;
function TJsonAstConverter.JsonToTupleNode(const AObj: TJSONObject): ITupleNode;
var
elems: TArray<IAstNode>;
elemsArray: TJSONArray;
elems: TList<IAstNode>;
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]);
elems := TList<IAstNode>.Create;
try
for i := 0 to elemsArray.Count - 1 do
elems.Add(JsonToNode(elemsArray.Items[i]));
Result := TAst.Tuple(elems.ToArray);
finally
elems.Free;
end;
Result := TAst.Tuple(elems); // Factory handles identity/type defaults
end;
function TJsonAstConverter.JsonToPipeNode(const AObj: TJSONObject): IPipeNode;
begin
Result := TAst.Pipe(JsonToNode(AObj.GetValue('Inputs')).AsTuple, JsonToNode(AObj.GetValue('Transformation')).AsLambdaExpression);
end;
end.
+9 -241
View File
@@ -109,9 +109,6 @@ type
INopNode = interface;
// Pipes
IPipeSelectorList = interface;
IPipeInputNode = interface;
IPipeInputList = interface;
IPipeNode = interface;
// Node Kinds & Helpers
@@ -144,10 +141,7 @@ type
akRecur,
akNop,
// Pipes
akPipe,
akPipeInput,
akPipeSelectorList,
akPipeInputList
akPipe
);
TAstNodeKindHelper = record helper for TAstNodeKind
@@ -204,9 +198,6 @@ type
function AsNop: INopNode;
// Pipes
function AsPipeInput: IPipeInputNode;
function AsPipeSelectorList: IPipeSelectorList;
function AsPipeInputList: IPipeInputList;
function AsPipe: IPipeNode;
property IsTyped: Boolean read GetIsTyped;
@@ -214,17 +205,6 @@ type
property Identity: IAstIdentity read GetIdentity;
end;
INodeList<T: IAstNode> = interface(IAstNode)
{$region 'private'}
function GetCount: Integer;
function GetItem(Index: Integer): T;
{$endregion}
function GetEnumerator: TEnumerator<T>;
function ToArray: TArray<T>;
property Count: Integer read GetCount;
property Items[Index: Integer]: T read GetItem; default;
end;
IAstTypedNode = interface(IAstNode)
{$region 'private'}
function GetStaticType: IStaticType;
@@ -468,31 +448,13 @@ type
property Series: IIdentifierNode read GetSeries;
end;
// A list of selector keywords [ :A :B ]
IPipeSelectorList = interface(INodeList<IKeywordNode>)
end;
// Represents a single input definition within a pipe: "btc [:Close :Open]"
IPipeInputNode = interface(IAstTypedNode)
{$region 'private'}
function GetStreamSource: IIdentifierNode;
function GetSelectors: IPipeSelectorList;
{$endregion}
property StreamSource: IIdentifierNode read GetStreamSource;
property Selectors: IPipeSelectorList read GetSelectors;
end;
// A list of pipe inputs
IPipeInputList = interface(INodeList<IPipeInputNode>)
end;
// The main pipe definition: (pipe [inputs...] (fn ...))
// The main pipe definition: (pipe [[source [:sel]]...] (fn ...))
IPipeNode = interface(IAstTypedNode)
{$region 'private'}
function GetInputs: IPipeInputList;
function GetInputs: ITupleNode;
function GetTransformation: ILambdaExpressionNode;
{$endregion}
property Inputs: IPipeInputList read GetInputs;
property Inputs: ITupleNode read GetInputs;
property Transformation: ILambdaExpressionNode read GetTransformation;
end;
@@ -546,9 +508,6 @@ type
function AsRecur: IRecurNode; virtual;
function AsNop: INopNode; virtual;
function AsPipeInput: IPipeInputNode; virtual;
function AsPipeSelectorList: IPipeSelectorList; virtual;
function AsPipeInputList: IPipeInputList; virtual;
function AsPipe: IPipeNode; virtual;
function AsTypedNode: IAstTypedNode; virtual;
@@ -570,35 +529,6 @@ type
property StaticType: IStaticType read GetStaticType;
end;
// --- List Implementation Bases ---
TAstNodeList<T: IAstNode> = class(TAstNode, INodeList<T>)
type
TEnumerator = class(TEnumerator<T>)
private
FArray: TArray<T>;
FIndex: NativeInt;
function GetCurrent: T; inline;
protected
function DoGetCurrent: T; override;
function DoMoveNext: Boolean; override;
public
constructor Create(const AArray: TArray<T>);
function MoveNext: Boolean; inline;
property Current: T read GetCurrent;
end;
private
FItems: TArray<T>;
function GetCount: Integer;
function GetItem(Index: Integer): T;
public
constructor Create(const AItems: TArray<T>; const AIdentity: IAstIdentity);
function GetEnumerator: TEnumerator<T>;
function ToArray: TArray<T>;
property Count: Integer read GetCount;
property Items[Index: Integer]: T read GetItem; default;
end;
TRecordFieldNode = class(TAstNode, IRecordFieldNode)
private
FKey: IKeywordNode;
@@ -977,49 +907,17 @@ type
function AsNop: INopNode; override;
end;
TPipeSelectorList = class(TAstNodeList<IKeywordNode>, IPipeSelectorList)
protected
function GetKind: TAstNodeKind; override;
public
function AsPipeSelectorList: IPipeSelectorList; override;
end;
TPipeInputNode = class(TAstTypedNode, IPipeInputNode)
private
FStreamSource: IIdentifierNode;
FSelectors: IPipeSelectorList;
function GetStreamSource: IIdentifierNode;
function GetSelectors: IPipeSelectorList;
protected
function GetKind: TAstNodeKind; override;
public
constructor Create(
const AStreamSource: IIdentifierNode;
const ASelectors: IPipeSelectorList;
const AIdentity: IAstIdentity;
const AStaticType: IStaticType
);
function AsPipeInput: IPipeInputNode; override;
end;
TPipeInputList = class(TAstNodeList<IPipeInputNode>, IPipeInputList)
protected
function GetKind: TAstNodeKind; override;
public
function AsPipeInputList: IPipeInputList; override;
end;
TPipeNode = class(TAstTypedNode, IPipeNode)
private
FInputs: IPipeInputList;
FInputs: ITupleNode;
FTransformation: ILambdaExpressionNode;
function GetInputs: IPipeInputList;
function GetInputs: ITupleNode;
function GetTransformation: ILambdaExpressionNode;
protected
function GetKind: TAstNodeKind; override;
public
constructor Create(
const AInputs: IPipeInputList;
const AInputs: ITupleNode;
const ATransformation: ILambdaExpressionNode;
const AIdentity: IAstIdentity;
const AStaticType: IStaticType
@@ -1329,21 +1227,6 @@ begin
Result := nil;
end;
function TAstNode.AsPipeInput: IPipeInputNode;
begin
Result := nil;
end;
function TAstNode.AsPipeSelectorList: IPipeSelectorList;
begin
Result := nil;
end;
function TAstNode.AsPipeInputList: IPipeInputList;
begin
Result := nil;
end;
function TAstNode.AsPipe: IPipeNode;
begin
Result := nil;
@@ -1372,34 +1255,6 @@ begin
Result := Self;
end;
{ TAstNodeList<T> }
constructor TAstNodeList<T>.Create(const AItems: TArray<T>; const AIdentity: IAstIdentity);
begin
inherited Create(AIdentity);
FItems := AItems;
end;
function TAstNodeList<T>.GetCount: Integer;
begin
Result := Length(FItems);
end;
function TAstNodeList<T>.GetEnumerator: TEnumerator<T>;
begin
Result := TEnumerator.Create(FItems);
end;
function TAstNodeList<T>.GetItem(Index: Integer): T;
begin
Result := FItems[Index];
end;
function TAstNodeList<T>.ToArray: TArray<T>;
begin
Result := FItems;
end;
{ TRecordFieldNode }
constructor TRecordFieldNode.Create(const AKey: IKeywordNode; const AValue: IAstNode; const AIdentity: IAstIdentity);
@@ -2160,97 +2015,10 @@ begin
Result := akNop;
end;
constructor TAstNodeList<T>.TEnumerator.Create(const AArray: TArray<T>);
begin
inherited Create;
FArray := AArray;
FIndex := -1;
end;
function TAstNodeList<T>.TEnumerator.DoGetCurrent: T;
begin
Result := Current;
end;
function TAstNodeList<T>.TEnumerator.DoMoveNext: Boolean;
begin
Result := MoveNext;
end;
function TAstNodeList<T>.TEnumerator.GetCurrent: T;
begin
Result := FArray[FIndex];
end;
function TAstNodeList<T>.TEnumerator.MoveNext: Boolean;
begin
Result := FIndex < High(FArray);
if Result then
Inc(FIndex);
end;
{ TPipeSelectorList }
function TPipeSelectorList.AsPipeSelectorList: IPipeSelectorList;
begin
Result := Self;
end;
function TPipeSelectorList.GetKind: TAstNodeKind;
begin
Result := akPipeSelectorList;
end;
{ TPipeInputNode }
constructor TPipeInputNode.Create(
const AStreamSource: IIdentifierNode;
const ASelectors: IPipeSelectorList;
const AIdentity: IAstIdentity;
const AStaticType: IStaticType
);
begin
inherited Create(AStaticType, AIdentity);
FStreamSource := AStreamSource;
FSelectors := ASelectors;
end;
function TPipeInputNode.AsPipeInput: IPipeInputNode;
begin
Result := Self;
end;
function TPipeInputNode.GetKind: TAstNodeKind;
begin
Result := akPipeInput;
end;
function TPipeInputNode.GetSelectors: IPipeSelectorList;
begin
Result := FSelectors;
end;
function TPipeInputNode.GetStreamSource: IIdentifierNode;
begin
Result := FStreamSource;
end;
{ TPipeInputList }
function TPipeInputList.AsPipeInputList: IPipeInputList;
begin
Result := Self;
end;
function TPipeInputList.GetKind: TAstNodeKind;
begin
Result := akPipeInputList;
end;
{ TPipeNode }
constructor TPipeNode.Create(
const AInputs: IPipeInputList;
const AInputs: ITupleNode;
const ATransformation: ILambdaExpressionNode;
const AIdentity: IAstIdentity;
const AStaticType: IStaticType
@@ -2266,7 +2034,7 @@ begin
Result := Self;
end;
function TPipeNode.GetInputs: IPipeInputList;
function TPipeNode.GetInputs: ITupleNode;
begin
Result := FInputs;
end;
+2 -48
View File
@@ -53,9 +53,6 @@ type
function VisitRecurNode(const N: IAstNode): TVoid;
function VisitNop(const N: IAstNode): TVoid;
function VisitPipeInput(const N: IAstNode): TVoid;
function VisitPipeSelectorList(const N: IAstNode): TVoid;
function VisitPipeInputList(const N: IAstNode): TVoid;
function VisitPipe(const N: IAstNode): TVoid;
protected
@@ -115,9 +112,6 @@ begin
Register(akRecur, VisitRecurNode);
Register(akNop, VisitNop);
Register(akPipeInput, VisitPipeInput);
Register(akPipeSelectorList, VisitPipeSelectorList);
Register(akPipeInputList, VisitPipeInputList);
Register(akPipe, VisitPipe);
end;
@@ -155,54 +149,14 @@ end;
// --- Implementations ---
function TPrettyPrintVisitor.VisitPipeInput(const N: IAstNode): TVoid;
var
P: IPipeInputNode;
begin
P := N.AsPipeInput;
Visit(P.StreamSource);
Append(' ');
Visit(P.Selectors);
end;
function TPrettyPrintVisitor.VisitPipeSelectorList(const N: IAstNode): TVoid;
var
L: IPipeSelectorList;
i: Integer;
begin
L := N.AsPipeSelectorList;
Append('[');
for i := 0 to L.Count - 1 do
begin
if i > 0 then
Append(' ');
Visit(L[i]);
end;
Append(']');
end;
function TPrettyPrintVisitor.VisitPipeInputList(const N: IAstNode): TVoid;
var
L: IPipeInputList;
i: Integer;
begin
L := N.AsPipeInputList;
Append('[');
for i := 0 to L.Count - 1 do
begin
if i > 0 then
Append(' ');
Visit(L[i]);
end;
Append(']');
end;
function TPrettyPrintVisitor.VisitPipe(const N: IAstNode): TVoid;
var
P: IPipeNode;
begin
P := N.AsPipe;
Append('(pipe ');
// Inputs is a Tuple containing Tuples.
// VisitTuple will automatically print [ ... ] for the outer and inner lists.
Visit(P.Inputs);
Indent;
NewLine;
+7 -45
View File
@@ -532,59 +532,21 @@ begin
begin
if SameText(head.Token.Text, 'pipe') then
begin
// (pipe [stream1 [:Key1 :Key2] stream2 [:Key3]] (fn ...))
// (pipe [[stream1 [:Key1 :Key2]] [stream2 [:Key3]]] (fn ...))
if Length(tailNodes) <> 2 then
Error('Syntax Error: ''pipe'' requires [inputs] vector and (fn) transformation.');
if tailNodes[0].Kind <> akTuple then
Error('Syntax Error: ''pipe'' first argument must be a vector [...].');
// Access tuple elements safely
var inputsTuple := tailNodes[0].AsTuple;
if (inputsTuple.Count mod 2) <> 0 then
Error('Syntax Error: Pipe inputs must be pairs of Identifier and Selector Vector (e.g. [btc [:Close]]).');
// NOTE: We do NOT validate inner tuples or keywords here anymore.
// It is just a Tuple of Tuples structurally.
// The TypeChecker will ensure valid structure (Series + Keywords).
var pipeInputs := TList<IPipeInputNode>.Create;
try
var k := 0;
while k < inputsTuple.Count do
begin
var streamNode := inputsTuple.Items[k];
if streamNode.Kind <> akIdentifier then
Error('Syntax Error: Pipe input stream must be an identifier.');
if tailNodes[1].Kind <> akLambdaExpression then
Error('Syntax Error: Pipe transformation must be a lambda (fn).');
var selNode := inputsTuple.Items[k + 1];
var streamId := streamNode.AsIdentifier;
var selKeywords: TArray<IKeywordNode>;
if selNode.Kind <> akTuple then
Error('Syntax Error: Pipe selector must be a vector of keywords (e.g. [:Close :High]).');
var selTuple := selNode.AsTuple;
if selTuple.Count = 0 then
Error('Syntax Error: Selector list cannot be empty.');
SetLength(selKeywords, selTuple.Count);
for var m := 0 to selTuple.Count - 1 do
begin
if selTuple.Items[m].Kind <> akKeyword then
Error('Syntax Error: Selector vector must contain only keywords.');
selKeywords[m] := selTuple.Items[m].AsKeyword;
end;
var selectorList := TAst.PipeSelectorList(selKeywords, selNode.Identity.Location);
pipeInputs.Add(TAst.PipeInput(streamId, selectorList, streamId.Identity.Location));
Inc(k, 2);
end;
if tailNodes[1].Kind <> akLambdaExpression then
Error('Syntax Error: Pipe transformation must be a lambda (fn).');
Result := TAst.Pipe(pipeInputs.ToArray, tailNodes[1].AsLambdaExpression, startLoc);
finally
pipeInputs.Free;
end;
Result := TAst.Pipe(tailNodes[0].AsTuple, tailNodes[1].AsLambdaExpression, startLoc);
end
else if SameText(head.Token.Text, 'fn') then
begin
+2 -101
View File
@@ -83,9 +83,6 @@ type
function VisitTuple(const N: IAstNode): IAstNode;
// Pipes
function VisitPipeInput(const N: IAstNode): IAstNode;
function VisitPipeSelectorList(const N: IAstNode): IAstNode;
function VisitPipeInputList(const N: IAstNode): IAstNode;
function VisitPipe(const N: IAstNode): IAstNode;
procedure SetupHandlers; override;
@@ -122,9 +119,6 @@ type
function WalkTuple(const N: IAstNode): TVoid;
function WalkPipeInput(const N: IAstNode): TVoid;
function WalkPipeSelectorList(const N: IAstNode): TVoid;
function WalkPipeInputList(const N: IAstNode): TVoid;
function WalkPipe(const N: IAstNode): TVoid;
procedure SetupHandlers; override;
@@ -233,9 +227,6 @@ begin
Register(akTuple, VisitTuple);
// Pipes
Register(akPipeInput, VisitPipeInput);
Register(akPipeSelectorList, VisitPipeSelectorList);
Register(akPipeInputList, VisitPipeInputList);
Register(akPipe, VisitPipe);
end;
@@ -522,82 +513,14 @@ begin
Result := TAst.Recur(N.Identity, a, R.StaticType);
end;
function TAstTransformer.VisitPipeInput(const N: IAstNode): IAstNode;
var
P: IPipeInputNode;
s: IAstNode;
sel: IPipeSelectorList;
begin
P := N.AsPipeInput;
s := Visit(P.StreamSource);
sel := Visit(P.Selectors).AsPipeSelectorList;
if (s = P.StreamSource) and (sel = P.Selectors) then
Result := N
else
Result := TAst.PipeInput(s.AsIdentifier, sel, N.Identity.Location);
end;
function TAstTransformer.VisitPipeSelectorList(const N: IAstNode): IAstNode;
var
L: IPipeSelectorList;
hasChanged: Boolean;
newItems: TArray<IKeywordNode>;
item, newItem: IKeywordNode;
i: Integer;
begin
L := N.AsPipeSelectorList;
hasChanged := False;
SetLength(newItems, L.Count);
for i := 0 to L.Count - 1 do
begin
item := L[i];
newItem := Visit(item).AsKeyword;
newItems[i] := newItem;
if item <> newItem then
hasChanged := True;
end;
if not hasChanged then
Result := N
else
Result := TPipeSelectorList.Create(newItems, N.Identity);
end;
function TAstTransformer.VisitPipeInputList(const N: IAstNode): IAstNode;
var
L: IPipeInputList;
hasChanged: Boolean;
newItems: TArray<IPipeInputNode>;
item, newItem: IPipeInputNode;
i: Integer;
begin
L := N.AsPipeInputList;
hasChanged := False;
SetLength(newItems, L.Count);
for i := 0 to L.Count - 1 do
begin
item := L[i];
newItem := Visit(item).AsPipeInput;
newItems[i] := newItem;
if item <> newItem then
hasChanged := True;
end;
if not hasChanged then
Result := N
else
Result := TPipeInputList.Create(newItems, N.Identity);
end;
function TAstTransformer.VisitPipe(const N: IAstNode): IAstNode;
var
P: IPipeNode;
inp: IPipeInputList;
inp: ITupleNode;
trans: ILambdaExpressionNode;
begin
P := N.AsPipe;
inp := Visit(P.Inputs).AsPipeInputList;
inp := Visit(P.Inputs).AsTuple;
trans := Visit(P.Transformation).AsLambdaExpression;
if (inp = P.Inputs) and (trans = P.Transformation) then
@@ -673,9 +596,6 @@ begin
Register(akTuple, WalkTuple);
// Pipes
Register(akPipeInput, WalkPipeInput);
Register(akPipeSelectorList, WalkPipeSelectorList);
Register(akPipeInputList, WalkPipeInputList);
Register(akPipe, WalkPipe);
end;
@@ -803,25 +723,6 @@ begin
Visit(N.AsRecur.Arguments);
end;
function TAstVisitor.WalkPipeInput(const N: IAstNode): TVoid;
begin
var P := N.AsPipeInput;
Visit(P.StreamSource);
Visit(P.Selectors);
end;
function TAstVisitor.WalkPipeSelectorList(const N: IAstNode): TVoid;
begin
for var item in N.AsPipeSelectorList do
Visit(item);
end;
function TAstVisitor.WalkPipeInputList(const N: IAstNode): TVoid;
begin
for var item in N.AsPipeInputList do
Visit(item);
end;
function TAstVisitor.WalkPipe(const N: IAstNode): TVoid;
begin
var P := N.AsPipe;
+5 -36
View File
@@ -269,23 +269,15 @@ type
// --- PIPES ---
class function PipeSelectorList(const AKeywords: TArray<IKeywordNode>; const Loc: ISourceLocation = nil): IPipeSelectorList; static;
class function PipeInput(
const AStream: IIdentifierNode;
const ASelectors: IPipeSelectorList;
const Loc: ISourceLocation = nil
): IPipeInputNode; static;
class function Pipe(
const AInputs: TArray<IPipeInputNode>;
const AInputs: ITupleNode;
const ATransformation: ILambdaExpressionNode;
const Loc: ISourceLocation = nil
): IPipeNode; overload; static;
class function Pipe(
const Identity: IAstIdentity;
const AInputs: IPipeInputList;
const AInputs: ITupleNode;
const ATransformation: ILambdaExpressionNode;
const AStaticType: IStaticType = nil
): IPipeNode; overload; static;
@@ -892,38 +884,15 @@ end;
// --- PIPES ---
class function TAst.PipeSelectorList(const AKeywords: TArray<IKeywordNode>; const Loc: ISourceLocation): IPipeSelectorList;
begin
var listId := TIdentities.List('[', ']', ' ', Loc);
Result := TPipeSelectorList.Create(AKeywords, listId);
end;
class function TAst.PipeInput(
const AStream: IIdentifierNode;
const ASelectors: IPipeSelectorList;
const Loc: ISourceLocation
): IPipeInputNode;
class function TAst.Pipe(const AInputs: ITupleNode; const ATransformation: ILambdaExpressionNode; const Loc: ISourceLocation): IPipeNode;
begin
var id := TIdentities.Structural(Loc);
Result := TPipeInputNode.Create(AStream, ASelectors, id, TTypes.Unknown);
end;
class function TAst.Pipe(
const AInputs: TArray<IPipeInputNode>;
const ATransformation: ILambdaExpressionNode;
const Loc: ISourceLocation
): IPipeNode;
begin
var listId := TIdentities.List('[', ']', ' ', Loc);
var inputList := TPipeInputList.Create(AInputs, listId);
var id := TIdentities.Structural(Loc);
Result := TPipeNode.Create(inputList, ATransformation, id, TTypes.Unknown);
Result := TPipeNode.Create(AInputs, ATransformation, id, TTypes.Unknown);
end;
class function TAst.Pipe(
const Identity: IAstIdentity;
const AInputs: IPipeInputList;
const AInputs: ITupleNode;
const ATransformation: ILambdaExpressionNode;
const AStaticType: IStaticType
): IPipeNode;
+21 -140
View File
@@ -11,7 +11,7 @@ uses
Myc.Fmx.AstEditor.Node;
type
// Unified handler for all list-like structures (Parameters, Arguments, Vectors, etc.)
// Unified handler for all list-like structures (Parameters, Arguments, Vectors, and Pipe-Inputs)
TTupleHandler = class(TBaseNodeHandler<ITupleNode>)
private
FChildNodes: TList<TAstViewNode>;
@@ -22,30 +22,11 @@ type
function ReconstructAst(OwnerNode: TAstViewNode): IAstNode; override;
end;
// --- Pipe Specific Lists (Not yet unified) ---
TPipeSelectorListHandler = class(TBaseNodeHandler<IPipeSelectorList>)
private
FChildNodes: TList<TAstViewNode>;
public
constructor Create(const ANode: IPipeSelectorList);
destructor Destroy; override;
procedure BuildUI(OwnerNode: TAstViewNode); override;
function ReconstructAst(OwnerNode: TAstViewNode): IAstNode; override;
end;
TPipeInputListHandler = class(TBaseNodeHandler<IPipeInputList>)
private
FChildNodes: TList<TAstViewNode>;
public
constructor Create(const ANode: IPipeInputList);
destructor Destroy; override;
procedure BuildUI(OwnerNode: TAstViewNode); override;
function ReconstructAst(OwnerNode: TAstViewNode): IAstNode; override;
end;
implementation
uses
System.UITypes;
{ TTupleHandler }
constructor TTupleHandler.Create(const ANode: ITupleNode);
@@ -65,20 +46,26 @@ var
visu: IAstVisualizer;
i: Integer;
childView: TAstViewNode;
lbl: TTextNode;
begin
// Tuples act as lists, generally horizontal flow (like [1 2 3]).
// Note: If Vertical flow is needed (e.g. for Blocks), the Parent View should probably
// override this or we need a context flag. Defaulting to Horizontal.
// Tuples act as lists, using horizontal flow (e.g. [1 2 3] or [[src [:sel]]])
OwnerNode.Frameless := True;
OwnerNode.Orientation := loHorizontal;
OwnerNode.Alignment := laCenter;
// Pass same depth, or increment? Usually lists don't indent depth unless they are blocks.
// Opening Bracket
lbl := OwnerNode.AddLabel(OwnerNode, '[');
lbl.FontSettings.Font.Style := [TFontStyle.fsBold];
lbl.Color := $FFBBBBBB;
visu := OwnerNode.Visualizer.Clone(OwnerNode, OwnerNode.Visualizer.ExprDepth);
for i := 0 to FNode.Count - 1 do
begin
// Standard spacing between elements
if i > 0 then
OwnerNode.AddLabel(OwnerNode, ' ');
childView := visu.CallAccept(FNode.Items[i]);
FChildNodes.Add(childView);
end;
@@ -86,9 +73,13 @@ begin
// Visual hint for empty tuples
if FNode.Count = 0 then
begin
var lbl := OwnerNode.AddLabel(OwnerNode, '[]');
lbl.FontSettings.FontColor := $FF808080;
OwnerNode.AddLabel(OwnerNode, ' '); // spacer
end;
// Closing Bracket
lbl := OwnerNode.AddLabel(OwnerNode, ']');
lbl.FontSettings.Font.Style := [TFontStyle.fsBold];
lbl.Color := $FFBBBBBB;
end;
function TTupleHandler.ReconstructAst(OwnerNode: TAstViewNode): IAstNode;
@@ -103,114 +94,4 @@ begin
Result := TAst.Tuple(FNode.Identity, newElements, FNode.StaticType);
end;
{ TPipeSelectorListHandler }
constructor TPipeSelectorListHandler.Create(const ANode: IPipeSelectorList);
begin
inherited Create(ANode);
FChildNodes := TList<TAstViewNode>.Create;
end;
destructor TPipeSelectorListHandler.Destroy;
begin
FChildNodes.Free;
inherited;
end;
procedure TPipeSelectorListHandler.BuildUI(OwnerNode: TAstViewNode);
var
visu: IAstVisualizer;
item: IKeywordNode;
begin
OwnerNode.Frameless := True;
OwnerNode.Orientation := loHorizontal;
visu := OwnerNode.Visualizer.Clone(OwnerNode, OwnerNode.Visualizer.ExprDepth);
for item in FNode do
FChildNodes.Add(visu.CallAccept(item));
end;
function TPipeSelectorListHandler.ReconstructAst(OwnerNode: TAstViewNode): IAstNode;
var
newItems: TArray<IKeywordNode>;
i: Integer;
begin
SetLength(newItems, FChildNodes.Count);
for i := 0 to FChildNodes.Count - 1 do
newItems[i] := FChildNodes[i].CreateAst.AsKeyword;
Result := TAst.PipeSelectorList(newItems, FNode.Identity.Location);
end;
{ TPipeInputListHandler }
constructor TPipeInputListHandler.Create(const ANode: IPipeInputList);
begin
inherited Create(ANode);
FChildNodes := TList<TAstViewNode>.Create;
end;
destructor TPipeInputListHandler.Destroy;
begin
FChildNodes.Free;
inherited;
end;
procedure TPipeInputListHandler.BuildUI(OwnerNode: TAstViewNode);
var
visu: IAstVisualizer;
item: IPipeInputNode;
begin
OwnerNode.Frameless := True;
OwnerNode.Orientation := loHorizontal; // Inputs flow horizontally in pipe def
visu := OwnerNode.Visualizer.Clone(OwnerNode, OwnerNode.Visualizer.ExprDepth);
for item in FNode do
FChildNodes.Add(visu.CallAccept(item));
end;
function TPipeInputListHandler.ReconstructAst(OwnerNode: TAstViewNode): IAstNode;
var
newItems: TArray<IPipeInputNode>;
i: Integer;
begin
SetLength(newItems, FChildNodes.Count);
for i := 0 to FChildNodes.Count - 1 do
newItems[i] := FChildNodes[i].CreateAst.AsPipeInput;
Result := TAst.Pipe(newItems, nil, FNode.Identity.Location).AsPipe.Inputs; // Hack to reuse factory logic or create direct?
// Better: Since TAst doesn't expose PipeInputList factory directly (it's internal to Pipe factory),
// we should really rely on the PipeNode handler to reconstruct the list.
// However, if we must return a node here:
// We assume TAst has a factory or we create the implementation directly if exposed.
// Since we don't have direct access to TPipeInputList.Create here (it is in implementation of Nodes),
// we use a workaround via the generic TAst.Pipe factory which takes an array.
// Actually, looking at TAst.Pipe overload that takes TArray<IPipeInputNode>, it creates the list internally.
// But here we need to return the list object itself.
// Since this method is likely called by the PipeHandler to reconstruct its children,
// the PipeHandler usually calls CreateAst on the child nodes.
// If the ViewNode for the list returns the list AST, we are good.
// BUT: We cannot instantiate the list implementation here easily without the factory exposing it.
// FIX: We need to expose a factory for PipeInputList in TAst or make the implementation accessible.
// For this Proof of Concept, I will assume TAst has a method or we added one.
// Let's verify Myc.Ast.pas ... It doesn't have PipeInputList factory public.
// It has: class function Pipe(const AInputs: TArray<IPipeInputNode> ...
// Workaround: We cast to TAstNodeList and create it, assuming we can access the class if we add it to interface?
// No, TImplementation classes are usually hidden.
// CORRECT FIX: The PipeHandler should probably manage the list reconstruction if it's just an array in the factory.
// However, to satisfy the interface, let's assume we added `TAst.PipeInputList` factory.
// I will add a TO-DO comment or use a hack.
// REALITY CHECK: In the previous step for Myc.Ast, I added:
// class function Pipe(const AInputs: IPipeInputList ...
// But I did NOT add a factory to create IPipeInputList standalone.
// I will use a dummy return or raise exception, as typically the Pipe Handler reconstructs the whole pipe
// using arrays, not by asking the list-view to reconstruct itself.
raise ENotSupportedException.Create('Reconstructing PipeInputList directly is not supported. Reconstruct the Pipe Node instead.');
end;
end.
+10 -114
View File
@@ -13,8 +13,8 @@ uses
Myc.Fmx.AstEditor.Handlers;
type
// --- Pipe Container ---
// --- Pipe Container Handler ---
// Repräsentiert die (pipe [[src [:sel]]...] (fn ...)) Struktur.
TPipeNodeHandler = class(TBaseNodeHandler<IPipeNode>)
private
FInputsNode: TAstViewNode;
@@ -24,34 +24,6 @@ type
function ReconstructAst(OwnerNode: TAstViewNode): IAstNode; override;
end;
// --- Pipe Inputs ---
TPipeInputListHandler = class(TNodeListHandler<IPipeInputNode>)
protected
function GetOrientation: TLayoutOrientation; override;
function GetAlignment: TLayoutAlignment; override;
public
function ReconstructAst(OwnerNode: TAstViewNode): IAstNode; override;
end;
TPipeInputNodeHandler = class(TBaseNodeHandler<IPipeInputNode>)
private
FStreamNode: TAstViewNode;
FSelectorsNode: TAstViewNode;
public
procedure BuildUI(OwnerNode: TAstViewNode); override;
function ReconstructAst(OwnerNode: TAstViewNode): IAstNode; override;
end;
// --- Selectors ---
TPipeSelectorListHandler = class(TNodeListHandler<IKeywordNode>)
protected
function GetOrientation: TLayoutOrientation; override;
public
function ReconstructAst(OwnerNode: TAstViewNode): IAstNode; override;
end;
implementation
{ TPipeNodeHandler }
@@ -68,7 +40,7 @@ begin
OwnerNode.Orientation := loVertical;
OwnerNode.Alignment := laCenter;
// Distinct background color for Pipes to make them stand out as data flows
// Visuelles Styling für den Dataflow-Container
OwnerNode.BackgroundColor := $FF202530;
OwnerNode.BorderColor := TAlphaColors.Slateblue;
@@ -77,101 +49,25 @@ begin
header.FontSettings.Font.Style := [TFontStyle.fsBold];
header.Color := TAlphaColors.Lightskyblue;
// Section 1: Inputs
// Sektion 1: Inputs
// FNode.Inputs ist jetzt ITupleNode. CallAccept triggert automatisch
// den TTupleHandler aus Myc.Fmx.AstEditor.Handlers.Lists.
FInputsNode := visu.CallAccept(FNode.Inputs);
// Visual Separator / Direction indicator
// Visueller Trenner
arrow := OwnerNode.AddLabel(OwnerNode, '▼ Transform ▼');
arrow.FontSettings.Font.Size := 10;
arrow.Color := TAlphaColors.Gray;
arrow.Margins := TMarginRect.Create(0, 5, 0, 5);
// Section 2: Transformation (Lambda)
// Sektion 2: Transformation (Lambda)
FTransformNode := visu.CallAccept(FNode.Transformation);
end;
function TPipeNodeHandler.ReconstructAst(OwnerNode: TAstViewNode): IAstNode;
begin
Result :=
TAst.Pipe(FNode.Identity, FInputsNode.CreateAst.AsPipeInputList, FTransformNode.CreateAst.AsLambdaExpression, FNode.StaticType);
end;
{ TPipeInputListHandler }
function TPipeInputListHandler.GetOrientation: TLayoutOrientation;
begin
// Stack inputs vertically
Result := loVertical;
end;
function TPipeInputListHandler.GetAlignment: TLayoutAlignment;
begin
Result := laFlush;
end;
function TPipeInputListHandler.ReconstructAst(OwnerNode: TAstViewNode): IAstNode;
var
arr: TArray<IPipeInputNode>;
i: Integer;
begin
SetLength(arr, FChildNodes.Count);
for i := 0 to FChildNodes.Count - 1 do
arr[i] := FChildNodes[i].CreateAst.AsPipeInput;
Result := TPipeInputList.Create(arr, FNode.Identity);
end;
{ TPipeInputNodeHandler }
procedure TPipeInputNodeHandler.BuildUI(OwnerNode: TAstViewNode);
var
visu: IAstVisualizer;
begin
visu := OwnerNode.Visualizer.Clone(OwnerNode, OwnerNode.Visualizer.ExprDepth);
OwnerNode.Frameless := True;
OwnerNode.Orientation := loHorizontal;
OwnerNode.Alignment := laCenter;
// "StreamName [ :Col :Col ]"
FStreamNode := visu.CallAccept(FNode.StreamSource);
// Add small spacing
var spacer := TAutoFitLayout.Create(OwnerNode.Host);
spacer.Size := TSizeF.Create(5, 5);
OwnerNode.AddChild(spacer);
FSelectorsNode := visu.CallAccept(FNode.Selectors);
end;
function TPipeInputNodeHandler.ReconstructAst(OwnerNode: TAstViewNode): IAstNode;
begin
Result :=
TAst.PipeInput(
FNode.StreamSource, // Identifiers are immutable usually, or re-create if needed
FSelectorsNode.CreateAst.AsPipeSelectorList,
FNode.Identity.Location
);
end;
{ TPipeSelectorListHandler }
function TPipeSelectorListHandler.GetOrientation: TLayoutOrientation;
begin
// Selectors are horizontal: [:A :B :C]
Result := loHorizontal;
end;
function TPipeSelectorListHandler.ReconstructAst(OwnerNode: TAstViewNode): IAstNode;
var
arr: TArray<IKeywordNode>;
i: Integer;
begin
SetLength(arr, FChildNodes.Count);
for i := 0 to FChildNodes.Count - 1 do
arr[i] := FChildNodes[i].CreateAst.AsKeyword;
Result := TPipeSelectorList.Create(arr, FNode.Identity);
// Rekonstruktion nutzt das nun generische ITupleNode für die Inputs
Result := TAst.Pipe(FNode.Identity, FInputsNode.CreateAst.AsTuple, FTransformNode.CreateAst.AsLambdaExpression, FNode.StaticType);
end;
end.
+6 -46
View File
@@ -15,7 +15,7 @@ uses
type
// Implementation of the Factory/Visitor for the Virtual Tree
// Uses TAstVisitor<TAstViewNode> which now uses closures for dispatch.
// Uses TAstVisitor<TAstViewNode> which uses closures for dispatch.
TAstVisualizer = class(TAstVisitor<TAstViewNode>, IAstVisualizer)
private
FExprDepth: Integer;
@@ -77,9 +77,6 @@ type
function VisitSeriesLength(const Node: IAstNode): TAstViewNode;
// Pipes
function VisitPipeInput(const Node: IAstNode): TAstViewNode;
function VisitPipeSelectorList(const Node: IAstNode): TAstViewNode;
function VisitPipeInputList(const Node: IAstNode): TAstViewNode;
function VisitPipe(const Node: IAstNode): TAstViewNode;
public
@@ -101,7 +98,8 @@ uses
Myc.Fmx.AstEditor.Handlers.Data,
Myc.Fmx.AstEditor.Handlers.Control,
Myc.Fmx.AstEditor.Handlers.Primitives,
Myc.Fmx.AstEditor.Handlers.Lists;
Myc.Fmx.AstEditor.Handlers.Lists,
Myc.Fmx.AstEditor.Handlers.Pipes;
function IsStandardOperator(const Name: string): Boolean;
begin
@@ -166,10 +164,7 @@ begin
Register(akAddSeriesItem, VisitAddSeriesItem);
Register(akSeriesLength, VisitSeriesLength);
// Pipes
Register(akPipeInput, VisitPipeInput);
Register(akPipeSelectorList, VisitPipeSelectorList);
Register(akPipeInputList, VisitPipeInputList);
// Pipes (Simplified)
Register(akPipe, VisitPipe);
end;
@@ -208,7 +203,7 @@ begin
Result := FWorkspace;
end;
// --- Visitor Implementations (Cast-heavy for efficiency) ---
// --- Visitor Implementations ---
function TAstVisualizer.VisitConstant(const Node: IAstNode): TAstViewNode;
begin
@@ -230,8 +225,6 @@ begin
Result := TAstViewNode.Create(Self, TNopNodeHandler.Create(Node.AsNop));
end;
// Lists
function TAstVisualizer.VisitTuple(const Node: IAstNode): TAstViewNode;
begin
Result := TAstViewNode.Create(Self, TTupleHandler.Create(Node.AsTuple));
@@ -242,8 +235,6 @@ begin
Result := TAstViewNode.Create(Self, TRecordFieldHandler.Create(Node.AsRecordField));
end;
// Control Flow
function TAstVisualizer.VisitBlockExpression(const Node: IAstNode): TAstViewNode;
begin
Result := TAstViewNode.Create(Self, TBlockExpressionNodeHandler.Create(Node.AsBlockExpression));
@@ -264,8 +255,6 @@ begin
Result := TAstViewNode.Create(Self, TRecurNodeHandler.Create(Node.AsRecur));
end;
// Functions
function TAstVisualizer.VisitLambdaExpression(const Node: IAstNode): TAstViewNode;
begin
Result := TAstViewNode.Create(Self, TLambdaExpressionNodeHandler.Create(Node.AsLambdaExpression));
@@ -302,8 +291,6 @@ begin
Result := TAstViewNode.Create(Self, TMacroExpansionNodeHandler.Create(Node.AsMacroExpansion));
end;
// Declarations
function TAstVisualizer.VisitVariableDeclaration(const Node: IAstNode): TAstViewNode;
begin
Result := TAstViewNode.Create(Self, TVariableDeclarationNodeHandler.Create(Node.AsVariableDeclaration));
@@ -319,8 +306,6 @@ begin
Result := TAstViewNode.Create(Self, TMacroDefinitionNodeHandler.Create(Node.AsMacroDefinition));
end;
// Metaprogramming
function TAstVisualizer.VisitQuasiquote(const Node: IAstNode): TAstViewNode;
begin
Result := TAstViewNode.Create(Self, TQuasiquoteNodeHandler.Create(Node.AsQuasiquote));
@@ -336,8 +321,6 @@ begin
Result := TAstViewNode.Create(Self, TUnquoteSplicingNodeHandler.Create(Node.AsUnquoteSplicing));
end;
// Data Access
function TAstVisualizer.VisitIndexer(const Node: IAstNode): TAstViewNode;
begin
Result := TAstViewNode.Create(Self, TIndexerNodeHandler.Create(Node.AsIndexer));
@@ -353,8 +336,6 @@ begin
Result := TAstViewNode.Create(Self, TRecordLiteralNodeHandler.Create(Node.AsRecordLiteral));
end;
// Series
function TAstVisualizer.VisitCreateSeries(const Node: IAstNode): TAstViewNode;
begin
Result := TAstViewNode.Create(Self, TCreateSeriesNodeHandler.Create(Node.AsCreateSeries));
@@ -370,30 +351,9 @@ begin
Result := TAstViewNode.Create(Self, TSeriesLengthNodeHandler.Create(Node.AsSeriesLength));
end;
// Pipes
function TAstVisualizer.VisitPipeInput(const Node: IAstNode): TAstViewNode;
begin
// Needs a dedicated handler for better visualization, fallback to Nop for now?
// Actually, we don't have a specific handler for PipeInputNode in the library yet.
// It's a typed node with children, so we should probably create one or reuse generic.
// For this refactoring step, I'll use Nop to keep it compiling, but this should be improved.
Result := TAstViewNode.Create(Self, TNopNodeHandler.Create(Node.AsNop));
end;
function TAstVisualizer.VisitPipeSelectorList(const Node: IAstNode): TAstViewNode;
begin
Result := TAstViewNode.Create(Self, TPipeSelectorListHandler.Create(Node.AsPipeSelectorList));
end;
function TAstVisualizer.VisitPipeInputList(const Node: IAstNode): TAstViewNode;
begin
Result := TAstViewNode.Create(Self, TPipeInputListHandler.Create(Node.AsPipeInputList));
end;
function TAstVisualizer.VisitPipe(const Node: IAstNode): TAstViewNode;
begin
Result := TAstViewNode.Create(Self, TNopNodeHandler.Create(Node.AsNop));
Result := TAstViewNode.Create(Self, TPipeNodeHandler.Create(Node.AsPipe));
end;
end.