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
+76 -31
View File
@@ -116,79 +116,109 @@ end;
function TAstTCO.VisitBlockExpression(const Node: IBlockExpressionNode): IAstNode;
var
i: Integer;
isContextTail: Boolean;
N: TBlockExpressionNode;
newExprs: TArray<IAstNode>; // 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<IIdentifierNode>;
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<IAstNode>;
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<IAstNode>;
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.