Files
MycLib/Src/AST/Myc.Ast.Refactoring.Remove.pas
T
2025-12-07 12:06:29 +01:00

450 lines
15 KiB
ObjectPascal

unit Myc.Ast.Refactoring.Remove;
interface
uses
System.SysUtils,
System.Classes,
System.Generics.Collections,
Myc.Ast,
Myc.Ast.Nodes,
Myc.Ast.Visitor,
Myc.Ast.Types;
type
// Removes a specific node from the AST.
// - If the node is part of a list, it is removed from the list.
// - If the node is a mandatory child of a structure (e.g. If-Condition),
// it is replaced by a Nop node to maintain tree integrity.
TAstNodeRemover = class(TAstTransformer)
private
FTarget: IAstNode;
FRemoved: Boolean;
// Generic helper to filter nil items from lists during transformation
function FilterList<T: IAstNode>(const Source: INodeList<T>): TArray<T>;
// Helper to ensure a mandatory slot is never nil (replaces with Nop)
function EnsureNode(const Node: IAstNode; const ContextNode: IAstNode): IAstNode;
protected
function Accept(const Node: IAstNode): IAstNode; override;
// --- List Containers (Filter Logic) ---
function VisitBlockExpression(const Node: IBlockExpressionNode): IAstNode; override;
function VisitExpressionList(const Node: IExpressionList): IAstNode; override;
function VisitArgumentList(const Node: IArgumentList): IAstNode; override;
function VisitParameterList(const Node: IParameterList): IAstNode; override;
function VisitRecordFieldList(const Node: IRecordFieldList): IAstNode; override;
// --- Structural Nodes (Replace Logic) ---
function VisitIfExpression(const Node: IIfExpressionNode): IAstNode; override;
function VisitCondExpression(const Node: ICondExpressionNode): IAstNode; override; // Replaces Ternary
function VisitAssignment(const Node: IAssignmentNode): IAstNode; override;
function VisitVariableDeclaration(const Node: IVariableDeclarationNode): IAstNode; override;
function VisitLambdaExpression(const Node: ILambdaExpressionNode): IAstNode; override;
function VisitFunctionCall(const Node: IFunctionCallNode): IAstNode; override;
function VisitIndexer(const Node: IIndexerNode): IAstNode; override;
function VisitMemberAccess(const Node: IMemberAccessNode): IAstNode; override;
function VisitAddSeriesItem(const Node: IAddSeriesItemNode): IAstNode; override;
public
constructor Create(const ATarget: IAstNode);
// Tries to remove TargetNode from RootNode.
// Returns True if the node was found and removed/replaced.
// If successful, NewRootNode contains the modified AST.
// If not found, NewRootNode returns the original RootNode.
class function TryRemove(const RootNode, TargetNode: IAstNode; out NewRootNode: IAstNode): Boolean; static;
end;
implementation
{ TAstNodeRemover }
constructor TAstNodeRemover.Create(const ATarget: IAstNode);
begin
inherited Create;
FTarget := ATarget;
FRemoved := False;
end;
class function TAstNodeRemover.TryRemove(const RootNode, TargetNode: IAstNode; out NewRootNode: IAstNode): Boolean;
var
Remover: TAstNodeRemover;
begin
// Edge case: Removing the root itself
if (RootNode = TargetNode) then
begin
// We replace the root with a Nop, as we cannot return 'nil' for a valid AST root expectation usually
NewRootNode := TAst.Nop(RootNode.Identity);
Result := True;
Exit;
end;
Remover := TAstNodeRemover.Create(TargetNode);
try
NewRootNode := Remover.Accept(RootNode);
Result := Remover.FRemoved;
if not Result then
begin
// If nothing was removed, return the original node to ensure reference stability
NewRootNode := RootNode;
end
else if (NewRootNode = nil) then
begin
// Safety fallback: if traversal somehow resulted in nil (should be caught by EnsureNode logic usually)
NewRootNode := TAst.Nop(RootNode.Identity);
end;
finally
Remover.Free;
end;
end;
function TAstNodeRemover.Accept(const Node: IAstNode): IAstNode;
begin
// If we matched the target, flag it as removed and return nil.
// The parent visitor is responsible for handling the nil result (Filter or Replace).
if Node = FTarget then
begin
FRemoved := True;
Exit(nil);
end;
Result := inherited Accept(Node);
end;
function TAstNodeRemover.EnsureNode(const Node: IAstNode; const ContextNode: IAstNode): IAstNode;
begin
if Assigned(Node) then
Result := Node
else
// Mandatory child was deleted -> Replace with Nop using parent's identity location
Result := TAst.Nop(ContextNode.Identity);
end;
function TAstNodeRemover.FilterList<T>(const Source: INodeList<T>): TArray<T>;
var
i: Integer;
item: T;
transformed: IAstNode;
list: TList<T>;
begin
list := TList<T>.Create;
try
for i := 0 to Source.Count - 1 do
begin
item := Source[i];
transformed := Accept(item);
// If transformed is not nil, keep it.
// If it is nil, it was the target, so we skip (delete) it.
if Assigned(transformed) then
list.Add(T(transformed));
end;
Result := list.ToArray;
finally
list.Free;
end;
end;
// -----------------------------------------------------------------------------
// List Visitors (Filtering)
// -----------------------------------------------------------------------------
function TAstNodeRemover.VisitBlockExpression(const Node: IBlockExpressionNode): IAstNode;
var
newExprs: TArray<IAstNode>;
newList: IExpressionList;
begin
newExprs := FilterList<IAstNode>(Node.Expressions);
// If counts differ, something was removed
if Length(newExprs) = Node.Expressions.Count then
begin
// If not marked as removed, we assume no change in this branch
if not FRemoved then
Exit(Node);
// Deep check for replacement
var changed := False;
for var i := 0 to High(newExprs) do
if newExprs[i] <> Node.Expressions[i] then
changed := True;
if not changed then
Exit(Node);
end;
newList := TExpressionList.Create(newExprs, Node.Expressions.Identity);
Result := TAst.Block(Node.Identity, newList, Node.StaticType);
end;
function TAstNodeRemover.VisitExpressionList(const Node: IExpressionList): IAstNode;
var
newExprs: TArray<IAstNode>;
begin
newExprs := FilterList<IAstNode>(Node);
if Length(newExprs) = Node.Count then
begin
var changed := False;
for var i := 0 to High(newExprs) do
if newExprs[i] <> Node[i] then
changed := True;
if not changed then
Exit(Node);
end;
Result := TExpressionList.Create(newExprs, Node.Identity);
end;
function TAstNodeRemover.VisitArgumentList(const Node: IArgumentList): IAstNode;
var
newArgs: TArray<IAstNode>;
begin
newArgs := FilterList<IAstNode>(Node);
if Length(newArgs) = Node.Count then
begin
var changed := False;
for var i := 0 to High(newArgs) do
if newArgs[i] <> Node[i] then
changed := True;
if not changed then
Exit(Node);
end;
Result := TArgumentList.Create(newArgs, Node.Identity);
end;
function TAstNodeRemover.VisitParameterList(const Node: IParameterList): IAstNode;
var
newParams: TArray<IIdentifierNode>;
begin
newParams := FilterList<IIdentifierNode>(Node);
if Length(newParams) = Node.Count then
begin
var changed := False;
for var i := 0 to High(newParams) do
if newParams[i] <> Node[i] then
changed := True;
if not changed then
Exit(Node);
end;
Result := TParameterList.Create(newParams, Node.Identity);
end;
function TAstNodeRemover.VisitRecordFieldList(const Node: IRecordFieldList): IAstNode;
var
newFields: TArray<IRecordFieldNode>;
begin
newFields := FilterList<IRecordFieldNode>(Node);
if Length(newFields) = Node.Count then
begin
var changed := False;
for var i := 0 to High(newFields) do
if newFields[i] <> Node[i] then
changed := True;
if not changed then
Exit(Node);
end;
Result := TRecordFieldList.Create(newFields, Node.Identity);
end;
// -----------------------------------------------------------------------------
// Structural Visitors (Replacement with Nop/Void)
// -----------------------------------------------------------------------------
function TAstNodeRemover.VisitIfExpression(const Node: IIfExpressionNode): IAstNode;
var
newCond, newThen, newElse: IAstNode;
begin
newCond := Accept(Node.Condition);
newThen := Accept(Node.ThenBranch);
newElse := Accept(Node.ElseBranch);
// If condition or Then-branch is deleted, replace with Nop
newCond := EnsureNode(newCond, Node);
newThen := EnsureNode(newThen, Node);
// Else branch is optional
if (newCond = Node.Condition) and (newThen = Node.ThenBranch) and (newElse = Node.ElseBranch) then
Exit(Node);
Result := TAst.IfExpr(Node.Identity, newCond, newThen, newElse, Node.StaticType);
end;
function TAstNodeRemover.VisitCondExpression(const Node: ICondExpressionNode): IAstNode;
var
i: Integer;
newPairs: TArray<TCondPair>;
newElse: IAstNode;
newCond, newBranch: IAstNode;
hasChanged: Boolean;
begin
SetLength(newPairs, Length(Node.Pairs));
hasChanged := False;
for i := 0 to High(Node.Pairs) do
begin
// Ensure mandatory children exist (replace with Nop if removed)
newCond := EnsureNode(Accept(Node.Pairs[i].Condition), Node);
newBranch := EnsureNode(Accept(Node.Pairs[i].Branch), Node);
newPairs[i] := TCondPair.Create(newCond, newBranch);
if (newCond <> Node.Pairs[i].Condition) or (newBranch <> Node.Pairs[i].Branch) then
hasChanged := True;
end;
// Else branch is effectively mandatory in the structure, even if it's Nop/Void
newElse := EnsureNode(Accept(Node.ElseBranch), Node);
if newElse <> Node.ElseBranch then
hasChanged := True;
if not hasChanged then
Exit(Node);
Result := TAst.CondExpr(Node.Identity, newPairs, newElse, Node.StaticType);
end;
function TAstNodeRemover.VisitAssignment(const Node: IAssignmentNode): IAstNode;
var
newTarget, newValue: IAstNode;
begin
newTarget := EnsureNode(Accept(Node.Target), Node);
newValue := EnsureNode(Accept(Node.Value), Node);
if (newTarget = Node.Target) and (newValue = Node.Value) then
Exit(Node);
Result := TAst.Assign(Node.Identity, newTarget, newValue, Node.StaticType);
end;
function TAstNodeRemover.VisitVariableDeclaration(const Node: IVariableDeclarationNode): IAstNode;
var
newTarget, newInit: IAstNode;
begin
newTarget := EnsureNode(Accept(Node.Target), Node);
newInit := Accept(Node.Initializer); // Initializer is optional
if (newTarget = Node.Target) and (newInit = Node.Initializer) then
Exit(Node);
Result := TAst.VarDecl(Node.Identity, newTarget, newInit, Node.StaticType, Node.IsBoxed);
end;
function TAstNodeRemover.VisitLambdaExpression(const Node: ILambdaExpressionNode): IAstNode;
var
newParams: IAstNode;
newBody: IAstNode;
begin
newParams := Accept(Node.Parameters);
// If the parameter list itself was the target, we replace it with an empty list
if newParams = nil then
newParams := TParameterList.Create([], Node.Parameters.Identity);
newBody := EnsureNode(Accept(Node.Body), Node);
if (newParams = Node.Parameters) and (newBody = Node.Body) then
Exit(Node);
Result :=
TAst.LambdaExpr(
Node.Identity,
newParams.AsParameterList,
newBody,
Node.Layout,
Node.Descriptor,
Node.Upvalues,
Node.HasNestedLambdas,
Node.IsPure,
Node.StaticType
);
end;
function TAstNodeRemover.VisitFunctionCall(const Node: IFunctionCallNode): IAstNode;
var
newCallee: IAstNode;
newArgs: IAstNode;
begin
newCallee := EnsureNode(Accept(Node.Callee), Node);
newArgs := Accept(Node.Arguments);
if newArgs = nil then
newArgs := TArgumentList.Create([], Node.Arguments.Identity);
if (newCallee = Node.Callee) and (newArgs = Node.Arguments) then
Exit(Node);
Result :=
TAst.FunctionCall(
Node.Identity,
newCallee,
newArgs.AsArgumentList,
Node.StaticType,
Node.IsTailCall,
Node.StaticTarget,
Node.IsTargetPure
);
end;
function TAstNodeRemover.VisitIndexer(const Node: IIndexerNode): IAstNode;
var
newBase, newIndex: IAstNode;
begin
newBase := EnsureNode(Accept(Node.Base), Node);
newIndex := EnsureNode(Accept(Node.Index), Node);
if (newBase = Node.Base) and (newIndex = Node.Index) then
Exit(Node);
Result := TAst.Indexer(Node.Identity, newBase, newIndex, Node.StaticType);
end;
function TAstNodeRemover.VisitMemberAccess(const Node: IMemberAccessNode): IAstNode;
var
newBase: IAstNode;
newMember: IAstNode;
begin
newBase := EnsureNode(Accept(Node.Base), Node);
newMember := Accept(Node.Member);
if newMember = nil then
begin
// If member name is deleted, the access is invalid. Replace whole node with Nop.
Exit(TAst.Nop(Node.Identity));
end;
if (newBase = Node.Base) and (newMember = Node.Member) then
Exit(Node);
Result := TAst.MemberAccess(Node.Identity, newBase, newMember.AsKeyword, Node.StaticType);
end;
function TAstNodeRemover.VisitAddSeriesItem(const Node: IAddSeriesItemNode): IAstNode;
var
newSeries: IAstNode;
newValue: IAstNode;
newLookback: IAstNode;
begin
newSeries := Accept(Node.Series);
if newSeries = nil then
Exit(TAst.Nop(Node.Identity)); // Cannot exist without series identifier
newValue := EnsureNode(Accept(Node.Value), Node);
newLookback := Accept(Node.Lookback); // Optional
if (newSeries = Node.Series) and (newValue = Node.Value) and (newLookback = Node.Lookback) then
Exit(Node);
Result := TAst.AddSeriesItem(Node.Identity, newSeries.AsIdentifier, newValue, newLookback, Node.StaticType);
end;
end.