unit Myc.Ast.Compiler.TCO; interface uses System.SysUtils, System.Classes, System.Generics.Collections, Myc.Data.Value, Myc.Ast.Nodes, Myc.Ast.Visitor, Myc.Ast.Scope, Myc.Ast.Types, Myc.Ast, Myc.Ast.Binding.Nodes; type IAstTCO = interface(IAstVisitor) function Execute(const RootNode: IAstNode): IAstNode; end; // This transformer runs *after* the Lowerer (Phase 4). // Its sole responsibility is to identify tail calls (TCO) // and set the `IsTailCall` flag on TBoundFunctionCallNode. TAstTCO = class(TAstTransformer, IAstTCO) private FIsTailStack: TStack; FNextIsTail: Boolean; protected function Accept(const Node: IAstNode): TDataValue; override; // Overrides for TCO propagation function VisitBlockExpression(const Node: IBlockExpressionNode): TDataValue; override; function VisitIfExpression(const Node: IIfExpressionNode): TDataValue; override; function VisitTernaryExpression(const Node: ITernaryExpressionNode): TDataValue; override; function VisitLambdaExpression(const Node: ILambdaExpressionNode): TDataValue; override; function VisitRecurNode(const Node: IRecurNode): TDataValue; override; function VisitMacroExpansionNode(const Node: IMacroExpansionNode): TDataValue; override; // The core TCO logic function VisitFunctionCall(const Node: IFunctionCallNode): TDataValue; override; public constructor Create; destructor Destroy; override; function Execute(const RootNode: IAstNode): IAstNode; class function Optimize(const RootNode: IAstNode): IAstNode; static; end; implementation { TAstTCO } constructor TAstTCO.Create; begin inherited Create; FIsTailStack := TStack.Create; FNextIsTail := True; // The root expression is in tail position end; destructor TAstTCO.Destroy; begin FIsTailStack.Free; inherited; end; class function TAstTCO.Optimize(const RootNode: IAstNode): IAstNode; begin var optimizer := TAstTCO.Create as IAstTCO; Result := optimizer.Execute(RootNode); end; function TAstTCO.Execute(const RootNode: IAstNode): IAstNode; begin var transformedValue := Accept(RootNode); if transformedValue.IsVoid then Result := TAst.Block([]) else Result := transformedValue.AsIntf; end; function TAstTCO.Accept(const Node: IAstNode): TDataValue; begin if (not Assigned(Node)) or Done then exit; FIsTailStack.Push(FNextIsTail); try Result := inherited Accept(Node); finally FNextIsTail := FIsTailStack.Pop; end; end; function TAstTCO.VisitBlockExpression(const Node: IBlockExpressionNode): TDataValue; var i: Integer; isContextTail: Boolean; begin isContextTail := FIsTailStack.Peek; for i := 0 to High(Node.Expressions) do begin // Only the last expression in a block is in tail position FNextIsTail := isContextTail and (i = High(Node.Expressions)); Accept(Node.Expressions[i]); end; Result := TDataValue.FromIntf(Node); end; function TAstTCO.VisitIfExpression(const Node: IIfExpressionNode): TDataValue; var isContextTail: Boolean; begin isContextTail := FIsTailStack.Peek; // Condition is never in tail position FNextIsTail := False; Accept(Node.Condition); // Then/Else branches ARE in tail position if the IfExpr is FNextIsTail := isContextTail; Accept(Node.ThenBranch); if Assigned(Node.ElseBranch) then Accept(Node.ElseBranch); Result := TDataValue.FromIntf(Node); end; function TAstTCO.VisitTernaryExpression(const Node: ITernaryExpressionNode): TDataValue; var isContextTail: Boolean; begin isContextTail := FIsTailStack.Peek; // Condition is never in tail position FNextIsTail := False; Accept(Node.Condition); // Then/Else branches ARE in tail position if the TernaryExpr is FNextIsTail := isContextTail; Accept(Node.ThenBranch); Accept(Node.ElseBranch); Result := TDataValue.FromIntf(Node); end; function TAstTCO.VisitLambdaExpression(const Node: ILambdaExpressionNode): TDataValue; begin // Parameters are never in tail position FNextIsTail := False; AcceptNodes(Node.Parameters); // The body of a lambda is *always* a tail position (relative to the lambda) FNextIsTail := True; Accept(Node.Body); Result := TDataValue.FromIntf(Node); end; function TAstTCO.VisitRecurNode(const Node: IRecurNode): TDataValue; begin if not FIsTailStack.Peek then raise Exception.Create('''recur'' can only be used in a tail position.'); // Arguments are not in tail position FNextIsTail := False; AcceptNodes(Node.Arguments); Result := TDataValue.FromIntf(Node); end; function TAstTCO.VisitMacroExpansionNode(const Node: IMacroExpansionNode): TDataValue; begin // Propagate tail call status to the expanded body FNextIsTail := FIsTailStack.Peek; Accept(Node.ExpandedBody); // Also visit the original call nodes, though they are not in tail pos FNextIsTail := False; Accept(Node.Callee); AcceptNodes(Node.Arguments); Result := TDataValue.FromIntf(Node); end; function TAstTCO.VisitFunctionCall(const Node: IFunctionCallNode): TDataValue; var callee: IAstNode; args: TArray; isTailCall: Boolean; begin isTailCall := FIsTailStack.Peek; // Arguments are not in tail position FNextIsTail := False; callee := Accept(Node.Callee).AsIntf; args := AcceptNodes(Node.Arguments); // If TCO status changed, we MUST rebuild the node. var boundNode := (Node as TBoundFunctionCallNode); if (boundNode.IsTailCall <> isTailCall) or (callee <> Node.Callee) or (args <> Node.Arguments) then begin var newBoundCall := TBoundFunctionCallNode.Create(Node, callee, args, isTailCall); (newBoundCall as TAstNode).StaticType := (Node as TAstNode).StaticType; Result := TDataValue.FromIntf(newBoundCall); end else Result := TDataValue.FromIntf(Node); // No change end; end.