diff --git a/ASTPlayground/Myc.Fmx.AstEditor.Node.pas b/ASTPlayground/Myc.Fmx.AstEditor.Node.pas index 428fb25..6ef3d6b 100644 --- a/ASTPlayground/Myc.Fmx.AstEditor.Node.pas +++ b/ASTPlayground/Myc.Fmx.AstEditor.Node.pas @@ -1809,7 +1809,7 @@ end; function TMacroExpansionNodeHandler.ReconstructAst(OwnerNode: TAuraNode): IAstNode; begin - Result := TAst.MacroExpansionNode(FNode, FExpandedBodyNode.CreateAst); + Result := TAst.MacroExpansionNode(FNode.CallNode, FExpandedBodyNode.CreateAst); end; { TRecurNodeHandler } diff --git a/ASTPlayground/Myc.Fmx.AstEditor.Text.pas b/ASTPlayground/Myc.Fmx.AstEditor.Text.pas index f5db6f9..9bf1514 100644 --- a/ASTPlayground/Myc.Fmx.AstEditor.Text.pas +++ b/ASTPlayground/Myc.Fmx.AstEditor.Text.pas @@ -117,7 +117,7 @@ begin // To represent a macro expansion as a single line of text, the most // informative representation is the original call itself. We can simply // delegate to the VisitFunctionCall implementation to format it. - Result := VisitFunctionCall(Node); + Result := VisitFunctionCall(Node.CallNode); end; function TAstToTextVisitor.VisitRecurNode(const Node: IRecurNode): string; diff --git a/Src/AST/Myc.Ast.Compiler.TCO.pas b/Src/AST/Myc.Ast.Compiler.TCO.pas index 80b249b..8cffe27 100644 --- a/Src/AST/Myc.Ast.Compiler.TCO.pas +++ b/Src/AST/Myc.Ast.Compiler.TCO.pas @@ -116,79 +116,109 @@ end; function TAstTCO.VisitBlockExpression(const Node: IBlockExpressionNode): IAstNode; var - i: Integer; isContextTail: Boolean; - N: TBlockExpressionNode; + newExprs: TArray; // For CoW begin - N := (Node as TBlockExpressionNode); isContextTail := FIsTailStack.Peek; - // We must manually iterate here to set FNextIsTail for each child - for i := 0 to High(N.Expressions) do - begin - // Only the last expression in a block is in tail position - FNextIsTail := isContextTail and (i = High(N.Expressions)); - N.Expressions[i] := Accept(N.Expressions[i]); - end; - Result := N; + // Call the base implementation which handles CoW + var nTail := High(Node.Expressions); + newExprs := + AcceptNodes( + Node.Expressions, + function(idx: Integer; Node: IAstNode): IAstNode + begin + FNextIsTail := isContextTail and (idx = nTail); + Result := Accept(Node); + end + ); + + if newExprs = Node.Expressions then + Result := Node + else + Result := TBlockExpressionNode.Create(newExprs, Node.StaticType); end; function TAstTCO.VisitIfExpression(const Node: IIfExpressionNode): IAstNode; var isContextTail: Boolean; N: TIfExpressionNode; + newCond, newThen, newElse: IAstNode; begin N := (Node as TIfExpressionNode); isContextTail := FIsTailStack.Peek; // Condition is never in tail position FNextIsTail := False; - N.Condition := Accept(N.Condition); + newCond := Accept(N.Condition); // Then/Else branches ARE in tail position if the IfExpr is FNextIsTail := isContextTail; - N.ThenBranch := Accept(N.ThenBranch); - N.ElseBranch := Accept(N.ElseBranch); + newThen := Accept(N.ThenBranch); + newElse := Accept(N.ElseBranch); - Result := N; + if (newCond = N.Condition) and (newThen = N.ThenBranch) and (newElse = N.ElseBranch) then + Result := Node + else + Result := TIfExpressionNode.Create(newCond, newThen, newElse, N.StaticType); end; function TAstTCO.VisitTernaryExpression(const Node: ITernaryExpressionNode): IAstNode; var isContextTail: Boolean; N: TTernaryExpressionNode; + newCond, newThen, newElse: IAstNode; begin N := (Node as TTernaryExpressionNode); isContextTail := FIsTailStack.Peek; // Condition is never in tail position FNextIsTail := False; - N.Condition := Accept(N.Condition); + newCond := Accept(N.Condition); // Then/Else branches ARE in tail position if the TernaryExpr is FNextIsTail := isContextTail; - N.ThenBranch := Accept(N.ThenBranch); - N.ElseBranch := Accept(N.ElseBranch); + newThen := Accept(N.ThenBranch); + newElse := Accept(N.ElseBranch); - Result := N; + if (newCond = N.Condition) and (newThen = N.ThenBranch) and (newElse = N.ElseBranch) then + Result := Node + else + Result := TTernaryExpressionNode.Create(newCond, newThen, newElse, N.StaticType); end; function TAstTCO.VisitLambdaExpression(const Node: ILambdaExpressionNode): IAstNode; var N: TLambdaExpressionNode; + newParams: TArray; + newBody: IAstNode; begin N := (Node as TLambdaExpressionNode); + // Parameters are not in tail position (handled by AcceptParameters) + FNextIsTail := False; + newParams := AcceptParameters(N.Parameters); + // The body of a lambda is *always* a tail position (relative to the lambda) FNextIsTail := True; - N.Body := Accept(N.Body); + newBody := Accept(N.Body); - Result := N; + if (newParams = N.Parameters) and (newBody = N.Body) then + Result := Node + else + begin + Result := TLambdaExpressionNode.Create(newParams, newBody, N.StaticType); + // Copy runtime properties + (Result as TLambdaExpressionNode).ScopeDescriptor := N.ScopeDescriptor; + (Result as TLambdaExpressionNode).Upvalues := N.Upvalues; + (Result as TLambdaExpressionNode).HasNestedLambdas := N.HasNestedLambdas; + end; end; function TAstTCO.VisitRecurNode(const Node: IRecurNode): IAstNode; var N: TRecurNode; + newArgs: TArray; begin if not FIsTailStack.Peek then raise Exception.Create('''recur'' can only be used in a tail position.'); @@ -197,41 +227,56 @@ begin // Arguments are not in tail position FNextIsTail := False; - N.Arguments := AcceptNodes(N.Arguments); + newArgs := AcceptNodes(N.Arguments); - Result := N; + if newArgs = N.Arguments then + Result := Node + else + Result := TRecurNode.Create(newArgs, N.StaticType); end; function TAstTCO.VisitMacroExpansionNode(const Node: IMacroExpansionNode): IAstNode; var N: TMacroExpansionNode; + newBody: IAstNode; begin N := (Node as TMacroExpansionNode); // Propagate tail call status to the expanded body FNextIsTail := FIsTailStack.Peek; - N.ExpandedBody := Accept(N.ExpandedBody); + newBody := Accept(N.ExpandedBody); - Result := N; + if newBody = N.ExpandedBody then + Result := Node + else + // Rebuild, preserving the original CallNode metadata + Result := TMacroExpansionNode.Create(N.CallNode, newBody, newBody.AsTypedNode.StaticType); end; function TAstTCO.VisitFunctionCall(const Node: IFunctionCallNode): IAstNode; var isTailCall: Boolean; N: TFunctionCallNode; + newCallee: IAstNode; + newArgs: TArray; begin isTailCall := FIsTailStack.Peek; N := (Node as TFunctionCallNode); // Arguments are not in tail position FNextIsTail := False; - N.Callee := Accept(N.Callee); - N.Arguments := AcceptNodes(N.Arguments); + newCallee := Accept(N.Callee); + newArgs := AcceptNodes(N.Arguments); - // Mutate the node with the TCO status - N.IsTailCall := isTailCall; - - Result := N; + // CoW check: Create a new node only if children changed OR IsTailCall needs update + if (newCallee = N.Callee) and (newArgs = N.Arguments) and (isTailCall = N.IsTailCall) then + Result := Node + else + begin + Result := TFunctionCallNode.Create(newCallee, newArgs, N.StaticType); + // Mutate the *new* node with the TCO status + (Result as TFunctionCallNode).IsTailCall := isTailCall; + end; end; end. diff --git a/Src/AST/Myc.Ast.Dumper.pas b/Src/AST/Myc.Ast.Dumper.pas index dabcba8..adf96d1 100644 --- a/Src/AST/Myc.Ast.Dumper.pas +++ b/Src/AST/Myc.Ast.Dumper.pas @@ -288,10 +288,10 @@ begin Log('Original Call:'); Indent; Log('Callee:'); - Node.Callee.Accept(Self); - LogFmt('Arguments (%d):', [Length(Node.Arguments)]); + Node.CallNode.Callee.Accept(Self); + LogFmt('Arguments (%d):', [Length(Node.CallNode.Arguments)]); Indent; - for arg in Node.Arguments do + for arg in Node.CallNode.Arguments do arg.Accept(Self); Unindent; Unindent; diff --git a/Src/AST/Myc.Ast.JSON.pas b/Src/AST/Myc.Ast.JSON.pas index 5a8d62c..58c0de3 100644 --- a/Src/AST/Myc.Ast.JSON.pas +++ b/Src/AST/Myc.Ast.JSON.pas @@ -303,11 +303,11 @@ var argsArray: TJSONArray; arg: IAstNode; begin - calleeObj := Accept(Node.Callee); + calleeObj := Accept(Node.CallNode.Callee); bodyObj := Accept(Node.ExpandedBody); argsArray := TJSONArray.Create; - for arg in Node.Arguments do + for arg in Node.CallNode.Arguments do argsArray.Add(Accept(arg)); Result := TJSONObject.Create; diff --git a/Src/AST/Myc.Ast.MacroExpander.pas b/Src/AST/Myc.Ast.MacroExpander.pas index 3bd90bb..1574333 100644 --- a/Src/AST/Myc.Ast.MacroExpander.pas +++ b/Src/AST/Myc.Ast.MacroExpander.pas @@ -57,6 +57,9 @@ type function VisitMacroDefinition(const Node: IMacroDefinitionNode): IAstNode; override; // Finds and expands macro calls function VisitFunctionCall(const Node: IFunctionCallNode): IAstNode; override; + function VisitQuasiquote(const Node: IQuasiquoteNode): IAstNode; override; + function VisitUnquote(const Node: IUnquoteNode): IAstNode; override; + function VisitUnquoteSplicing(const Node: IUnquoteSplicingNode): IAstNode; override; public constructor Create(const AInitialScope: IExecutionScope; const AEvaluatorFactory: TEvaluatorFactory); function Execute(const RootNode: IAstNode; out Descriptor: IScopeDescriptor): IAstNode; @@ -327,4 +330,19 @@ begin Result := Self.Accept(macroNode); // Calls the IAstNode helper, returns IAstNode end; +function TMacroExpander.VisitQuasiquote(const Node: IQuasiquoteNode): IAstNode; +begin + Result := Accept(Node.Expression); +end; + +function TMacroExpander.VisitUnquote(const Node: IUnquoteNode): IAstNode; +begin + Result := Accept(Node.Expression); +end; + +function TMacroExpander.VisitUnquoteSplicing(const Node: IUnquoteSplicingNode): IAstNode; +begin + Result := Accept(Node.Expression); +end; + end. diff --git a/Src/AST/Myc.Ast.Nodes.pas b/Src/AST/Myc.Ast.Nodes.pas index 8ecc390..6456d3b 100644 --- a/Src/AST/Myc.Ast.Nodes.pas +++ b/Src/AST/Myc.Ast.Nodes.pas @@ -179,7 +179,7 @@ type // This node is illegal during compilation. end; - IConstantNode = interface(IAstNode) + IConstantNode = interface(IAstTypedNode) // Represents a constant value in the AST (Scalar, Text, or Void). {$region 'private'} function GetValue: TDataValue; @@ -187,7 +187,7 @@ type property Value: TDataValue read GetValue; end; - IIdentifierNode = interface(IAstNode) + IIdentifierNode = interface(IAstTypedNode) {$region 'private'} function GetName: string; {$endregion} @@ -202,7 +202,7 @@ type end; // Represents a keyword literal in the AST (e.g., :foo) - IKeywordNode = interface(IAstNode) + IKeywordNode = interface(IAstTypedNode) {$region 'private'} function GetValue: IKeyword; {$endregion} @@ -210,7 +210,7 @@ type property Value: IKeyword read GetValue; end; - IBinaryExpressionNode = interface(IAstNode) + IBinaryExpressionNode = interface(IAstTypedNode) {$region 'private'} function GetLeft: IAstNode; function GetOperator: TScalar.TBinaryOp; @@ -221,7 +221,7 @@ type property Right: IAstNode read GetRight; end; - IUnaryExpressionNode = interface(IAstNode) + IUnaryExpressionNode = interface(IAstTypedNode) {$region 'private'} function GetOperator: TScalar.TUnaryOp; function GetRight: IAstNode; @@ -230,7 +230,7 @@ type property Right: IAstNode read GetRight; end; - IIfExpressionNode = interface(IAstNode) + IIfExpressionNode = interface(IAstTypedNode) {$region 'private'} function GetCondition: IAstNode; function GetThenBranch: IAstNode; @@ -241,7 +241,7 @@ type property ElseBranch: IAstNode read GetElseBranch; end; - ITernaryExpressionNode = interface(IAstNode) + ITernaryExpressionNode = interface(IAstTypedNode) {$region 'private'} function GetCondition: IAstNode; function GetThenBranch: IAstNode; @@ -252,7 +252,7 @@ type property ElseBranch: IAstNode read GetElseBranch; end; - ILambdaExpressionNode = interface(IAstNode) + ILambdaExpressionNode = interface(IAstTypedNode) {$region 'private'} function GetParameters: TArray; function GetBody: IAstNode; @@ -261,7 +261,7 @@ type property Body: IAstNode read GetBody; end; - IFunctionCallNode = interface(IAstNode) + IFunctionCallNode = interface(IAstTypedNode) {$region 'private'} function GetCallee: IAstNode; function GetArguments: TArray; @@ -271,30 +271,32 @@ type end; // A node representing a macro call. - IMacroExpansionNode = interface(IFunctionCallNode) + IMacroExpansionNode = interface(IAstTypedNode) {$region 'private'} + function GetCallNode: IFunctionCallNode; function GetExpandedBody: IAstNode; {$endregion} // The AST fragment that resulted from the macro expansion. + property CallNode: IFunctionCallNode read GetCallNode; property ExpandedBody: IAstNode read GetExpandedBody; end; // A node representing a tail-recursive call. - IRecurNode = interface(IAstNode) + IRecurNode = interface(IAstTypedNode) {$region 'private'} function GetArguments: TArray; {$endregion} property Arguments: TArray read GetArguments; end; - IBlockExpressionNode = interface(IAstNode) + IBlockExpressionNode = interface(IAstTypedNode) {$region 'private'} function GetExpressions: TArray; {$endregion} property Expressions: TArray read GetExpressions; end; - IVariableDeclarationNode = interface(IAstNode) + IVariableDeclarationNode = interface(IAstTypedNode) {$region 'private'} function GetIdentifier: IIdentifierNode; function GetInitializer: IAstNode; @@ -303,7 +305,7 @@ type property Initializer: IAstNode read GetInitializer; end; - IAssignmentNode = interface(IAstNode) + IAssignmentNode = interface(IAstTypedNode) {$region 'private'} function GetIdentifier: IIdentifierNode; function GetValue: IAstNode; @@ -347,7 +349,7 @@ type property Expression: IQuasiquoteNode read GetExpression; end; - IIndexerNode = interface(IAstNode) + IIndexerNode = interface(IAstTypedNode) {$region 'private'} function GetBase: IAstNode; function GetIndex: IAstNode; @@ -356,7 +358,7 @@ type property Index: IAstNode read GetIndex; end; - IMemberAccessNode = interface(IAstNode) + IMemberAccessNode = interface(IAstTypedNode) {$region 'private'} function GetBase: IAstNode; function GetMember: IKeywordNode; @@ -366,21 +368,21 @@ type end; // Represents a record literal, e.g., {:a 1 :b 2} - IRecordLiteralNode = interface(IAstNode) + IRecordLiteralNode = interface(IAstTypedNode) {$region 'private'} function GetFields: TArray; {$endregion} property Fields: TArray read GetFields; end; - ICreateSeriesNode = interface(IAstNode) + ICreateSeriesNode = interface(IAstTypedNode) {$region 'private'} function GetDefinition: String; {$endregion} property Definition: String read GetDefinition; end; - IAddSeriesItemNode = interface(IAstNode) + IAddSeriesItemNode = interface(IAstTypedNode) {$region 'private'} function GetSeries: IIdentifierNode; function GetValue: IAstNode; @@ -391,7 +393,7 @@ type property Lookback: IAstNode read GetLookback; end; - ISeriesLengthNode = interface(IAstNode) + ISeriesLengthNode = interface(IAstTypedNode) {$region 'private'} function GetSeries: IIdentifierNode; {$endregion} diff --git a/Src/AST/Myc.Ast.Script.pas b/Src/AST/Myc.Ast.Script.pas index c2b6a74..fe6b144 100644 --- a/Src/AST/Myc.Ast.Script.pas +++ b/Src/AST/Myc.Ast.Script.pas @@ -970,7 +970,7 @@ procedure TPrettyPrintVisitor.VisitMacroExpansionNode(const Node: IMacroExpansio begin // For pretty-printing, show the original macro call, not the expanded body. // Delegate to the regular VisitFunctionCall to print it as such. - VisitFunctionCall(Node); + VisitFunctionCall(Node.CallNode); end; procedure TPrettyPrintVisitor.VisitRecurNode(const Node: IRecurNode); diff --git a/Src/AST/Myc.Ast.Visitor.pas b/Src/AST/Myc.Ast.Visitor.pas index 2eb038b..eef5ddc 100644 --- a/Src/AST/Myc.Ast.Visitor.pas +++ b/Src/AST/Myc.Ast.Visitor.pas @@ -7,6 +7,7 @@ uses System.Generics.Collections, Myc.Data.Value, Myc.Ast, + Myc.Ast.Types, Myc.Ast.Nodes; type @@ -100,7 +101,7 @@ type TAstTransformer = class abstract(TAstVisitor) protected function AcceptParameters(const Nodes: TArray): TArray; - function AcceptNodes(const Nodes: TArray): TArray; + function AcceptNodes(const Nodes: TArray; const AcceptProc: TFunc = nil): TArray; function VisitConstant(const Node: IConstantNode): IAstNode; override; function VisitIdentifier(const Node: IIdentifierNode): IAstNode; override; @@ -357,27 +358,50 @@ end; function TAstTransformer.AcceptParameters(const Nodes: TArray): TArray; var i: Integer; + hasChanged: Boolean; + newNode: IIdentifierNode; begin + // Implement Copy-on-Write for parameter arrays + hasChanged := False; SetLength(Result, Length(Nodes)); + for i := 0 to High(Nodes) do - Result[i] := Accept(Nodes[i]).AsIdentifier; + begin + newNode := Accept(Nodes[i]).AsIdentifier; + Result[i] := newNode; + if newNode <> Nodes[i] then + hasChanged := True; + end; + + if not hasChanged then + Result := Nodes; // Return original array if no changes end; -function TAstTransformer.AcceptNodes(const Nodes: TArray): TArray; +function TAstTransformer.AcceptNodes( + const Nodes: TArray; + const AcceptProc: TFunc = nil +): TArray; var i: Integer; newNode: IAstNode; // Changed from TDataValue newList: TList; // Used if nodes are removed (e.g. defmacro) + hasChanged: Boolean; begin - Result := Nodes; // Assume no changes + // This implementation already supports CoW and node removal (nil) + // We just need to track if the array reference itself needs to change. + hasChanged := False; newList := nil; for i := 0 to High(Nodes) do begin - newNode := Accept(Nodes[i]); // Calls new Accept, returns IAstNode (or nil) + if Assigned(AcceptProc) then + newNode := AcceptProc(i, Nodes[i]) + else + newNode := Accept(Nodes[i]); // Calls new Accept, returns IAstNode (or nil) - if not Assigned(newNode) then // Node was removed (e.g. defmacro) + if not Assigned(newNode) then // Node was removed begin + hasChanged := True; if newList = nil then // First change begin newList := TList.Create; @@ -390,6 +414,7 @@ begin begin if newNode <> Nodes[i] then // Node was replaced begin + hasChanged := True; if newList = nil then // First change begin newList := TList.Create; @@ -406,193 +431,297 @@ begin end; end; - if newList <> nil then + if not hasChanged then + Result := Nodes // Return original array + else if newList <> nil then begin Result := newList.ToArray; newList.Free; - end; + end + else + Result := []; // All nodes were removed end; // --- Base Virtual Implementations (IAstNode-based) --- -// --- Implemented with traversal logic from old TAstTransformer --- +// --- Now fully implementing Copy-on-Write (CoW) --- function TAstTransformer.VisitConstant(const Node: IConstantNode): IAstNode; begin - Result := Node; // Leaf node + Result := Node; // Leaf node, immutable end; function TAstTransformer.VisitIdentifier(const Node: IIdentifierNode): IAstNode; begin - Result := Node; // Leaf node + Result := Node; // Leaf node, immutable (will be replaced by Binder) end; function TAstTransformer.VisitKeyword(const Node: IKeywordNode): IAstNode; begin - Result := Node; // Leaf node + Result := Node; // Leaf node, immutable end; function TAstTransformer.VisitNop(const Node: INopNode): IAstNode; begin // Added Nop implementation - Result := Node; // Leaf node + Result := Node; // Leaf node, immutable end; function TAstTransformer.VisitBinaryExpression(const Node: IBinaryExpressionNode): IAstNode; var + newLeft, newRight: IAstNode; N: TBinaryExpressionNode; begin N := (Node as TBinaryExpressionNode); - N.Left := Accept(N.Left); - N.Right := Accept(N.Right); - Result := Node; + newLeft := Accept(N.Left); + newRight := Accept(N.Right); + + if (newLeft = N.Left) and (newRight = N.Right) then + Result := Node + else + Result := TBinaryExpressionNode.Create(newLeft, N.Operator, newRight, N.StaticType); end; function TAstTransformer.VisitUnaryExpression(const Node: IUnaryExpressionNode): IAstNode; var + newRight: IAstNode; N: TUnaryExpressionNode; begin N := (Node as TUnaryExpressionNode); - N.Right := Accept(N.Right); - Result := Node; + newRight := Accept(N.Right); + + if newRight = N.Right then + Result := Node + else + Result := TUnaryExpressionNode.Create(N.Operator, newRight, N.StaticType); end; function TAstTransformer.VisitIfExpression(const Node: IIfExpressionNode): IAstNode; var + newCond, newThen, newElse: IAstNode; N: TIfExpressionNode; begin N := (Node as TIfExpressionNode); - N.Condition := Accept(N.Condition); - N.ThenBranch := Accept(N.ThenBranch); - N.ElseBranch := Accept(N.ElseBranch); // Accept handles nil - Result := Node; + newCond := Accept(N.Condition); + newThen := Accept(N.ThenBranch); + newElse := Accept(N.ElseBranch); // Accept handles nil + + if (newCond = N.Condition) and (newThen = N.ThenBranch) and (newElse = N.ElseBranch) then + Result := Node + else + Result := TIfExpressionNode.Create(newCond, newThen, newElse, N.StaticType); end; function TAstTransformer.VisitTernaryExpression(const Node: ITernaryExpressionNode): IAstNode; var + newCond, newThen, newElse: IAstNode; N: TTernaryExpressionNode; begin N := (Node as TTernaryExpressionNode); - N.Condition := Accept(N.Condition); - N.ThenBranch := Accept(N.ThenBranch); - N.ElseBranch := Accept(N.ElseBranch); - Result := Node; + newCond := Accept(N.Condition); + newThen := Accept(N.ThenBranch); + newElse := Accept(N.ElseBranch); + + if (newCond = N.Condition) and (newThen = N.ThenBranch) and (newElse = N.ElseBranch) then + Result := Node + else + Result := TTernaryExpressionNode.Create(newCond, newThen, newElse, N.StaticType); end; function TAstTransformer.VisitLambdaExpression(const Node: ILambdaExpressionNode): IAstNode; var + newParams: TArray; + newBody: IAstNode; N: TLambdaExpressionNode; begin N := (Node as TLambdaExpressionNode); - N.Parameters := AcceptParameters(N.Parameters); - N.Body := Accept(N.Body); - Result := Node; + newParams := AcceptParameters(N.Parameters); + newBody := Accept(N.Body); + + if (newParams = N.Parameters) and (newBody = N.Body) then + Result := Node + else + begin + Result := TLambdaExpressionNode.Create(newParams, newBody, N.StaticType); + // Copy runtime properties + (Result as TLambdaExpressionNode).ScopeDescriptor := N.ScopeDescriptor; + (Result as TLambdaExpressionNode).Upvalues := N.Upvalues; + (Result as TLambdaExpressionNode).HasNestedLambdas := N.HasNestedLambdas; + end; end; function TAstTransformer.VisitFunctionCall(const Node: IFunctionCallNode): IAstNode; var + newCallee: IAstNode; + newArgs: TArray; N: TFunctionCallNode; begin N := (Node as TFunctionCallNode); - N.Callee := Accept(N.Callee); - N.Arguments := AcceptNodes(N.Arguments); - Result := Node; + newCallee := Accept(N.Callee); + newArgs := AcceptNodes(N.Arguments); + + if (newCallee = N.Callee) and (newArgs = N.Arguments) then + Result := Node + else + begin + Result := TFunctionCallNode.Create(newCallee, newArgs, N.StaticType); + // Copy runtime properties + (Result as TFunctionCallNode).IsTailCall := N.IsTailCall; + end; end; function TAstTransformer.VisitMacroExpansionNode(const Node: IMacroExpansionNode): IAstNode; var + newBody: IAstNode; N: TMacroExpansionNode; + newType: IStaticType; begin N := (Node as TMacroExpansionNode); - // ONLY visit the expanded body, as this is the "real" AST node. - N.ExpandedBody := Accept(N.ExpandedBody); + // 1. Visit the body + newBody := Accept(N.ExpandedBody); - // Callee and Arguments are metadata for the visualizer and must NOT be visited - // by subsequent phases (Binder, TypeChecker, etc.). - - Result := Node; // Return the (mutated) wrapper + // 2. Check if the body changed (Copy-on-Write) + if newBody = N.ExpandedBody then + begin + // No change, return the original immutable node + Result := Node; + end + else + begin + // The body changed. Create a NEW wrapper node. + // The type of the wrapper is the type of its body. + newType := newBody.AsTypedNode.StaticType; + Result := TMacroExpansionNode.Create(N.CallNode, newBody, newType); + end; end; function TAstTransformer.VisitBlockExpression(const Node: IBlockExpressionNode): IAstNode; var + newExprs: TArray; N: TBlockExpressionNode; begin N := (Node as TBlockExpressionNode); - N.Expressions := AcceptNodes(N.Expressions); - Result := Node; + newExprs := AcceptNodes(N.Expressions); + + if newExprs = N.Expressions then + Result := Node + else + Result := TBlockExpressionNode.Create(newExprs, N.StaticType); end; function TAstTransformer.VisitVariableDeclaration(const Node: IVariableDeclarationNode): IAstNode; var + newIdent: IIdentifierNode; + newInit: IAstNode; N: TVariableDeclarationNode; begin N := (Node as TVariableDeclarationNode); - N.Identifier := Accept(N.Identifier).AsIdentifier; - N.Initializer := Accept(N.Initializer); // Accept handles nil - Result := Node; + newIdent := Accept(N.Identifier).AsIdentifier; + newInit := Accept(N.Initializer); // Accept handles nil + + if (newIdent = N.Identifier) and (newInit = N.Initializer) then + Result := Node + else + begin + Result := TVariableDeclarationNode.Create(newIdent, newInit, N.StaticType); + // Copy runtime properties + (Result as TVariableDeclarationNode).IsBoxed := N.IsBoxed; + end; end; function TAstTransformer.VisitAssignment(const Node: IAssignmentNode): IAstNode; var + newIdent: IIdentifierNode; + newValue: IAstNode; N: TAssignmentNode; begin N := (Node as TAssignmentNode); - N.Value := Accept(N.Value); - N.Identifier := Accept(N.Identifier).AsIdentifier; - Result := Node; + newValue := Accept(N.Value); + newIdent := Accept(N.Identifier).AsIdentifier; + + if (newValue = N.Value) and (newIdent = N.Identifier) then + Result := Node + else + Result := TAssignmentNode.Create(newIdent, newValue, N.StaticType); end; function TAstTransformer.VisitMacroDefinition(const Node: IMacroDefinitionNode): IAstNode; begin - // Do nothing, just pass the node through + // A macro definition is a compile-time construct. + // Transformers (Binder, TypeChecker, Lowerer) should not traverse its + // children (Name, Parameters, Body) as they are not part of the + // standard execution AST. + // Serializers (TAstDumper/TJsonAstConverter), which need to traverse, + // provide their own override. Result := Node; end; function TAstTransformer.VisitQuasiquote(const Node: IQuasiquoteNode): IAstNode; var - N: TQuasiquoteNode; + newExpr: IAstNode; begin - N := (Node as TQuasiquoteNode); - N.Expression := Accept(N.Expression); - Result := Node; + // Rebuild instead of mutate (Copy-on-Write) + newExpr := Accept(Node.Expression); + if newExpr = Node.Expression then + Result := Node + else + Result := TAst.Quasiquote(newExpr); end; function TAstTransformer.VisitUnquote(const Node: IUnquoteNode): IAstNode; var - N: TUnquoteNode; + newExpr: IAstNode; begin - N := (Node as TUnquoteNode); - N.Expression := Accept(N.Expression); - Result := Node; + // Rebuild instead of mutate (Copy-on-Write) + newExpr := Accept(Node.Expression); + if newExpr = Node.Expression then + Result := Node + else + Result := TAst.Unquote(newExpr); end; function TAstTransformer.VisitUnquoteSplicing(const Node: IUnquoteSplicingNode): IAstNode; var - N: TUnquoteSplicingNode; + newExpr: IAstNode; begin - N := (Node as TUnquoteSplicingNode); - N.Expression := Accept(N.Expression).AsQuasiquote; - Result := Node; + // Rebuild instead of mutate (Copy-on-Write) + newExpr := Accept(Node.Expression); + + if (newExpr = Node.Expression) then + Result := Node + else + Result := TAst.UnquoteSplicing(newExpr.AsQuasiquote); end; function TAstTransformer.VisitIndexer(const Node: IIndexerNode): IAstNode; var + newBase, newIndex: IAstNode; N: TIndexerNode; begin N := (Node as TIndexerNode); - N.Base := Accept(N.Base); - N.Index := Accept(N.Index); - Result := Node; + newBase := Accept(N.Base); + newIndex := Accept(N.Index); + + if (newBase = N.Base) and (newIndex = N.Index) then + Result := Node + else + Result := TIndexerNode.Create(newBase, newIndex, N.StaticType); end; function TAstTransformer.VisitMemberAccess(const Node: IMemberAccessNode): IAstNode; var + newBase: IAstNode; + newMember: IKeywordNode; N: TMemberAccessNode; begin N := (Node as TMemberAccessNode); - N.Base := Accept(N.Base); - N.Member := Accept(N.Member).AsKeyword; - Result := Node; + newBase := Accept(N.Base); + newMember := Accept(N.Member).AsKeyword; // Keyword ist Blattknoten + + if (newBase = N.Base) and (newMember = N.Member) then + Result := Node + else + Result := TMemberAccessNode.Create(newBase, newMember, N.StaticType); end; function TAstTransformer.VisitRecordLiteral(const Node: IRecordLiteralNode): IAstNode; @@ -600,51 +729,86 @@ var N: TRecordLiteralNode; i: Integer; newFields: TArray; + hasChanged: Boolean; begin N := (Node as TRecordLiteralNode); - // TRecordFieldLiteral ist ein Record, kein IAstNode, wir müssen manuell traversieren SetLength(newFields, Length(N.Fields)); + hasChanged := False; + for i := 0 to High(N.Fields) do begin newFields[i].Key := Accept(N.Fields[i].Key).AsKeyword; newFields[i].Value := Accept(N.Fields[i].Value); + if (newFields[i].Key <> N.Fields[i].Key) or (newFields[i].Value <> N.Fields[i].Value) then + hasChanged := True; + end; + + if not hasChanged then + Result := Node + else + begin + // Rebuild the node, preserving its specific type and definitions + if N is TGenericRecordLiteralNode then + begin + Result := TGenericRecordLiteralNode.Create(newFields, N.StaticType); + (Result as TGenericRecordLiteralNode).GenericDefinition := (N as TGenericRecordLiteralNode).GenericDefinition; + end + else + begin + Result := TRecordLiteralNode.Create(newFields, N.StaticType); + (Result as TRecordLiteralNode).Definition := N.Definition; + end; end; - N.Fields := newFields; - Result := Node; end; function TAstTransformer.VisitCreateSeries(const Node: ICreateSeriesNode): IAstNode; begin - Result := Node; // Leaf node + Result := Node; // Leaf node, immutable end; function TAstTransformer.VisitAddSeriesItem(const Node: IAddSeriesItemNode): IAstNode; var + newSeries: IIdentifierNode; + newValue, newLookback: IAstNode; N: TAddSeriesItemNode; begin N := (Node as TAddSeriesItemNode); - N.Series := Accept(N.Series).AsIdentifier; - N.Value := Accept(N.Value); - N.Lookback := Accept(N.Lookback); // Accept handles nil - Result := Node; + newSeries := Accept(N.Series).AsIdentifier; + newValue := Accept(N.Value); + newLookback := Accept(N.Lookback); // Accept handles nil + + if (newSeries = N.Series) and (newValue = N.Value) and (newLookback = N.Lookback) then + Result := Node + else + Result := TAddSeriesItemNode.Create(newSeries, newValue, newLookback, N.StaticType); end; function TAstTransformer.VisitSeriesLength(const Node: ISeriesLengthNode): IAstNode; var + newSeries: IIdentifierNode; N: TSeriesLengthNode; begin N := (Node as TSeriesLengthNode); - N.Series := Accept(N.Series).AsIdentifier; - Result := Node; + newSeries := Accept(N.Series).AsIdentifier; + + if newSeries = N.Series then + Result := Node + else + Result := TSeriesLengthNode.Create(newSeries, N.StaticType); end; function TAstTransformer.VisitRecurNode(const Node: IRecurNode): IAstNode; var + newArgs: TArray; N: TRecurNode; begin N := (Node as TRecurNode); - N.Arguments := AcceptNodes(N.Arguments); - Result := Node; + newArgs := AcceptNodes(N.Arguments); + + if newArgs = N.Arguments then + Result := Node + else + Result := TRecurNode.Create(newArgs, N.StaticType); end; { TAstVisitor } diff --git a/Src/AST/Myc.Ast.pas b/Src/AST/Myc.Ast.pas index bd3a3ba..5b86ad2 100644 --- a/Src/AST/Myc.Ast.pas +++ b/Src/AST/Myc.Ast.pas @@ -276,9 +276,9 @@ type constructor Create(const AName: IIdentifierNode; const AParameters: TArray; const ABody: IAstNode); function Accept(const Visitor: IAstVisitor): TDataValue; override; function AsMacroDefinition: IMacroDefinitionNode; override; - property Name: IIdentifierNode read GetName write FName; // Writeable - property Parameters: TArray read FParameters write FParameters; - property Body: IAstNode read FBody write FBody; // Writeable + property Name: IIdentifierNode read GetName; + property Parameters: TArray read FParameters; + property Body: IAstNode read FBody; end; TQuasiquoteNode = class(TAstNode, IQuasiquoteNode) @@ -290,7 +290,7 @@ type constructor Create(const AExpression: IAstNode); function Accept(const Visitor: IAstVisitor): TDataValue; override; function AsQuasiquote: IQuasiquoteNode; override; - property Expression: IAstNode read GetExpression write FExpression; // Writeable + property Expression: IAstNode read GetExpression; end; TUnquoteNode = class(TAstNode, IUnquoteNode) @@ -302,7 +302,7 @@ type constructor Create(const AExpression: IAstNode); function Accept(const Visitor: IAstVisitor): TDataValue; override; function AsUnquote: IUnquoteNode; override; - property Expression: IAstNode read GetExpression write FExpression; // Writeable + property Expression: IAstNode read GetExpression; end; TUnquoteSplicingNode = class(TAstNode, IUnquoteSplicingNode) @@ -314,7 +314,7 @@ type constructor Create(const AExpression: IQuasiquoteNode); function Accept(const Visitor: IAstVisitor): TDataValue; override; function AsUnquoteSplicing: IUnquoteSplicingNode; override; - property Expression: IQuasiquoteNode read GetExpression write FExpression; + property Expression: IQuasiquoteNode read GetExpression; end; TFunctionCallNode = class(TAstTypedNode, IFunctionCallNode) @@ -346,16 +346,19 @@ type property Arguments: TArray read FArguments write FArguments; // Writeable end; - TMacroExpansionNode = class(TFunctionCallNode, IMacroExpansionNode) + TMacroExpansionNode = class(TAstTypedNode, IMacroExpansionNode) private + FCallNode: IFunctionCallNode; FExpandedBody: IAstNode; + function GetCallNode: IFunctionCallNode; function GetExpandedBody: IAstNode; function GetKind: TAstNodeKind; override; public - constructor Create(const AOriginalCallNode: IFunctionCallNode; const AExpandedBody: IAstNode; const AStaticType: IStaticType); + constructor Create(const ACallNode: IFunctionCallNode; const AExpandedBody: IAstNode; const AStaticType: IStaticType); function Accept(const Visitor: IAstVisitor): TDataValue; override; function AsMacroExpansion: IMacroExpansionNode; override; - property ExpandedBody: IAstNode read FExpandedBody write FExpandedBody; // Writeable + property CallNode: IFunctionCallNode read GetCallNode; + property ExpandedBody: IAstNode read FExpandedBody; end; TBlockExpressionNode = class(TAstTypedNode, IBlockExpressionNode) @@ -1331,15 +1334,12 @@ end; { TMacroExpansionNode } -constructor TMacroExpansionNode.Create( - const AOriginalCallNode: IFunctionCallNode; - const AExpandedBody: IAstNode; - const AStaticType: IStaticType -); +constructor TMacroExpansionNode.Create(const ACallNode: IFunctionCallNode; const AExpandedBody: IAstNode; const AStaticType: IStaticType); begin // Copy properties from the original call node - inherited Create(AOriginalCallNode.Callee, AOriginalCallNode.Arguments, AStaticType); + inherited Create(AStaticType); FExpandedBody := AExpandedBody; + FCallNode := ACallNode; end; function TMacroExpansionNode.Accept(const Visitor: IAstVisitor): TDataValue; @@ -1352,6 +1352,11 @@ begin Result := Self; end; +function TMacroExpansionNode.GetCallNode: IFunctionCallNode; +begin + Result := FCallNode; +end; + function TMacroExpansionNode.GetExpandedBody: IAstNode; begin Result := FExpandedBody;