From 264314cd9357f1379a495334c433105755366cc9 Mon Sep 17 00:00:00 2001 From: Michael Schimmel Date: Mon, 5 Jan 2026 00:13:51 +0100 Subject: [PATCH] Pipe Parameter is now a Tuple --- Src/AST/Myc.Ast.Analysis.Purity.pas | 33 +- Src/AST/Myc.Ast.Compiler.TypeChecker.pas | 203 ++++++----- Src/AST/Myc.Ast.Dumper.pas | 97 +----- Src/AST/Myc.Ast.Evaluator.pas | 53 ++- Src/AST/Myc.Ast.JSON.pas | 341 +++++-------------- Src/AST/Myc.Ast.Nodes.pas | 250 +------------- Src/AST/Myc.Ast.Script.Print.pas | 50 +-- Src/AST/Myc.Ast.Script.pas | 52 +-- Src/AST/Myc.Ast.Visitor.pas | 103 +----- Src/AST/Myc.Ast.pas | 41 +-- Src/AST/Myc.Fmx.AstEditor.Handlers.Lists.pas | 161 ++------- Src/AST/Myc.Fmx.AstEditor.Handlers.Pipes.pas | 124 +------ Src/AST/Myc.Fmx.AstEditor.Visualizer.pas | 52 +-- Test/AST/Test.Myc.Ast.Pipes.pas | 4 +- Test/Scripts/pipe1.txt | 2 +- 15 files changed, 322 insertions(+), 1244 deletions(-) diff --git a/Src/AST/Myc.Ast.Analysis.Purity.pas b/Src/AST/Myc.Ast.Analysis.Purity.pas index 57a3678..c3eb116 100644 --- a/Src/AST/Myc.Ast.Analysis.Purity.pas +++ b/Src/AST/Myc.Ast.Analysis.Purity.pas @@ -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; diff --git a/Src/AST/Myc.Ast.Compiler.TypeChecker.pas b/Src/AST/Myc.Ast.Compiler.TypeChecker.pas index d1635de..96ca832 100644 --- a/Src/AST/Myc.Ast.Compiler.TypeChecker.pas +++ b/Src/AST/Myc.Ast.Compiler.TypeChecker.pas @@ -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; - streamType: IStaticType; + newInputs: TList; paramTypes: TList; lambda: ILambdaExpressionNode; newParams: TArray; newParamsAsNodes: TArray; inferredType: IStaticType; + + // Iteration vars + inputPair: IAstNode; + rawTuple: ITupleNode; + streamNode: IIdentifierNode; + selectorsNode: ITupleNode; + newSelectors: TList; + streamType: IStaticType; begin P := Node.AsPipe; - SetLength(newInputs, P.Inputs.Count); + rawInputs := P.Inputs; // This is the Tuple of Tuples [[id [sel]] ...] + + newInputs := TList.Create; paramTypes := TList.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.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; diff --git a/Src/AST/Myc.Ast.Dumper.pas b/Src/AST/Myc.Ast.Dumper.pas index 5013651..b8754a7 100644 --- a/Src/AST/Myc.Ast.Dumper.pas +++ b/Src/AST/Myc.Ast.Dumper.pas @@ -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(' ', [staticType.ToString]); - end + typeStr := Format(' ', [staticType.ToString]) else - begin typeStr := ' '; - 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; 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); diff --git a/Src/AST/Myc.Ast.Evaluator.pas b/Src/AST/Myc.Ast.Evaluator.pas index 63bdc73..99559ee 100644 --- a/Src/AST/Myc.Ast.Evaluator.pas +++ b/Src/AST/Myc.Ast.Evaluator.pas @@ -454,35 +454,61 @@ end; function TEvaluatorVisitor.VisitPipe(const N: IPipeNode): TDataValue; var i: Integer; - inputNode: IPipeInputNode; sources: TArray; 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; diff --git a/Src/AST/Myc.Ast.JSON.pas b/Src/AST/Myc.Ast.JSON.pas index 827aaf2..1465fe0 100644 --- a/Src/AST/Myc.Ast.JSON.pas +++ b/Src/AST/Myc.Ast.JSON.pas @@ -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, 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('Value'); - end + Result := valObj.GetValue('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('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('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; - paramArray: TJSONArray; - i: Integer; + identNodes: TList; + node: IAstNode; begin - paramArray := AObj.GetValue('Parameters'); - SetLength(params, paramArray.Count); - for i := 0 to paramArray.Count - 1 do - begin - params[i] := JsonToIdentifierNode(paramArray.Items[i] as TJSONObject); + identNodes := TList.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; - paramArray: TJSONArray; - i: Integer; + identNodes: TList; + node: IAstNode; begin - paramArray := AObj.GetValue('Parameters'); - SetLength(params, paramArray.Count); - for i := 0 to paramArray.Count - 1 do - begin - params[i] := JsonToIdentifierNode(paramArray.Items[i] as TJSONObject); + identNodes := TList.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; - argsArray: TJSONArray; - i: Integer; begin - argsArray := AObj.GetValue('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; - argsArray: TJSONArray; - i: Integer; - tempCallNode: IFunctionCallNode; + argsTuple: ITupleNode; begin callee := JsonToNode(AObj.GetValue('Callee')); expandedBody := JsonToNode(AObj.GetValue('ExpandedBody')); - argsArray := AObj.GetValue('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; - argsArray: TJSONArray; - i: Integer; begin - argsArray := AObj.GetValue('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; - exprsArray: TJSONArray; - i: Integer; begin - exprsArray := AObj.GetValue('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; fieldsArray: TJSONArray; + fields: TList; i: Integer; begin fieldsArray := AObj.GetValue('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('Name')), - JsonToNode((fieldsArray.Items[i] as TJSONObject).GetValue('Value')) - ); + fields := TList.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('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; elemsArray: TJSONArray; + elems: TList; i: Integer; begin elemsArray := AObj.GetValue('Elements'); - SetLength(elems, elemsArray.Count); - for i := 0 to elemsArray.Count - 1 do - begin - elems[i] := JsonToNode(elemsArray.Items[i]); + elems := TList.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. diff --git a/Src/AST/Myc.Ast.Nodes.pas b/Src/AST/Myc.Ast.Nodes.pas index fa03075..cf49d28 100644 --- a/Src/AST/Myc.Ast.Nodes.pas +++ b/Src/AST/Myc.Ast.Nodes.pas @@ -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 = interface(IAstNode) - {$region 'private'} - function GetCount: Integer; - function GetItem(Index: Integer): T; - {$endregion} - function GetEnumerator: TEnumerator; - function ToArray: TArray; - 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) - 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) - 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 = class(TAstNode, INodeList) - type - TEnumerator = class(TEnumerator) - private - FArray: TArray; - FIndex: NativeInt; - function GetCurrent: T; inline; - protected - function DoGetCurrent: T; override; - function DoMoveNext: Boolean; override; - public - constructor Create(const AArray: TArray); - function MoveNext: Boolean; inline; - property Current: T read GetCurrent; - end; - private - FItems: TArray; - function GetCount: Integer; - function GetItem(Index: Integer): T; - public - constructor Create(const AItems: TArray; const AIdentity: IAstIdentity); - function GetEnumerator: TEnumerator; - function ToArray: TArray; - 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, 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, 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 } - -constructor TAstNodeList.Create(const AItems: TArray; const AIdentity: IAstIdentity); -begin - inherited Create(AIdentity); - FItems := AItems; -end; - -function TAstNodeList.GetCount: Integer; -begin - Result := Length(FItems); -end; - -function TAstNodeList.GetEnumerator: TEnumerator; -begin - Result := TEnumerator.Create(FItems); -end; - -function TAstNodeList.GetItem(Index: Integer): T; -begin - Result := FItems[Index]; -end; - -function TAstNodeList.ToArray: TArray; -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.TEnumerator.Create(const AArray: TArray); -begin - inherited Create; - FArray := AArray; - FIndex := -1; -end; - -function TAstNodeList.TEnumerator.DoGetCurrent: T; -begin - Result := Current; -end; - -function TAstNodeList.TEnumerator.DoMoveNext: Boolean; -begin - Result := MoveNext; -end; - -function TAstNodeList.TEnumerator.GetCurrent: T; -begin - Result := FArray[FIndex]; -end; - -function TAstNodeList.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; diff --git a/Src/AST/Myc.Ast.Script.Print.pas b/Src/AST/Myc.Ast.Script.Print.pas index 92ac8c1..9def21a 100644 --- a/Src/AST/Myc.Ast.Script.Print.pas +++ b/Src/AST/Myc.Ast.Script.Print.pas @@ -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; diff --git a/Src/AST/Myc.Ast.Script.pas b/Src/AST/Myc.Ast.Script.pas index 302111e..a8eed4a 100644 --- a/Src/AST/Myc.Ast.Script.pas +++ b/Src/AST/Myc.Ast.Script.pas @@ -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.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; - - 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 diff --git a/Src/AST/Myc.Ast.Visitor.pas b/Src/AST/Myc.Ast.Visitor.pas index 161dce2..8a8e8d0 100644 --- a/Src/AST/Myc.Ast.Visitor.pas +++ b/Src/AST/Myc.Ast.Visitor.pas @@ -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; - 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; - 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; diff --git a/Src/AST/Myc.Ast.pas b/Src/AST/Myc.Ast.pas index 3df8b89..1f6a726 100644 --- a/Src/AST/Myc.Ast.pas +++ b/Src/AST/Myc.Ast.pas @@ -269,23 +269,15 @@ type // --- PIPES --- - class function PipeSelectorList(const AKeywords: TArray; 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; + 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; 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; - 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; diff --git a/Src/AST/Myc.Fmx.AstEditor.Handlers.Lists.pas b/Src/AST/Myc.Fmx.AstEditor.Handlers.Lists.pas index 0970513..15fa35d 100644 --- a/Src/AST/Myc.Fmx.AstEditor.Handlers.Lists.pas +++ b/Src/AST/Myc.Fmx.AstEditor.Handlers.Lists.pas @@ -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) private FChildNodes: TList; @@ -22,30 +22,11 @@ type function ReconstructAst(OwnerNode: TAstViewNode): IAstNode; override; end; - // --- Pipe Specific Lists (Not yet unified) --- - - TPipeSelectorListHandler = class(TBaseNodeHandler) - private - FChildNodes: TList; - public - constructor Create(const ANode: IPipeSelectorList); - destructor Destroy; override; - procedure BuildUI(OwnerNode: TAstViewNode); override; - function ReconstructAst(OwnerNode: TAstViewNode): IAstNode; override; - end; - - TPipeInputListHandler = class(TBaseNodeHandler) - private - FChildNodes: TList; - 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.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; - 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.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; - 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, 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 ... - - // 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. diff --git a/Src/AST/Myc.Fmx.AstEditor.Handlers.Pipes.pas b/Src/AST/Myc.Fmx.AstEditor.Handlers.Pipes.pas index b2628d5..992e427 100644 --- a/Src/AST/Myc.Fmx.AstEditor.Handlers.Pipes.pas +++ b/Src/AST/Myc.Fmx.AstEditor.Handlers.Pipes.pas @@ -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) private FInputsNode: TAstViewNode; @@ -24,34 +24,6 @@ type function ReconstructAst(OwnerNode: TAstViewNode): IAstNode; override; end; - // --- Pipe Inputs --- - - TPipeInputListHandler = class(TNodeListHandler) - protected - function GetOrientation: TLayoutOrientation; override; - function GetAlignment: TLayoutAlignment; override; - public - function ReconstructAst(OwnerNode: TAstViewNode): IAstNode; override; - end; - - TPipeInputNodeHandler = class(TBaseNodeHandler) - private - FStreamNode: TAstViewNode; - FSelectorsNode: TAstViewNode; - public - procedure BuildUI(OwnerNode: TAstViewNode); override; - function ReconstructAst(OwnerNode: TAstViewNode): IAstNode; override; - end; - - // --- Selectors --- - - TPipeSelectorListHandler = class(TNodeListHandler) - 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; - 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; - 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. diff --git a/Src/AST/Myc.Fmx.AstEditor.Visualizer.pas b/Src/AST/Myc.Fmx.AstEditor.Visualizer.pas index e50e79b..696fe46 100644 --- a/Src/AST/Myc.Fmx.AstEditor.Visualizer.pas +++ b/Src/AST/Myc.Fmx.AstEditor.Visualizer.pas @@ -15,7 +15,7 @@ uses type // Implementation of the Factory/Visitor for the Virtual Tree - // Uses TAstVisitor which now uses closures for dispatch. + // Uses TAstVisitor which uses closures for dispatch. TAstVisualizer = class(TAstVisitor, 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. diff --git a/Test/AST/Test.Myc.Ast.Pipes.pas b/Test/AST/Test.Myc.Ast.Pipes.pas index 267373d..4adc09f 100644 --- a/Test/AST/Test.Myc.Ast.Pipes.pas +++ b/Test/AST/Test.Myc.Ast.Pipes.pas @@ -195,7 +195,7 @@ begin FEnv.RootScope.Define('src', mockStream, TTypes.CreateRecordSeries(source.Def)); // Act - script := '(pipe [src [:val]] (fn [x] {:res (* x 2)}))'; + script := '(pipe [[src [:val]]] (fn [x] {:res (* x 2)}))'; // 1. Compile & Link & Run (Constructs the Pipe Object) resVal := FEnv.Run(TAstScript.Parse(script)); @@ -253,7 +253,7 @@ begin FEnv.RootScope.Define('in2', m2, TTypes.CreateRecordSeries(s2.Def)); // Act - script := '(pipe [in1 [:a] in2 [:b]] (fn [valA valB] {:sum (+ valA valB)}))'; + script := '(pipe [[in1 [:a]] [in2 [:b]]] (fn [valA valB] {:sum (+ valA valB)}))'; resSeries := FEnv.Run(TAstScript.Parse(script)).AsStream.Series; diff --git a/Test/Scripts/pipe1.txt b/Test/Scripts/pipe1.txt index 2fa97ed..e3720ca 100644 --- a/Test/Scripts/pipe1.txt +++ b/Test/Scripts/pipe1.txt @@ -1,5 +1,5 @@ ;; EXPECT: (do - (def btc-ma (pipe [btc [:Close]] (fn [c] (do {:ma c})))) + (def btc-ma (pipe [[btc [:Close]]] (fn [c] (do {:ma c})))) (.ma btc-ma) ) \ No newline at end of file