AST Identities

This commit is contained in:
Michael Schimmel
2025-11-25 19:41:26 +01:00
parent 0b7a60e338
commit aff4cec7d5
9 changed files with 2334 additions and 2150 deletions
+55 -75
View File
@@ -8,7 +8,8 @@ uses
Myc.Data.Value,
Myc.Ast,
Myc.Ast.Types,
Myc.Ast.Nodes;
Myc.Ast.Nodes,
Myc.Ast.Identities; // Needed for identity casting
type
TAstVisitor<T> = class abstract(TInterfacedObject, IAstVisitor)
@@ -36,7 +37,7 @@ type
function IAstVisitor.VisitAddSeriesItem = DoVisitAddSeriesItem;
function IAstVisitor.VisitSeriesLength = DoVisitSeriesLength;
function IAstVisitor.VisitRecurNode = DoVisitRecurNode;
function IAstVisitor.VisitNop = DoVisitNop; // Added Nop
function IAstVisitor.VisitNop = DoVisitNop;
// Private bridge method implementations
function DoVisitConstant(const Node: IConstantNode): TDataValue;
@@ -61,7 +62,7 @@ type
function DoVisitAddSeriesItem(const Node: IAddSeriesItemNode): TDataValue;
function DoVisitSeriesLength(const Node: ISeriesLengthNode): TDataValue;
function DoVisitRecurNode(const Node: IRecurNode): TDataValue;
function DoVisitNop(const Node: INopNode): TDataValue; // Added Nop
function DoVisitNop(const Node: INopNode): TDataValue;
protected
// Visit a node.
@@ -89,7 +90,7 @@ type
function VisitAddSeriesItem(const Node: IAddSeriesItemNode): T; virtual; abstract;
function VisitSeriesLength(const Node: ISeriesLengthNode): T; virtual; abstract;
function VisitRecurNode(const Node: IRecurNode): T; virtual; abstract;
function VisitNop(const Node: INopNode): T; virtual; abstract; // Added Nop
function VisitNop(const Node: INopNode): T; virtual; abstract;
end;
TAstTransformer = class abstract(TAstVisitor<IAstNode>)
@@ -119,7 +120,7 @@ type
function VisitAddSeriesItem(const Node: IAddSeriesItemNode): IAstNode; override;
function VisitSeriesLength(const Node: ISeriesLengthNode): IAstNode; override;
function VisitRecurNode(const Node: IRecurNode): IAstNode; override;
function VisitNop(const Node: INopNode): IAstNode; override; // Added Nop
function VisitNop(const Node: INopNode): IAstNode; override;
end;
TAstVisitor = class abstract(TInterfacedObject, IAstVisitor)
@@ -147,7 +148,7 @@ type
function IAstVisitor.VisitAddSeriesItem = DoVisitAddSeriesItem;
function IAstVisitor.VisitSeriesLength = DoVisitSeriesLength;
function IAstVisitor.VisitRecurNode = DoVisitRecurNode;
function IAstVisitor.VisitNop = DoVisitNop; // Added Nop
function IAstVisitor.VisitNop = DoVisitNop;
// Private bridge method implementations
function DoVisitConstant(const Node: IConstantNode): TDataValue;
@@ -172,7 +173,7 @@ type
function DoVisitAddSeriesItem(const Node: IAddSeriesItemNode): TDataValue;
function DoVisitSeriesLength(const Node: ISeriesLengthNode): TDataValue;
function DoVisitRecurNode(const Node: IRecurNode): TDataValue;
function DoVisitNop(const Node: INopNode): TDataValue; // Added Nop
function DoVisitNop(const Node: INopNode): TDataValue;
protected
// Virtual procedures for descendants (Interpreters/Side-effects) to override
@@ -198,7 +199,7 @@ type
procedure VisitAddSeriesItem(const Node: IAddSeriesItemNode); virtual; abstract;
procedure VisitSeriesLength(const Node: ISeriesLengthNode); virtual; abstract;
procedure VisitRecurNode(const Node: IRecurNode); virtual; abstract;
procedure VisitNop(const Node: INopNode); virtual; abstract; // Added Nop
procedure VisitNop(const Node: INopNode); virtual; abstract;
end;
implementation
@@ -327,7 +328,6 @@ end;
function TAstVisitor<T>.DoVisitNop(const Node: INopNode): TDataValue;
begin
// Added Nop implementation
Result := TDataValue.FromGeneric<T>(VisitNop(Node));
end;
@@ -337,7 +337,6 @@ var
hasChanged: Boolean;
newNode: IIdentifierNode;
begin
// Implement Copy-on-Write for parameter arrays
hasChanged := False;
SetLength(Result, Length(Nodes));
@@ -350,7 +349,7 @@ begin
end;
if not hasChanged then
Result := Nodes; // Return original array if no changes
Result := Nodes;
end;
function TAstTransformer.AcceptNodes(
@@ -359,12 +358,10 @@ function TAstTransformer.AcceptNodes(
): TArray<IAstNode>;
var
i: Integer;
newNode: IAstNode; // Changed from TDataValue
newList: TList<IAstNode>; // Used if nodes are removed (e.g. defmacro)
newNode: IAstNode;
newList: TList<IAstNode>;
hasChanged: Boolean;
begin
// 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;
@@ -373,25 +370,24 @@ begin
if Assigned(AcceptProc) then
newNode := AcceptProc(i, Nodes[i])
else
newNode := Accept(Nodes[i]); // Calls new Accept, returns IAstNode (or nil)
newNode := Accept(Nodes[i]);
if not Assigned(newNode) then // Node was removed
if not Assigned(newNode) then
begin
hasChanged := True;
if newList = nil then // First change
if newList = nil then
begin
newList := TList<IAstNode>.Create;
for var j := 0 to i - 1 do
newList.Add(Nodes[j]);
end;
// else: just skip adding it
end
else
begin
if newNode <> Nodes[i] then // Node was replaced
if newNode <> Nodes[i] then
begin
hasChanged := True;
if newList = nil then // First change
if newList = nil then
begin
newList := TList<IAstNode>.Create;
for var j := 0 to i - 1 do
@@ -401,45 +397,43 @@ begin
end
else
begin
if newList <> nil then // No change, but we are already copying
if newList <> nil then
newList.Add(Nodes[i]);
end;
end;
end;
if not hasChanged then
Result := Nodes // Return original array
Result := Nodes
else if newList <> nil then
begin
Result := newList.ToArray;
newList.Free;
end
else
Result := []; // All nodes were removed
Result := [];
end;
// --- Base Virtual Implementations (IAstNode-based) ---
// --- Now fully implementing Copy-on-Write (CoW) ---
// --- Base Virtual Implementations ---
function TAstTransformer.VisitConstant(const Node: IConstantNode): IAstNode;
begin
Result := Node; // Leaf node, immutable
Result := Node;
end;
function TAstTransformer.VisitIdentifier(const Node: IIdentifierNode): IAstNode;
begin
Result := Node; // Leaf node, immutable (will be replaced by Binder)
Result := Node;
end;
function TAstTransformer.VisitKeyword(const Node: IKeywordNode): IAstNode;
begin
Result := Node; // Leaf node, immutable
Result := Node;
end;
function TAstTransformer.VisitNop(const Node: INopNode): IAstNode;
begin
// Added Nop implementation
Result := Node; // Leaf node, immutable
Result := Node;
end;
function TAstTransformer.VisitIfExpression(const Node: IIfExpressionNode): IAstNode;
@@ -448,13 +442,12 @@ var
begin
newCond := Accept(Node.Condition);
newThen := Accept(Node.ThenBranch);
newElse := Accept(Node.ElseBranch); // Accept handles nil
newElse := Accept(Node.ElseBranch);
if (newCond = Node.Condition) and (newThen = Node.ThenBranch) and (newElse = Node.ElseBranch) then
Result := Node
else
// Use TAst factory
Result := TAst.IfExpr(newCond, newThen, newElse, Node.StaticType);
Result := TAst.IfExpr(Node.Identity, newCond, newThen, newElse, Node.StaticType);
end;
function TAstTransformer.VisitTernaryExpression(const Node: ITernaryExpressionNode): IAstNode;
@@ -468,8 +461,7 @@ begin
if (newCond = Node.Condition) and (newThen = Node.ThenBranch) and (newElse = Node.ElseBranch) then
Result := Node
else
// Use TAst factory
Result := TAst.TernaryExpr(newCond, newThen, newElse, Node.StaticType);
Result := TAst.TernaryExpr(Node.Identity, newCond, newThen, newElse, Node.StaticType);
end;
function TAstTransformer.VisitLambdaExpression(const Node: ILambdaExpressionNode): IAstNode;
@@ -477,7 +469,6 @@ var
newParams: TArray<IIdentifierNode>;
newBody: IAstNode;
begin
// No longer cast to concrete class, use interface
newParams := AcceptParameters(Node.Parameters);
newBody := Accept(Node.Body);
@@ -485,9 +476,9 @@ begin
Result := Node
else
begin
// Use TAst factory and copy properties via interface getters
Result :=
TAst.LambdaExpr(
Node.Identity,
newParams,
newBody,
Node.Layout,
@@ -512,8 +503,8 @@ begin
Result := Node
else
begin
// Use TAst factory and copy properties via interface getters
Result := TAst.FunctionCall(newCallee, newArgs, Node.StaticType, Node.IsTailCall, Node.StaticTarget);
Result :=
TAst.FunctionCall(Node.Identity, newCallee, newArgs, Node.StaticType, Node.IsTailCall, Node.StaticTarget, Node.IsTargetPure);
end;
end;
@@ -521,13 +512,12 @@ function TAstTransformer.VisitMacroExpansionNode(const Node: IMacroExpansionNode
var
newBody: IAstNode;
begin
// Visit the body and check if it changed
newBody := Accept(Node.ExpandedBody);
if newBody = Node.ExpandedBody then
exit(Node);
// The body changed. Create a NEW wrapper node.
Result := TAst.MacroExpansionNode(Node.CallNode, newBody);
// Macro Expansion Nodes are structural, reuse identity
Result := TAst.MacroExpansionNode(Node.Identity, Node.CallNode, newBody);
end;
function TAstTransformer.VisitBlockExpression(const Node: IBlockExpressionNode): IAstNode;
@@ -539,8 +529,7 @@ begin
if newExprs = Node.Expressions then
Result := Node
else
// Use TAst factory
Result := TAst.Block(newExprs, Node.StaticType);
Result := TAst.Block(Node.Identity, newExprs, Node.StaticType);
end;
function TAstTransformer.VisitVariableDeclaration(const Node: IVariableDeclarationNode): IAstNode;
@@ -555,7 +544,7 @@ begin
Result := Node
else
begin
Result := TAst.VarDecl(newTarget, newInit, Node.StaticType, Node.IsBoxed);
Result := TAst.VarDecl(Node.Identity, newTarget, newInit, Node.StaticType, Node.IsBoxed);
end;
end;
@@ -570,17 +559,13 @@ begin
if (newTarget = Node.Target) and (newValue = Node.Value) then
Result := Node
else
Result := TAst.Assign(newTarget, newValue, Node.StaticType);
Result := TAst.Assign(Node.Identity, newTarget, newValue, Node.StaticType);
end;
function TAstTransformer.VisitMacroDefinition(const Node: IMacroDefinitionNode): IAstNode;
begin
// 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.
// Macro definitions are usually stripped/ignored in transformers,
// but we return them as-is to support partial pipelines.
Result := Node;
end;
@@ -588,37 +573,38 @@ function TAstTransformer.VisitQuasiquote(const Node: IQuasiquoteNode): IAstNode;
var
newExpr: IAstNode;
begin
// Rebuild instead of mutate (Copy-on-Write)
newExpr := Accept(Node.Expression);
if newExpr = Node.Expression then
Result := Node
else
Result := TAst.Quasiquote(newExpr);
Result := TAst.Quasiquote(Node.Identity, newExpr);
end;
function TAstTransformer.VisitUnquote(const Node: IUnquoteNode): IAstNode;
var
newExpr: IAstNode;
begin
// Rebuild instead of mutate (Copy-on-Write)
newExpr := Accept(Node.Expression);
if newExpr = Node.Expression then
Result := Node
else
Result := TAst.Unquote(newExpr);
Result := TAst.Unquote(Node.Identity, newExpr);
end;
function TAstTransformer.VisitUnquoteSplicing(const Node: IUnquoteSplicingNode): IAstNode;
var
newExpr: IAstNode;
begin
// Rebuild instead of mutate (Copy-on-Write)
// Note: Accept expects IAstNode, IQuasiquoteNode inherits from it.
newExpr := Accept(Node.Expression);
if (newExpr = Node.Expression) then
Result := Node
else
Result := TAst.UnquoteSplicing(newExpr.AsQuasiquote);
// We need to ensure the transformed expression is still a Quasiquote,
// or we have to assume the transformer knows what it's doing.
// For safety, we cast back. If transformation changed type, this will fail fast.
Result := TAst.UnquoteSplicing(Node.Identity, newExpr.AsQuasiquote);
end;
function TAstTransformer.VisitIndexer(const Node: IIndexerNode): IAstNode;
@@ -631,8 +617,7 @@ begin
if (newBase = Node.Base) and (newIndex = Node.Index) then
Result := Node
else
// Use TAst factory
Result := TAst.Indexer(newBase, newIndex, Node.StaticType);
Result := TAst.Indexer(Node.Identity, newBase, newIndex, Node.StaticType);
end;
function TAstTransformer.VisitMemberAccess(const Node: IMemberAccessNode): IAstNode;
@@ -641,13 +626,12 @@ var
newMember: IKeywordNode;
begin
newBase := Accept(Node.Base);
newMember := Accept(Node.Member).AsKeyword; // Keyword ist Blattknoten
newMember := Accept(Node.Member).AsKeyword;
if (newBase = Node.Base) and (newMember = Node.Member) then
Result := Node
else
// Use TAst factory
Result := TAst.MemberAccess(newBase, newMember, Node.StaticType);
Result := TAst.MemberAccess(Node.Identity, newBase, newMember, Node.StaticType);
end;
function TAstTransformer.VisitRecordLiteral(const Node: IRecordLiteralNode): IAstNode;
@@ -661,6 +645,7 @@ begin
for i := 0 to High(Node.Fields) do
begin
// Keys are typically static, but we visit them anyway
newFields[i].Key := Accept(Node.Fields[i].Key).AsKeyword;
newFields[i].Value := Accept(Node.Fields[i].Value);
if (newFields[i].Key <> Node.Fields[i].Key) or (newFields[i].Value <> Node.Fields[i].Value) then
@@ -671,15 +656,13 @@ begin
Result := Node
else
begin
// Rebuild the node, preserving its specific type and definitions
// Use TAst factory
Result := TAst.RecordLiteral(newFields, Node.ScalarDefinition, Node.GenericDefinition, Node.StaticType);
Result := TAst.RecordLiteral(Node.Identity, newFields, Node.ScalarDefinition, Node.GenericDefinition, Node.StaticType);
end;
end;
function TAstTransformer.VisitCreateSeries(const Node: ICreateSeriesNode): IAstNode;
begin
Result := Node; // Leaf node, immutable
Result := Node;
end;
function TAstTransformer.VisitAddSeriesItem(const Node: IAddSeriesItemNode): IAstNode;
@@ -689,13 +672,12 @@ var
begin
newSeries := Accept(Node.Series).AsIdentifier;
newValue := Accept(Node.Value);
newLookback := Accept(Node.Lookback); // Accept handles nil
newLookback := Accept(Node.Lookback);
if (newSeries = Node.Series) and (newValue = Node.Value) and (newLookback = Node.Lookback) then
Result := Node
else
// Use TAst factory
Result := TAst.AddSeriesItem(newSeries, newValue, newLookback, Node.StaticType);
Result := TAst.AddSeriesItem(Node.Identity, newSeries, newValue, newLookback, Node.StaticType);
end;
function TAstTransformer.VisitSeriesLength(const Node: ISeriesLengthNode): IAstNode;
@@ -707,8 +689,7 @@ begin
if newSeries = Node.Series then
Result := Node
else
// Use TAst factory
Result := TAst.SeriesLength(newSeries, Node.StaticType);
Result := TAst.SeriesLength(Node.Identity, newSeries, Node.StaticType);
end;
function TAstTransformer.VisitRecurNode(const Node: IRecurNode): IAstNode;
@@ -720,8 +701,7 @@ begin
if newArgs = Node.Arguments then
Result := Node
else
// Use TAst factory
Result := TAst.Recur(newArgs, Node.StaticType);
Result := TAst.Recur(Node.Identity, newArgs, Node.StaticType);
end;
{ TAstVisitor }