From d82a75aba667e897c88b46ef60f074ee0e142f83 Mon Sep 17 00:00:00 2001 From: Michael Schimmel Date: Tue, 4 Nov 2025 20:12:12 +0100 Subject: [PATCH] Making AST node inmmutable - WIP --- Src/AST/Myc.Ast.Binding.pas | 82 +++++++++++++++++++-------------- Src/AST/Myc.Ast.Dumper.pas | 12 +++-- Src/AST/Myc.Ast.Evaluator.pas | 14 +++--- Src/AST/Myc.Ast.Nodes.pas | 16 ++++++- Src/AST/Myc.Ast.TypeChecker.pas | 8 ++-- Src/AST/Myc.Ast.pas | 47 +++++++++++++++++-- 6 files changed, 123 insertions(+), 56 deletions(-) diff --git a/Src/AST/Myc.Ast.Binding.pas b/Src/AST/Myc.Ast.Binding.pas index a2b39f0..1291619 100644 --- a/Src/AST/Myc.Ast.Binding.pas +++ b/Src/AST/Myc.Ast.Binding.pas @@ -284,13 +284,17 @@ begin // Define parameters for i := 0 to High(N.Parameters) do begin - var paramNode := N.Parameters[i]; + var paramNode := N.Parameters[i]; // This is a TIdentifierNode var slotIndex := FCurrentDescriptor.Define(paramNode.Name, TTypes.Unknown); adr := TResolvedAddress.Create(akLocalOrParent, 0, slotIndex); - // Mutate the parameter node with its address - (paramNode as TIdentifierNode).Address := adr; - (paramNode as TAstNode).StaticType := TTypes.Unknown; + // --- Replace Node --- + // Create a new TBoundIdentifierNode (implementation is in Myc.Ast) + 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; // Visit the body *within the new scope* @@ -342,42 +346,50 @@ var symbol: TResolvedSymbol; adr: TResolvedAddress; begin - symbol := FCurrentDescriptor.FindSymbol(Node.Name); - adr := symbol.Address; - - if adr.Kind = akLocalOrParent then + // We only bind nodes that are the base parser type. + if Node.Kind = akIdentifier then begin - if (adr.ScopeDepth > 0) and (FUpvalueStack.Count > 0) then - begin - // --- Handle Upvalue --- - var upvalueMap := FUpvalueStack.Peek; - // Adjust address to be relative to the captured scope - dec(adr.ScopeDepth); + symbol := FCurrentDescriptor.FindSymbol(Node.Name); + adr := symbol.Address; - var upvalueIndex: Integer; - if not upvalueMap.TryGetValue(adr, upvalueIndex) then + if adr.Kind = akUnresolved 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 - // This is a new upvalue for this lambda - upvalueIndex := upvalueMap.Count; - upvalueMap.Add(adr, upvalueIndex); + // --- Handle Upvalue --- + var upvalueMap := FUpvalueStack.Peek; + // 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; - - // 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; + // else: It's a local (ScopeDepth=0), adr is already correct. 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 else - raise Exception.CreateFmt('Undefined identifier: "%s"', [Node.Name]); - - Result := Node; + begin + // It's already an akBoundIdentifier (or other specialized type), just return it. + Result := Node; + end; end; function TAstBinder.VisitVariableDeclaration(const Node: IVariableDeclarationNode): IAstNode; @@ -399,8 +411,10 @@ begin slotIndex := FCurrentDescriptor.Define(N.Identifier.Name, TTypes.Unknown); address := TResolvedAddress.Create(akLocalOrParent, 0, slotIndex); - // 3. Mutate the Identifier node (which is NOT visited by inherited call) - (N.Identifier as TIdentifierNode).Address := address; + // 3. --- Replace Node --- + // 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 // 4. Mutate this declaration node diff --git a/Src/AST/Myc.Ast.Dumper.pas b/Src/AST/Myc.Ast.Dumper.pas index 6c59d8d..dabcba8 100644 --- a/Src/AST/Myc.Ast.Dumper.pas +++ b/Src/AST/Myc.Ast.Dumper.pas @@ -143,13 +143,15 @@ end; procedure TAstDumper.VisitIdentifier(const Node: IIdentifierNode); var - N: TIdentifierNode; + adr: TResolvedAddress; begin - N := (Node as TIdentifierNode); - if N.Address.Kind <> akUnresolved then - LogFmt('Identifier: %s -> %s', [N.Name, FormatAddress(N.Address)]) + if Node.Kind = akBoundIdentifier then + adr := Node.AsBoundIdentifierNode.Address; + + if adr.Kind <> akUnresolved then + LogFmt('Identifier: %s -> %s', [Node.Name, FormatAddress(adr)]) else - LogFmt('Identifier: %s (unbound)', [N.Name]); + LogFmt('Identifier: %s (unbound)', [Node.Name]); end; procedure TAstDumper.VisitKeyword(const Node: IKeywordNode); diff --git a/Src/AST/Myc.Ast.Evaluator.pas b/Src/AST/Myc.Ast.Evaluator.pas index 9d9dfc7..281c043 100644 --- a/Src/AST/Myc.Ast.Evaluator.pas +++ b/Src/AST/Myc.Ast.Evaluator.pas @@ -234,7 +234,7 @@ begin for i := 0 to High(ArgValues) do begin // 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]; end; @@ -334,7 +334,7 @@ var itemValue, lookbackValue, seriesVar: TDataValue; lookback: Int64; begin - seriesVar := FScope[(Node.Series as TIdentifierNode).Address]; // Changed + seriesVar := FScope[Node.Series.AsBoundIdentifierNode.Address]; // Changed itemValue := Node.Value.Accept(Self); lookback := -1; @@ -368,7 +368,7 @@ begin // Evaluate the new value. Result := Node.Value.Accept(Self); // 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; function TEvaluatorVisitor.VisitConstant(const Node: IConstantNode): TDataValue; @@ -405,7 +405,7 @@ end; function TEvaluatorVisitor.VisitIdentifier(const Node: IIdentifierNode): TDataValue; begin // The scope's GetValues implementation now handles unboxing automatically. - Result := FScope[(Node as TIdentifierNode).Address]; // Changed + Result := FScope[Node.AsBoundIdentifierNode.Address]; // Changed end; function TEvaluatorVisitor.VisitIndexer(const Node: IIndexerNode): TDataValue; @@ -548,8 +548,8 @@ begin Result := TDataValue.Void; // After binding, all declaration nodes are TVariableDeclarationNode - boundNode := Node as TVariableDeclarationNode; // Changed - address := (boundNode.Identifier as TIdentifierNode).Address; // Changed + boundNode := Node as TVariableDeclarationNode; + address := boundNode.Identifier.AsBoundIdentifierNode.Address; // Check the IsBoxed flag set by the binder. if boundNode.IsBoxed then @@ -641,7 +641,7 @@ var seriesValue: TDataValue; len: Int64; begin - seriesValue := FScope[(Node.Series as TIdentifierNode).Address]; // Changed + seriesValue := FScope[Node.Series.AsBoundIdentifierNode.Address]; // Changed case seriesValue.Kind of vkSeries: len := seriesValue.AsSeries.Count; diff --git a/Src/AST/Myc.Ast.Nodes.pas b/Src/AST/Myc.Ast.Nodes.pas index 946d716..8b41224 100644 --- a/Src/AST/Myc.Ast.Nodes.pas +++ b/Src/AST/Myc.Ast.Nodes.pas @@ -37,7 +37,8 @@ type IAddSeriesItemNode = interface; ISeriesLengthNode = interface; IRecurNode = interface; - INopNode = interface; // Added Nop + INopNode = interface; + IBoundIdentifierNode = interface; // Defines the concrete kinds of AST nodes TAstNodeKind = ( @@ -65,7 +66,9 @@ type akAddSeriesItem, akSeriesLength, akRecur, - akNop // Added Nop + akNop, + // Internal node produced by compiler + akBoundIdentifier ); // Helper for TAstNodeKind providing a string representation @@ -156,6 +159,8 @@ type function AsRecur: IRecurNode; function AsNop: INopNode; // Added Nop + function AsBoundIdentifierNode: IBoundIdentifierNode; + property Kind: TAstNodeKind read GetKind; end; @@ -179,6 +184,13 @@ type property Name: string read GetName; 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) IKeywordNode = interface(IAstNode) {$region 'private'} diff --git a/Src/AST/Myc.Ast.TypeChecker.pas b/Src/AST/Myc.Ast.TypeChecker.pas index c49f4d7..edb1fd8 100644 --- a/Src/AST/Myc.Ast.TypeChecker.pas +++ b/Src/AST/Myc.Ast.TypeChecker.pas @@ -137,7 +137,7 @@ end; function TTypeChecker.VisitVariableDeclaration(const Node: IVariableDeclarationNode): IAstNode; var initType: IStaticType; - boundIdent: TIdentifierNode; + boundIdent: IIdentifierNode; adr: TResolvedAddress; begin // 1. Visit children first (Identifier is leaf, Initializer is traversed) @@ -149,9 +149,9 @@ begin else initType := TTypes.Void; - // 3. Get the address from the bound identifier. - boundIdent := (Node.Identifier as TIdentifierNode); - adr := boundIdent.Address; + // 3. Get the address from the bound identifier (SAFE CAST) + boundIdent := Node.Identifier; + adr := boundIdent.AsBoundIdentifierNode.Address; // 4. Update the type in the scope descriptor (which was set to Unknown by the binder). FCurrentDescriptor.UpdateType(adr.SlotIndex, initType); diff --git a/Src/AST/Myc.Ast.pas b/Src/AST/Myc.Ast.pas index e575f76..8383b7e 100644 --- a/Src/AST/Myc.Ast.pas +++ b/Src/AST/Myc.Ast.pas @@ -109,6 +109,8 @@ type function AsRecur: IRecurNode; virtual; function AsNop: INopNode; virtual; // Added Nop + function AsBoundIdentifierNode: IBoundIdentifierNode; virtual; + property StaticType: IStaticType read FStaticType write SetStaticType; property Kind: TAstNodeKind read GetKind; end; @@ -128,15 +130,24 @@ type TIdentifierNode = class(TAstNode, IIdentifierNode) private FName: string; - FAddress: TResolvedAddress; function GetName: string; function GetKind: TAstNodeKind; override; public - constructor Create(AName: string); + constructor Create(const AName: string); function Accept(const Visitor: IAstVisitor): TDataValue; override; function AsIdentifier: IIdentifierNode; override; 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; TKeywordNode = class(TAstNode, IKeywordNode) @@ -724,6 +735,11 @@ begin raise ETypeException.Create('Node is not a BlockExpression'); end; +function TAstNode.AsBoundIdentifierNode: IBoundIdentifierNode; +begin + raise ETypeException.Create('Node is not a bound identifier'); +end; + function TAstNode.AsConstant: IConstantNode; begin raise ETypeException.Create('Node is not a Constant'); @@ -865,7 +881,7 @@ end; { TIdentifierNode } -constructor TIdentifierNode.Create(AName: string); +constructor TIdentifierNode.Create(const AName: string); begin inherited Create; FName := AName; @@ -1629,4 +1645,27 @@ begin Result := akSeriesLength; 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.