unit Myc.Ast.Visitor; interface uses System.SysUtils, System.Generics.Collections, Myc.Data.Value, Myc.Ast, Myc.Ast.Types, Myc.Ast.Nodes, Myc.Ast.Identities; type TAstVisitor = class; // Delegate for handling a specific node kind within the visitor context TNodeHandler = reference to function(const Node: IAstNode): T; TRegisterHandler = reference to procedure(Kind: TAstNodeKind; const Handler: TNodeHandler); TAstVisitor = class abstract(TInterfacedObject, IAstVisitor) strict private // The registry for O(1) dispatch based on TAstNodeKind FHandlers: array[TAstNodeKind] of TNodeHandler; // IAstVisitor explicit implementation (bridge methods) function IAstVisitor.Visit = VisitBridge; // Private helpers to wrap T into TDataValue for the non-generic interface function VisitBridge(const Node: IAstNode): TDataValue; protected // Initial setup of the dispatch table. // The base implementation maps all kinds to the legacy VisitXYZ virtual methods. procedure SetupHandlers(const Register: TRegisterHandler); virtual; // The central dispatch method. Looks up the handler in the registry. function Visit(const Node: IAstNode): T; virtual; // Legacy Virtual Methods (kept for backward compatibility and default mapping) // Subclasses should eventually move to RegisterHandler, but overriding these still works // because SetupHandlers maps them by default. function VisitConstant(const Node: IConstantNode): T; virtual; function VisitIdentifier(const Node: IIdentifierNode): T; virtual; function VisitKeyword(const Node: IKeywordNode): T; virtual; function VisitParameterList(const Node: IParameterList): T; virtual; function VisitArgumentList(const Node: IArgumentList): T; virtual; function VisitExpressionList(const Node: IExpressionList): T; virtual; function VisitRecordFieldList(const Node: IRecordFieldList): T; virtual; function VisitRecordField(const Node: IRecordFieldNode): T; virtual; function VisitIfExpression(const Node: IIfExpressionNode): T; virtual; function VisitCondExpression(const Node: ICondExpressionNode): T; virtual; function VisitLambdaExpression(const Node: ILambdaExpressionNode): T; virtual; function VisitFunctionCall(const Node: IFunctionCallNode): T; virtual; function VisitMacroExpansionNode(const Node: IMacroExpansionNode): T; virtual; function VisitBlockExpression(const Node: IBlockExpressionNode): T; virtual; function VisitVariableDeclaration(const Node: IVariableDeclarationNode): T; virtual; function VisitAssignment(const Node: IAssignmentNode): T; virtual; function VisitMacroDefinition(const Node: IMacroDefinitionNode): T; virtual; function VisitQuasiquote(const Node: IQuasiquoteNode): T; virtual; function VisitUnquote(const Node: IUnquoteNode): T; virtual; function VisitUnquoteSplicing(const Node: IUnquoteSplicingNode): T; virtual; function VisitIndexer(const Node: IIndexerNode): T; virtual; function VisitMemberAccess(const Node: IMemberAccessNode): T; virtual; function VisitRecordLiteral(const Node: IRecordLiteralNode): T; virtual; function VisitCreateSeries(const Node: ICreateSeriesNode): T; virtual; function VisitAddSeriesItem(const Node: IAddSeriesItemNode): T; virtual; function VisitSeriesLength(const Node: ISeriesLengthNode): T; virtual; function VisitRecurNode(const Node: IRecurNode): T; virtual; function VisitNop(const Node: INopNode): T; virtual; function VisitPipeInput(const Node: IPipeInputNode): T; virtual; function VisitPipeSelectorList(const Node: IPipeSelectorList): T; virtual; function VisitPipeInputList(const Node: IPipeInputList): T; virtual; function VisitPipe(const Node: IPipeNode): T; virtual; // Bridge to support calling Accept on node directly (double dispatch pattern) // In the new architecture, we prefer calling Visit(Node) directly. function Accept(const Node: IAstNode): T; virtual; public procedure AfterConstruction; override; procedure BeforeDestruction; override; end; // Helper for transformation visitors (returns IAstNode) // Overrides legacy methods to provide default recursive reconstruction behavior. TAstTransformer = class abstract(TAstVisitor) protected function VisitConstant(const Node: IConstantNode): IAstNode; override; function VisitIdentifier(const Node: IIdentifierNode): IAstNode; override; function VisitKeyword(const Node: IKeywordNode): IAstNode; override; function VisitParameterList(const Node: IParameterList): IAstNode; override; function VisitArgumentList(const Node: IArgumentList): IAstNode; override; function VisitExpressionList(const Node: IExpressionList): IAstNode; override; function VisitRecordFieldList(const Node: IRecordFieldList): IAstNode; override; function VisitRecordField(const Node: IRecordFieldNode): IAstNode; override; function VisitIfExpression(const Node: IIfExpressionNode): IAstNode; override; function VisitCondExpression(const Node: ICondExpressionNode): IAstNode; override; function VisitLambdaExpression(const Node: ILambdaExpressionNode): IAstNode; override; function VisitFunctionCall(const Node: IFunctionCallNode): IAstNode; override; function VisitMacroExpansionNode(const Node: IMacroExpansionNode): IAstNode; override; function VisitBlockExpression(const Node: IBlockExpressionNode): IAstNode; override; function VisitVariableDeclaration(const Node: IVariableDeclarationNode): IAstNode; override; function VisitAssignment(const Node: IAssignmentNode): IAstNode; override; function VisitMacroDefinition(const Node: IMacroDefinitionNode): IAstNode; override; function VisitQuasiquote(const Node: IQuasiquoteNode): IAstNode; override; function VisitUnquote(const Node: IUnquoteNode): IAstNode; override; function VisitUnquoteSplicing(const Node: IUnquoteSplicingNode): IAstNode; override; function VisitIndexer(const Node: IIndexerNode): IAstNode; override; function VisitMemberAccess(const Node: IMemberAccessNode): IAstNode; override; function VisitRecordLiteral(const Node: IRecordLiteralNode): IAstNode; override; function VisitCreateSeries(const Node: ICreateSeriesNode): IAstNode; override; function VisitAddSeriesItem(const Node: IAddSeriesItemNode): IAstNode; override; function VisitSeriesLength(const Node: ISeriesLengthNode): IAstNode; override; function VisitRecurNode(const Node: IRecurNode): IAstNode; override; function VisitNop(const Node: INopNode): IAstNode; override; function VisitPipeInput(const Node: IPipeInputNode): IAstNode; override; function VisitPipeSelectorList(const Node: IPipeSelectorList): IAstNode; override; function VisitPipeInputList(const Node: IPipeInputList): IAstNode; override; function VisitPipe(const Node: IPipeNode): IAstNode; override; end; TVoid = record end; TAstVisitor = class(TAstVisitor); implementation { TAstVisitor } procedure TAstVisitor.AfterConstruction; begin inherited; SetupHandlers(procedure(Kind: TAstNodeKind; const Handler: TNodeHandler) begin FHandlers[Kind] := Handler; end); end; procedure TAstVisitor.BeforeDestruction; begin for var i := Low(TAstNodeKind) to High(TAstNodeKind) do FHandlers[i] := nil; inherited; end; procedure TAstVisitor.SetupHandlers(const Register: TRegisterHandler); begin // Map all kinds to the legacy virtual methods. // This provides the compatibility layer for existing subclasses. // In Phase 2, subclasses will register their own handlers directly here. // Core Register(akConstant, function(const Node: IAstNode): T begin Result := VisitConstant(Node.AsConstant); end); Register(akIdentifier, function(const Node: IAstNode): T begin Result := VisitIdentifier(Node.AsIdentifier); end); Register(akKeyword, function(const Node: IAstNode): T begin Result := VisitKeyword(Node.AsKeyword); end); // Lists Register(akParameterList, function(const Node: IAstNode): T begin Result := VisitParameterList(Node.AsParameterList); end); Register(akArgumentList, function(const Node: IAstNode): T begin Result := VisitArgumentList(Node.AsArgumentList); end); Register(akExpressionList, function(const Node: IAstNode): T begin Result := VisitExpressionList(Node.AsExpressionList); end); Register(akRecordFieldList, function(const Node: IAstNode): T begin Result := VisitRecordFieldList(Node.AsRecordFieldList); end); Register(akRecordField, function(const Node: IAstNode): T begin Result := VisitRecordField(Node.AsRecordField); end); // Structural Register(akIfExpression, function(const Node: IAstNode): T begin Result := VisitIfExpression(Node.AsIfExpression); end); Register(akCondExpression, function(const Node: IAstNode): T begin Result := VisitCondExpression(Node.AsCondExpression); end); Register(akLambdaExpression, function(const Node: IAstNode): T begin Result := VisitLambdaExpression(Node.AsLambdaExpression); end); Register(akFunctionCall, function(const Node: IAstNode): T begin Result := VisitFunctionCall(Node.AsFunctionCall); end); Register(akMacroExpansion, function(const Node: IAstNode): T begin Result := VisitMacroExpansionNode(Node.AsMacroExpansion); end); Register(akBlockExpression, function(const Node: IAstNode): T begin Result := VisitBlockExpression(Node.AsBlockExpression); end); Register( akVariableDeclaration, function(const Node: IAstNode): T begin Result := VisitVariableDeclaration(Node.AsVariableDeclaration); end ); Register(akAssignment, function(const Node: IAstNode): T begin Result := VisitAssignment(Node.AsAssignment); end); Register(akMacroDefinition, function(const Node: IAstNode): T begin Result := VisitMacroDefinition(Node.AsMacroDefinition); end); Register(akQuasiquote, function(const Node: IAstNode): T begin Result := VisitQuasiquote(Node.AsQuasiquote); end); Register(akUnquote, function(const Node: IAstNode): T begin Result := VisitUnquote(Node.AsUnquote); end); Register(akUnquoteSplicing, function(const Node: IAstNode): T begin Result := VisitUnquoteSplicing(Node.AsUnquoteSplicing); end); Register(akIndexer, function(const Node: IAstNode): T begin Result := VisitIndexer(Node.AsIndexer); end); Register(akMemberAccess, function(const Node: IAstNode): T begin Result := VisitMemberAccess(Node.AsMemberAccess); end); Register(akRecordLiteral, function(const Node: IAstNode): T begin Result := VisitRecordLiteral(Node.AsRecordLiteral); end); Register(akCreateSeries, function(const Node: IAstNode): T begin Result := VisitCreateSeries(Node.AsCreateSeries); end); Register(akAddSeriesItem, function(const Node: IAstNode): T begin Result := VisitAddSeriesItem(Node.AsAddSeriesItem); end); Register(akSeriesLength, function(const Node: IAstNode): T begin Result := VisitSeriesLength(Node.AsSeriesLength); end); Register(akRecur, function(const Node: IAstNode): T begin Result := VisitRecurNode(Node.AsRecur); end); Register(akNop, function(const Node: IAstNode): T begin Result := VisitNop(Node.AsNop); end); // Pipes Register(akPipeInput, function(const Node: IAstNode): T begin Result := VisitPipeInput(Node.AsPipeInput); end); Register(akPipeSelectorList, function(const Node: IAstNode): T begin Result := VisitPipeSelectorList(Node.AsPipeSelectorList); end); Register(akPipeInputList, function(const Node: IAstNode): T begin Result := VisitPipeInputList(Node.AsPipeInputList); end); Register(akPipe, function(const Node: IAstNode): T begin Result := VisitPipe(Node.AsPipe); end); end; function TAstVisitor.Visit(const Node: IAstNode): T; var [unsafe] handler: TNodeHandler; begin if not Assigned(Node) then Exit(Default(T)); // O(1) Lookup handler := TNodeHandler(FHandlers[Node.Kind]); if Assigned(handler) then Result := handler(Node) else raise ENoHandlerException.Create(Node.Kind.ToString); end; function TAstVisitor.Accept(const Node: IAstNode): T; begin Result := Visit(Node); end; // --- Interface Bridge Methods (IAstVisitor -> TDataValue) --- function TAstVisitor.VisitBridge(const Node: IAstNode): TDataValue; begin Result := TDataValue.FromGeneric(Visit(Node)); end; // --- Default implementations (analogous to original abstract class stubs) --- function TAstVisitor.VisitConstant(const Node: IConstantNode): T; begin Result := Default(T); end; function TAstVisitor.VisitIdentifier(const Node: IIdentifierNode): T; begin Result := Default(T); end; function TAstVisitor.VisitKeyword(const Node: IKeywordNode): T; begin Result := Default(T); end; function TAstVisitor.VisitParameterList(const Node: IParameterList): T; begin Result := Default(T); for var Item in Node do Visit(Item); end; function TAstVisitor.VisitArgumentList(const Node: IArgumentList): T; begin Result := Default(T); for var Item in Node do Visit(Item); end; function TAstVisitor.VisitExpressionList(const Node: IExpressionList): T; begin Result := Default(T); for var Item in Node do Visit(Item); end; function TAstVisitor.VisitRecordFieldList(const Node: IRecordFieldList): T; begin Result := Default(T); for var Item in Node do Visit(Item); end; function TAstVisitor.VisitRecordField(const Node: IRecordFieldNode): T; begin Result := Default(T); Visit(Node.Key); Visit(Node.Value); end; function TAstVisitor.VisitIfExpression(const Node: IIfExpressionNode): T; begin Result := Default(T); Visit(Node.Condition); Visit(Node.ThenBranch); Visit(Node.ElseBranch); end; function TAstVisitor.VisitCondExpression(const Node: ICondExpressionNode): T; var pair: TCondPair; begin Result := Default(T); for pair in Node.Pairs do begin Visit(pair.Condition); Visit(pair.Branch); end; Visit(Node.ElseBranch); end; function TAstVisitor.VisitLambdaExpression(const Node: ILambdaExpressionNode): T; begin Result := Default(T); Visit(Node.Parameters); Visit(Node.Body); end; function TAstVisitor.VisitFunctionCall(const Node: IFunctionCallNode): T; begin Result := Default(T); Visit(Node.Callee); Visit(Node.Arguments); end; function TAstVisitor.VisitMacroExpansionNode(const Node: IMacroExpansionNode): T; begin Result := Default(T); Visit(Node.CallNode); Visit(Node.ExpandedBody); end; function TAstVisitor.VisitBlockExpression(const Node: IBlockExpressionNode): T; begin Result := Default(T); Visit(Node.Expressions); end; function TAstVisitor.VisitVariableDeclaration(const Node: IVariableDeclarationNode): T; begin Result := Default(T); Visit(Node.Target); Visit(Node.Initializer); end; function TAstVisitor.VisitAssignment(const Node: IAssignmentNode): T; begin Result := Default(T); Visit(Node.Target); Visit(Node.Value); end; function TAstVisitor.VisitMacroDefinition(const Node: IMacroDefinitionNode): T; begin Result := Default(T); Visit(Node.Name); Visit(Node.Parameters); Visit(Node.Body); end; function TAstVisitor.VisitQuasiquote(const Node: IQuasiquoteNode): T; begin Result := Default(T); Visit(Node.Expression); end; function TAstVisitor.VisitUnquote(const Node: IUnquoteNode): T; begin Result := Default(T); Visit(Node.Expression); end; function TAstVisitor.VisitUnquoteSplicing(const Node: IUnquoteSplicingNode): T; begin Result := Default(T); Visit(Node.Expression); end; function TAstVisitor.VisitIndexer(const Node: IIndexerNode): T; begin Result := Default(T); Visit(Node.Base); Visit(Node.Index); end; function TAstVisitor.VisitMemberAccess(const Node: IMemberAccessNode): T; begin Result := Default(T); Visit(Node.Base); Visit(Node.Member); end; function TAstVisitor.VisitRecordLiteral(const Node: IRecordLiteralNode): T; begin Result := Default(T); Visit(Node.Fields); end; function TAstVisitor.VisitCreateSeries(const Node: ICreateSeriesNode): T; begin Result := Default(T); end; function TAstVisitor.VisitAddSeriesItem(const Node: IAddSeriesItemNode): T; begin Result := Default(T); Visit(Node.Series); Visit(Node.Value); Visit(Node.Lookback); end; function TAstVisitor.VisitSeriesLength(const Node: ISeriesLengthNode): T; begin Result := Default(T); Visit(Node.Series); end; function TAstVisitor.VisitRecurNode(const Node: IRecurNode): T; begin Result := Default(T); Visit(Node.Arguments); end; function TAstVisitor.VisitNop(const Node: INopNode): T; begin Result := Default(T); end; function TAstVisitor.VisitPipeInput(const Node: IPipeInputNode): T; begin Result := Default(T); Visit(Node.StreamSource); Visit(Node.Selectors); end; function TAstVisitor.VisitPipeSelectorList(const Node: IPipeSelectorList): T; begin Result := Default(T); for var Item in Node do Visit(Item); end; function TAstVisitor.VisitPipeInputList(const Node: IPipeInputList): T; begin Result := Default(T); for var Item in Node do Visit(Item); end; function TAstVisitor.VisitPipe(const Node: IPipeNode): T; begin Result := Default(T); Visit(Node.Inputs); Visit(Node.Transformation); end; { TAstTransformer } // --- Base Virtual Implementations --- function TAstTransformer.VisitConstant(const Node: IConstantNode): IAstNode; begin Result := Node; end; function TAstTransformer.VisitIdentifier(const Node: IIdentifierNode): IAstNode; begin Result := Node; end; function TAstTransformer.VisitKeyword(const Node: IKeywordNode): IAstNode; begin Result := Node; end; function TAstTransformer.VisitNop(const Node: INopNode): IAstNode; begin Result := Node; end; function TAstTransformer.VisitCreateSeries(const Node: ICreateSeriesNode): IAstNode; begin Result := Node; end; // --- List Transformations --- function TAstTransformer.VisitParameterList(const Node: IParameterList): IAstNode; var hasChanged: Boolean; newItems: TArray; item, newItem: IIdentifierNode; i: Integer; begin hasChanged := False; SetLength(newItems, Node.Count); for i := 0 to Node.Count - 1 do begin item := Node[i]; newItem := Visit(item).AsIdentifier; newItems[i] := newItem; if item <> newItem then hasChanged := True; end; if not hasChanged then Result := Node else // Manually reconstruct using concrete class to verify transformation Result := TParameterList.Create(newItems, Node.Identity); end; function TAstTransformer.VisitArgumentList(const Node: IArgumentList): IAstNode; var hasChanged: Boolean; newItems: TArray; item, newItem: IAstNode; i: Integer; begin hasChanged := False; SetLength(newItems, Node.Count); for i := 0 to Node.Count - 1 do begin item := Node[i]; newItem := Visit(item); newItems[i] := newItem; if item <> newItem then hasChanged := True; end; if not hasChanged then Result := Node else Result := TArgumentList.Create(newItems, Node.Identity); end; function TAstTransformer.VisitExpressionList(const Node: IExpressionList): IAstNode; var hasChanged: Boolean; newItems: TArray; item, newItem: IAstNode; i: Integer; begin hasChanged := False; SetLength(newItems, Node.Count); for i := 0 to Node.Count - 1 do begin item := Node[i]; newItem := Visit(item); newItems[i] := newItem; if item <> newItem then hasChanged := True; end; if not hasChanged then Result := Node else Result := TExpressionList.Create(newItems, Node.Identity); end; function TAstTransformer.VisitRecordFieldList(const Node: IRecordFieldList): IAstNode; var hasChanged: Boolean; newItems: TArray; item, newItem: IRecordFieldNode; i: Integer; begin hasChanged := False; SetLength(newItems, Node.Count); for i := 0 to Node.Count - 1 do begin item := Node[i]; // Ensure we transform the field node itself via Visit to trigger VisitRecordField newItem := Visit(item).AsRecordField; newItems[i] := newItem; if item <> newItem then hasChanged := True; end; if not hasChanged then Result := Node else Result := TRecordFieldList.Create(newItems, Node.Identity); end; function TAstTransformer.VisitRecordField(const Node: IRecordFieldNode): IAstNode; var newKey: IKeywordNode; newValue: IAstNode; begin newKey := Visit(Node.Key).AsKeyword; newValue := Visit(Node.Value); if (newKey = Node.Key) and (newValue = Node.Value) then Result := Node else Result := TAst.RecordField(newKey, newValue, Node.Identity.Location); end; // --- Node Transformations --- function TAstTransformer.VisitIfExpression(const Node: IIfExpressionNode): IAstNode; var newCond, newThen, newElse: IAstNode; begin newCond := Visit(Node.Condition); newThen := Visit(Node.ThenBranch); newElse := Visit(Node.ElseBranch); if (newCond = Node.Condition) and (newThen = Node.ThenBranch) and (newElse = Node.ElseBranch) then Result := Node else Result := TAst.IfExpr(Node.Identity, newCond, newThen, newElse, Node.StaticType); end; function TAstTransformer.VisitCondExpression(const Node: ICondExpressionNode): IAstNode; var newPairs: TArray; newElse: IAstNode; hasChanged: Boolean; i: Integer; newCond, newBranch: IAstNode; begin hasChanged := False; SetLength(newPairs, Length(Node.Pairs)); for i := 0 to High(Node.Pairs) do begin newCond := Visit(Node.Pairs[i].Condition); newBranch := Visit(Node.Pairs[i].Branch); newPairs[i] := TCondPair.Create(newCond, newBranch); if (newCond <> Node.Pairs[i].Condition) or (newBranch <> Node.Pairs[i].Branch) then hasChanged := True; end; newElse := Visit(Node.ElseBranch); // Visit handles nil if newElse <> Node.ElseBranch then hasChanged := True; if not hasChanged then Result := Node else Result := TAst.CondExpr(Node.Identity, newPairs, newElse, Node.StaticType); end; function TAstTransformer.VisitLambdaExpression(const Node: ILambdaExpressionNode): IAstNode; var newParams: IParameterList; newBody: IAstNode; begin newParams := Visit(Node.Parameters).AsParameterList; newBody := Visit(Node.Body); if (newParams = Node.Parameters) and (newBody = Node.Body) then Result := Node else begin // Rebuild Lambda via concrete constructor to keep Layout/Descriptor info // TAst factory would reset layout info. Result := TLambdaExpressionNode.Create( newParams, newBody, Node.StaticType, Node.Layout, Node.Descriptor, Node.Upvalues, Node.HasNestedLambdas, Node.IsPure, Node.Identity ); end; end; function TAstTransformer.VisitFunctionCall(const Node: IFunctionCallNode): IAstNode; var newCallee: IAstNode; newArgs: IArgumentList; begin newCallee := Visit(Node.Callee); newArgs := Visit(Node.Arguments).AsArgumentList; if (newCallee = Node.Callee) and (newArgs = Node.Arguments) then Result := Node else begin Result := TFunctionCallNode .Create(newCallee, newArgs, Node.StaticType, Node.IsTailCall, Node.StaticTarget, Node.IsTargetPure, Node.Identity); end; end; function TAstTransformer.VisitMacroExpansionNode(const Node: IMacroExpansionNode): IAstNode; var newBody: IAstNode; begin newBody := Visit(Node.ExpandedBody); if newBody = Node.ExpandedBody then exit(Node); // Macro Expansion Nodes are structural, reuse identity. // Note: We don't visit the CallNode as it is the "source" which doesn't change during this transformation typically Result := TAst.MacroExpansionNode(Node.Identity, Node.CallNode, newBody); end; function TAstTransformer.VisitBlockExpression(const Node: IBlockExpressionNode): IAstNode; var newExprs: IExpressionList; begin newExprs := Visit(Node.Expressions).AsExpressionList; if newExprs = Node.Expressions then Result := Node else // Rebuild manually to preserve type Result := TBlockExpressionNode.Create(newExprs, Node.StaticType, Node.Identity); end; function TAstTransformer.VisitVariableDeclaration(const Node: IVariableDeclarationNode): IAstNode; var newTarget: IAstNode; newInit: IAstNode; begin newTarget := Visit(Node.Target); newInit := Visit(Node.Initializer); if (newTarget = Node.Target) and (newInit = Node.Initializer) then Result := Node else begin Result := TAst.VarDecl(Node.Identity, newTarget, newInit, Node.StaticType, Node.IsBoxed); end; end; function TAstTransformer.VisitAssignment(const Node: IAssignmentNode): IAstNode; var newTarget: IAstNode; newValue: IAstNode; begin newTarget := Visit(Node.Target); newValue := Visit(Node.Value); if (newTarget = Node.Target) and (newValue = Node.Value) then Result := Node else Result := TAst.Assign(Node.Identity, newTarget, newValue, Node.StaticType); end; function TAstTransformer.VisitMacroDefinition(const Node: IMacroDefinitionNode): IAstNode; begin // Macro definitions are usually stripped/ignored in transformers, // but we return them as-is to support partial pipelines. Result := Node; end; function TAstTransformer.VisitQuasiquote(const Node: IQuasiquoteNode): IAstNode; var newExpr: IAstNode; begin newExpr := Visit(Node.Expression); if newExpr = Node.Expression then Result := Node else Result := TAst.Quasiquote(Node.Identity, newExpr); end; function TAstTransformer.VisitUnquote(const Node: IUnquoteNode): IAstNode; var newExpr: IAstNode; begin newExpr := Visit(Node.Expression); if newExpr = Node.Expression then Result := Node else Result := TAst.Unquote(Node.Identity, newExpr); end; function TAstTransformer.VisitUnquoteSplicing(const Node: IUnquoteSplicingNode): IAstNode; var newExpr: IAstNode; begin newExpr := Visit(Node.Expression); if (newExpr = Node.Expression) then Result := Node else Result := TAst.UnquoteSplicing(Node.Identity, newExpr.AsQuasiquote); end; function TAstTransformer.VisitIndexer(const Node: IIndexerNode): IAstNode; var newBase, newIndex: IAstNode; begin newBase := Visit(Node.Base); newIndex := Visit(Node.Index); if (newBase = Node.Base) and (newIndex = Node.Index) then Result := Node else Result := TAst.Indexer(Node.Identity, newBase, newIndex, Node.StaticType); end; function TAstTransformer.VisitMemberAccess(const Node: IMemberAccessNode): IAstNode; var newBase: IAstNode; newMember: IKeywordNode; begin newBase := Visit(Node.Base); newMember := Visit(Node.Member).AsKeyword; if (newBase = Node.Base) and (newMember = Node.Member) then Result := Node else Result := TAst.MemberAccess(Node.Identity, newBase, newMember, Node.StaticType); end; function TAstTransformer.VisitRecordLiteral(const Node: IRecordLiteralNode): IAstNode; var newFields: IRecordFieldList; begin newFields := Visit(Node.Fields).AsRecordFieldList; if newFields = Node.Fields then Result := Node else begin Result := TRecordLiteralNode.Create(newFields, Node.ScalarDefinition, Node.GenericDefinition, Node.StaticType, Node.Identity); end; end; function TAstTransformer.VisitAddSeriesItem(const Node: IAddSeriesItemNode): IAstNode; var newSeries: IIdentifierNode; newValue, newLookback: IAstNode; begin newSeries := Visit(Node.Series).AsIdentifier; newValue := Visit(Node.Value); newLookback := Visit(Node.Lookback); if (newSeries = Node.Series) and (newValue = Node.Value) and (newLookback = Node.Lookback) then Result := Node else Result := TAst.AddSeriesItem(Node.Identity, newSeries, newValue, newLookback, Node.StaticType); end; function TAstTransformer.VisitSeriesLength(const Node: ISeriesLengthNode): IAstNode; var newSeries: IIdentifierNode; begin newSeries := Visit(Node.Series).AsIdentifier; if newSeries = Node.Series then Result := Node else Result := TAst.SeriesLength(Node.Identity, newSeries, Node.StaticType); end; function TAstTransformer.VisitRecurNode(const Node: IRecurNode): IAstNode; var newArgs: IArgumentList; begin newArgs := Visit(Node.Arguments).AsArgumentList; if newArgs = Node.Arguments then Result := Node else Result := TRecurNode.Create(newArgs, Node.StaticType, Node.Identity); end; function TAstTransformer.VisitPipeInput(const Node: IPipeInputNode): IAstNode; var newSource: IIdentifierNode; newSels: IPipeSelectorList; begin newSource := Visit(Node.StreamSource).AsIdentifier; newSels := Visit(Node.Selectors).AsPipeSelectorList; if (newSource = Node.StreamSource) and (newSels = Node.Selectors) then Result := Node else Result := TAst.PipeInput(newSource, newSels, Node.Identity.Location); end; function TAstTransformer.VisitPipeSelectorList(const Node: IPipeSelectorList): IAstNode; var hasChanged: Boolean; newItems: TArray; item, newItem: IKeywordNode; i: Integer; begin hasChanged := False; SetLength(newItems, Node.Count); for i := 0 to Node.Count - 1 do begin item := Node[i]; newItem := Visit(item).AsKeyword; newItems[i] := newItem; if item <> newItem then hasChanged := True; end; if not hasChanged then Result := Node else // Manually reconstruct using helper Result := TAst.PipeSelectorList(newItems, Node.Identity.Location); end; function TAstTransformer.VisitPipeInputList(const Node: IPipeInputList): IAstNode; var hasChanged: Boolean; newItems: TArray; item, newItem: IPipeInputNode; i: Integer; begin hasChanged := False; SetLength(newItems, Node.Count); for i := 0 to Node.Count - 1 do begin item := Node[i]; newItem := Visit(item).AsPipeInput; // Explicit cast via new helper newItems[i] := newItem; if item <> newItem then hasChanged := True; end; if not hasChanged then Result := Node else // Using TList implementation directly implies we are creating the list structure // Since IPipeInputList is just INodeList, we reuse TPipeInputList Result := TPipeInputList.Create(newItems, Node.Identity); end; function TAstTransformer.VisitPipe(const Node: IPipeNode): IAstNode; var newInputs: IPipeInputList; newTrans: ILambdaExpressionNode; begin newInputs := Visit(Node.Inputs).AsPipeInputList; newTrans := Visit(Node.Transformation).AsLambdaExpression; if (newInputs = Node.Inputs) and (newTrans = Node.Transformation) then Result := Node else Result := TAst.Pipe(Node.Identity, newInputs, newTrans, Node.StaticType); end; end.