Imutability for macro nodes

This commit is contained in:
Michael Schimmel
2025-11-05 12:19:34 +01:00
parent 60358365cd
commit 9bd2d6f7ab
10 changed files with 385 additions and 151 deletions
+241 -77
View File
@@ -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<IAstNode>)
protected
function AcceptParameters(const Nodes: TArray<IIdentifierNode>): TArray<IIdentifierNode>;
function AcceptNodes(const Nodes: TArray<IAstNode>): TArray<IAstNode>;
function AcceptNodes(const Nodes: TArray<IAstNode>; const AcceptProc: TFunc<Integer, IAstNode, IAstNode> = nil): TArray<IAstNode>;
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<IIdentifierNode>): TArray<IIdentifierNode>;
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<IAstNode>): TArray<IAstNode>;
function TAstTransformer.AcceptNodes(
const Nodes: TArray<IAstNode>;
const AcceptProc: TFunc<Integer, IAstNode, IAstNode> = nil
): TArray<IAstNode>;
var
i: Integer;
newNode: IAstNode; // Changed from TDataValue
newList: TList<IAstNode>; // 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<IAstNode>.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<IAstNode>.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<IIdentifierNode>;
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<IAstNode>;
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<IAstNode>;
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<TRecordFieldLiteral>;
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<IAstNode>;
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 }