Imutability for macro nodes
This commit is contained in:
+241
-77
@@ -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 }
|
||||
|
||||
Reference in New Issue
Block a user