Making AST node inmmutable - WIP

This commit is contained in:
Michael Schimmel
2025-11-04 20:12:12 +01:00
parent 48ccb23060
commit d82a75aba6
6 changed files with 123 additions and 56 deletions
+48 -34
View File
@@ -284,13 +284,17 @@ begin
// Define parameters // Define parameters
for i := 0 to High(N.Parameters) do for i := 0 to High(N.Parameters) do
begin begin
var paramNode := N.Parameters[i]; var paramNode := N.Parameters[i]; // This is a TIdentifierNode
var slotIndex := FCurrentDescriptor.Define(paramNode.Name, TTypes.Unknown); var slotIndex := FCurrentDescriptor.Define(paramNode.Name, TTypes.Unknown);
adr := TResolvedAddress.Create(akLocalOrParent, 0, slotIndex); adr := TResolvedAddress.Create(akLocalOrParent, 0, slotIndex);
// Mutate the parameter node with its address // --- Replace Node ---
(paramNode as TIdentifierNode).Address := adr; // Create a new TBoundIdentifierNode (implementation is in Myc.Ast)
(paramNode as TAstNode).StaticType := TTypes.Unknown; var boundParamNode := TBoundIdentifierNode.Create(paramNode.Name, adr);
// Set its type
(boundParamNode as TAstNode).StaticType := TTypes.Unknown;
// Replace it in the parameter array
N.Parameters[i] := boundParamNode;
end; end;
// Visit the body *within the new scope* // Visit the body *within the new scope*
@@ -342,42 +346,50 @@ var
symbol: TResolvedSymbol; symbol: TResolvedSymbol;
adr: TResolvedAddress; adr: TResolvedAddress;
begin begin
symbol := FCurrentDescriptor.FindSymbol(Node.Name); // We only bind nodes that are the base parser type.
adr := symbol.Address; if Node.Kind = akIdentifier then
if adr.Kind = akLocalOrParent then
begin begin
if (adr.ScopeDepth > 0) and (FUpvalueStack.Count > 0) then symbol := FCurrentDescriptor.FindSymbol(Node.Name);
begin adr := symbol.Address;
// --- Handle Upvalue ---
var upvalueMap := FUpvalueStack.Peek;
// Adjust address to be relative to the captured scope
dec(adr.ScopeDepth);
var upvalueIndex: Integer; if adr.Kind = akUnresolved then
if not upvalueMap.TryGetValue(adr, upvalueIndex) then raise Exception.CreateFmt('Undefined identifier: "%s"', [Node.Name]);
if adr.Kind = akLocalOrParent then
begin
if (adr.ScopeDepth > 0) and (FUpvalueStack.Count > 0) then
begin begin
// This is a new upvalue for this lambda // --- Handle Upvalue ---
upvalueIndex := upvalueMap.Count; var upvalueMap := FUpvalueStack.Peek;
upvalueMap.Add(adr, upvalueIndex); // Adjust address to be relative to the captured scope
dec(adr.ScopeDepth);
var upvalueIndex: Integer;
if not upvalueMap.TryGetValue(adr, upvalueIndex) then
begin
// This is a new upvalue for this lambda
upvalueIndex := upvalueMap.Count;
upvalueMap.Add(adr, upvalueIndex);
end;
// Create final upvalue address
adr := TResolvedAddress.Create(akUpvalue, 0, upvalueIndex);
end; end;
// else: It's a local (ScopeDepth=0), adr is already correct.
// Mutate the node to point to the Upvalue slot
(Node as TIdentifierNode).Address := TResolvedAddress.Create(akUpvalue, 0, upvalueIndex);
end
else
begin
// --- Handle LocalOrParent ---
// Mutate the node to point to the Local/Parent slot
(Node as TIdentifierNode).Address := adr;
end; end;
// else: It was already an upvalue (e.g. nested lambda), adr is correct.
(Node as TAstNode).StaticType := symbol.StaticType; // Set type from scope // --- Replace Node ---
// Create the new TBoundIdentifierNode (implementation is in Myc.Ast)
Result := TBoundIdentifierNode.Create(Node.Name, adr);
// Copy static type from scope to the new node
(Result as TAstNode).StaticType := symbol.StaticType;
end end
else else
raise Exception.CreateFmt('Undefined identifier: "%s"', [Node.Name]); begin
// It's already an akBoundIdentifier (or other specialized type), just return it.
Result := Node; Result := Node;
end;
end; end;
function TAstBinder.VisitVariableDeclaration(const Node: IVariableDeclarationNode): IAstNode; function TAstBinder.VisitVariableDeclaration(const Node: IVariableDeclarationNode): IAstNode;
@@ -399,8 +411,10 @@ begin
slotIndex := FCurrentDescriptor.Define(N.Identifier.Name, TTypes.Unknown); slotIndex := FCurrentDescriptor.Define(N.Identifier.Name, TTypes.Unknown);
address := TResolvedAddress.Create(akLocalOrParent, 0, slotIndex); address := TResolvedAddress.Create(akLocalOrParent, 0, slotIndex);
// 3. Mutate the Identifier node (which is NOT visited by inherited call) // 3. --- Replace Node ---
(N.Identifier as TIdentifierNode).Address := address; // Replace the TIdentifierNode with a new TBoundIdentifierNode
N.Identifier := TBoundIdentifierNode.Create(N.Identifier.Name, address);
// Set static type (Unknown) on the *new* node
(N.Identifier as TAstNode).StaticType := TTypes.Unknown; // TypeChecker will set this (N.Identifier as TAstNode).StaticType := TTypes.Unknown; // TypeChecker will set this
// 4. Mutate this declaration node // 4. Mutate this declaration node
+7 -5
View File
@@ -143,13 +143,15 @@ end;
procedure TAstDumper.VisitIdentifier(const Node: IIdentifierNode); procedure TAstDumper.VisitIdentifier(const Node: IIdentifierNode);
var var
N: TIdentifierNode; adr: TResolvedAddress;
begin begin
N := (Node as TIdentifierNode); if Node.Kind = akBoundIdentifier then
if N.Address.Kind <> akUnresolved then adr := Node.AsBoundIdentifierNode.Address;
LogFmt('Identifier: %s -> %s', [N.Name, FormatAddress(N.Address)])
if adr.Kind <> akUnresolved then
LogFmt('Identifier: %s -> %s', [Node.Name, FormatAddress(adr)])
else else
LogFmt('Identifier: %s (unbound)', [N.Name]); LogFmt('Identifier: %s (unbound)', [Node.Name]);
end; end;
procedure TAstDumper.VisitKeyword(const Node: IKeywordNode); procedure TAstDumper.VisitKeyword(const Node: IKeywordNode);
+7 -7
View File
@@ -234,7 +234,7 @@ begin
for i := 0 to High(ArgValues) do for i := 0 to High(ArgValues) do
begin begin
// Parameters in a bound lambda must be bound identifiers. // Parameters in a bound lambda must be bound identifiers.
adr.SlotIndex := (params[i] as TIdentifierNode).Address.SlotIndex; // Changed adr.SlotIndex := params[i].AsBoundIdentifierNode.Address.SlotIndex; // Changed
lambdaScope[adr] := ArgValues[i]; lambdaScope[adr] := ArgValues[i];
end; end;
@@ -334,7 +334,7 @@ var
itemValue, lookbackValue, seriesVar: TDataValue; itemValue, lookbackValue, seriesVar: TDataValue;
lookback: Int64; lookback: Int64;
begin begin
seriesVar := FScope[(Node.Series as TIdentifierNode).Address]; // Changed seriesVar := FScope[Node.Series.AsBoundIdentifierNode.Address]; // Changed
itemValue := Node.Value.Accept(Self); itemValue := Node.Value.Accept(Self);
lookback := -1; lookback := -1;
@@ -368,7 +368,7 @@ begin
// Evaluate the new value. // Evaluate the new value.
Result := Node.Value.Accept(Self); Result := Node.Value.Accept(Self);
// Assign it. The scope's SetValues implementation now handles boxing correctly. // Assign it. The scope's SetValues implementation now handles boxing correctly.
FScope[(Node.Identifier as TIdentifierNode).Address] := Result; // Changed FScope[Node.Identifier.AsBoundIdentifierNode.Address] := Result; // Changed
end; end;
function TEvaluatorVisitor.VisitConstant(const Node: IConstantNode): TDataValue; function TEvaluatorVisitor.VisitConstant(const Node: IConstantNode): TDataValue;
@@ -405,7 +405,7 @@ end;
function TEvaluatorVisitor.VisitIdentifier(const Node: IIdentifierNode): TDataValue; function TEvaluatorVisitor.VisitIdentifier(const Node: IIdentifierNode): TDataValue;
begin begin
// The scope's GetValues implementation now handles unboxing automatically. // The scope's GetValues implementation now handles unboxing automatically.
Result := FScope[(Node as TIdentifierNode).Address]; // Changed Result := FScope[Node.AsBoundIdentifierNode.Address]; // Changed
end; end;
function TEvaluatorVisitor.VisitIndexer(const Node: IIndexerNode): TDataValue; function TEvaluatorVisitor.VisitIndexer(const Node: IIndexerNode): TDataValue;
@@ -548,8 +548,8 @@ begin
Result := TDataValue.Void; Result := TDataValue.Void;
// After binding, all declaration nodes are TVariableDeclarationNode // After binding, all declaration nodes are TVariableDeclarationNode
boundNode := Node as TVariableDeclarationNode; // Changed boundNode := Node as TVariableDeclarationNode;
address := (boundNode.Identifier as TIdentifierNode).Address; // Changed address := boundNode.Identifier.AsBoundIdentifierNode.Address;
// Check the IsBoxed flag set by the binder. // Check the IsBoxed flag set by the binder.
if boundNode.IsBoxed then if boundNode.IsBoxed then
@@ -641,7 +641,7 @@ var
seriesValue: TDataValue; seriesValue: TDataValue;
len: Int64; len: Int64;
begin begin
seriesValue := FScope[(Node.Series as TIdentifierNode).Address]; // Changed seriesValue := FScope[Node.Series.AsBoundIdentifierNode.Address]; // Changed
case seriesValue.Kind of case seriesValue.Kind of
vkSeries: len := seriesValue.AsSeries.Count; vkSeries: len := seriesValue.AsSeries.Count;
+14 -2
View File
@@ -37,7 +37,8 @@ type
IAddSeriesItemNode = interface; IAddSeriesItemNode = interface;
ISeriesLengthNode = interface; ISeriesLengthNode = interface;
IRecurNode = interface; IRecurNode = interface;
INopNode = interface; // Added Nop INopNode = interface;
IBoundIdentifierNode = interface;
// Defines the concrete kinds of AST nodes // Defines the concrete kinds of AST nodes
TAstNodeKind = ( TAstNodeKind = (
@@ -65,7 +66,9 @@ type
akAddSeriesItem, akAddSeriesItem,
akSeriesLength, akSeriesLength,
akRecur, akRecur,
akNop // Added Nop akNop,
// Internal node produced by compiler
akBoundIdentifier
); );
// Helper for TAstNodeKind providing a string representation // Helper for TAstNodeKind providing a string representation
@@ -156,6 +159,8 @@ type
function AsRecur: IRecurNode; function AsRecur: IRecurNode;
function AsNop: INopNode; // Added Nop function AsNop: INopNode; // Added Nop
function AsBoundIdentifierNode: IBoundIdentifierNode;
property Kind: TAstNodeKind read GetKind; property Kind: TAstNodeKind read GetKind;
end; end;
@@ -179,6 +184,13 @@ type
property Name: string read GetName; property Name: string read GetName;
end; end;
IBoundIdentifierNode = interface(IIdentifierNode)
{$region 'private'}
function GetAddress: TResolvedAddress;
{$endregion}
property Address: TResolvedAddress read GetAddress;
end;
// Represents a keyword literal in the AST (e.g., :foo) // Represents a keyword literal in the AST (e.g., :foo)
IKeywordNode = interface(IAstNode) IKeywordNode = interface(IAstNode)
{$region 'private'} {$region 'private'}
+4 -4
View File
@@ -137,7 +137,7 @@ end;
function TTypeChecker.VisitVariableDeclaration(const Node: IVariableDeclarationNode): IAstNode; function TTypeChecker.VisitVariableDeclaration(const Node: IVariableDeclarationNode): IAstNode;
var var
initType: IStaticType; initType: IStaticType;
boundIdent: TIdentifierNode; boundIdent: IIdentifierNode;
adr: TResolvedAddress; adr: TResolvedAddress;
begin begin
// 1. Visit children first (Identifier is leaf, Initializer is traversed) // 1. Visit children first (Identifier is leaf, Initializer is traversed)
@@ -149,9 +149,9 @@ begin
else else
initType := TTypes.Void; initType := TTypes.Void;
// 3. Get the address from the bound identifier. // 3. Get the address from the bound identifier (SAFE CAST)
boundIdent := (Node.Identifier as TIdentifierNode); boundIdent := Node.Identifier;
adr := boundIdent.Address; adr := boundIdent.AsBoundIdentifierNode.Address;
// 4. Update the type in the scope descriptor (which was set to Unknown by the binder). // 4. Update the type in the scope descriptor (which was set to Unknown by the binder).
FCurrentDescriptor.UpdateType(adr.SlotIndex, initType); FCurrentDescriptor.UpdateType(adr.SlotIndex, initType);
+43 -4
View File
@@ -109,6 +109,8 @@ type
function AsRecur: IRecurNode; virtual; function AsRecur: IRecurNode; virtual;
function AsNop: INopNode; virtual; // Added Nop function AsNop: INopNode; virtual; // Added Nop
function AsBoundIdentifierNode: IBoundIdentifierNode; virtual;
property StaticType: IStaticType read FStaticType write SetStaticType; property StaticType: IStaticType read FStaticType write SetStaticType;
property Kind: TAstNodeKind read GetKind; property Kind: TAstNodeKind read GetKind;
end; end;
@@ -128,15 +130,24 @@ type
TIdentifierNode = class(TAstNode, IIdentifierNode) TIdentifierNode = class(TAstNode, IIdentifierNode)
private private
FName: string; FName: string;
FAddress: TResolvedAddress;
function GetName: string; function GetName: string;
function GetKind: TAstNodeKind; override; function GetKind: TAstNodeKind; override;
public public
constructor Create(AName: string); constructor Create(const AName: string);
function Accept(const Visitor: IAstVisitor): TDataValue; override; function Accept(const Visitor: IAstVisitor): TDataValue; override;
function AsIdentifier: IIdentifierNode; override; function AsIdentifier: IIdentifierNode; override;
property Name: string read FName; // Name ist immutable property Name: string read FName; // Name ist immutable
property Address: TResolvedAddress read FAddress write FAddress; end;
TBoundIdentifierNode = class(TIdentifierNode, IBoundIdentifierNode)
private
FAddress: TResolvedAddress;
function GetAddress: TResolvedAddress;
function GetKind: TAstNodeKind; override;
public
constructor Create(const AName: string; const AAddress: TResolvedAddress);
function AsBoundIdentifierNode: IBoundIdentifierNode; override;
property Address: TResolvedAddress read GetAddress;
end; end;
TKeywordNode = class(TAstNode, IKeywordNode) TKeywordNode = class(TAstNode, IKeywordNode)
@@ -724,6 +735,11 @@ begin
raise ETypeException.Create('Node is not a BlockExpression'); raise ETypeException.Create('Node is not a BlockExpression');
end; end;
function TAstNode.AsBoundIdentifierNode: IBoundIdentifierNode;
begin
raise ETypeException.Create('Node is not a bound identifier');
end;
function TAstNode.AsConstant: IConstantNode; function TAstNode.AsConstant: IConstantNode;
begin begin
raise ETypeException.Create('Node is not a Constant'); raise ETypeException.Create('Node is not a Constant');
@@ -865,7 +881,7 @@ end;
{ TIdentifierNode } { TIdentifierNode }
constructor TIdentifierNode.Create(AName: string); constructor TIdentifierNode.Create(const AName: string);
begin begin
inherited Create; inherited Create;
FName := AName; FName := AName;
@@ -1629,4 +1645,27 @@ begin
Result := akSeriesLength; Result := akSeriesLength;
end; end;
{ TBoundIdentifierNode }
constructor TBoundIdentifierNode.Create(const AName: string; const AAddress: TResolvedAddress);
begin
inherited Create(AName);
FAddress := AAddress;
end;
function TBoundIdentifierNode.AsBoundIdentifierNode: IBoundIdentifierNode;
begin
Result := Self;
end;
function TBoundIdentifierNode.GetAddress: TResolvedAddress;
begin
Result := FAddress;
end;
function TBoundIdentifierNode.GetKind: TAstNodeKind;
begin
Result := akBoundIdentifier;
end;
end. end.