From 5003cfd899508c92f5a74ea5db19011daf43fee5 Mon Sep 17 00:00:00 2001 From: Michael Schimmel Date: Wed, 5 Nov 2025 18:33:29 +0100 Subject: [PATCH] Finished node immutability --- Src/AST/Myc.Ast.Lowering.pas | 20 ------- Src/AST/Myc.Ast.Nodes.pas | 4 ++ Src/AST/Myc.Ast.TypeChecker.pas | 6 +- Src/AST/Myc.Ast.Visitor.pas | 20 ++----- Src/AST/Myc.Ast.pas | 101 +++++++++++++++++++------------- 5 files changed, 71 insertions(+), 80 deletions(-) diff --git a/Src/AST/Myc.Ast.Lowering.pas b/Src/AST/Myc.Ast.Lowering.pas index a17fc26..0f79651 100644 --- a/Src/AST/Myc.Ast.Lowering.pas +++ b/Src/AST/Myc.Ast.Lowering.pas @@ -91,26 +91,6 @@ var left, right: IAstNode; nodeType: IStaticType; begin - // Get the type *before* replacing the node (it's an IAstTypedNode) - nodeType := Node.AsTypedNode.StaticType; - - // --- Transformation: Keyword-as-Function --- - if Node.Callee.Kind = akKeyword then - begin - var keywordNode := Node.Callee.AsKeyword; - if Length(Node.Arguments) <> 1 then - raise EArgumentException.CreateFmt( - 'Keyword :%s expects exactly one argument (the record/map), but got %d', - [keywordNode.Value.Name, Length(Node.Arguments)]); - - // Visit the base node - var baseNode := Accept(Node.Arguments[0]); - - // Create the new MemberAccess node, preserving the type - Result := TAst.MemberAccess(baseNode, keywordNode, nodeType); - exit; - end; - // --- Optimization: Operator Folding --- if Node.Callee.Kind = akIdentifier then begin diff --git a/Src/AST/Myc.Ast.Nodes.pas b/Src/AST/Myc.Ast.Nodes.pas index 6815506..ead3af0 100644 --- a/Src/AST/Myc.Ast.Nodes.pas +++ b/Src/AST/Myc.Ast.Nodes.pas @@ -362,8 +362,12 @@ type IRecordLiteralNode = interface(IAstTypedNode) {$region 'private'} function GetFields: TArray; + function GetGenericDefinition: IGenericRecordDefinition; + function GetScalarDefinition: IScalarRecordDefinition; {$endregion} property Fields: TArray read GetFields; + property GenericDefinition: IGenericRecordDefinition read GetGenericDefinition; + property ScalarDefinition: IScalarRecordDefinition read GetScalarDefinition; end; ICreateSeriesNode = interface(IAstTypedNode) diff --git a/Src/AST/Myc.Ast.TypeChecker.pas b/Src/AST/Myc.Ast.TypeChecker.pas index 83ecf25..34bbd4f 100644 --- a/Src/AST/Myc.Ast.TypeChecker.pas +++ b/Src/AST/Myc.Ast.TypeChecker.pas @@ -540,8 +540,7 @@ begin begin def := TScalarRecordRegistry.Intern(scalarDefFields); staticType := TTypes.CreateRecord(def); - Result := TRecordLiteralNode.Create(newFields, staticType); - (Result as TRecordLiteralNode).Definition := def; + Result := TAst.RecordLiteral(newFields, def, nil, staticType); end else begin @@ -552,8 +551,7 @@ begin var genDef := TGenericRecordRegistry.Intern(genDefFields); staticType := TTypes.CreateGenericRecord(genDef); - Result := TGenericRecordLiteralNode.Create(newFields, staticType); - (Result as TGenericRecordLiteralNode).GenericDefinition := genDef; + Result := TAst.RecordLiteral(newFields, nil, genDef, staticType); end; end; diff --git a/Src/AST/Myc.Ast.Visitor.pas b/Src/AST/Myc.Ast.Visitor.pas index 8909b03..86eb3bf 100644 --- a/Src/AST/Myc.Ast.Visitor.pas +++ b/Src/AST/Myc.Ast.Visitor.pas @@ -697,20 +697,18 @@ end; function TAstTransformer.VisitRecordLiteral(const Node: IRecordLiteralNode): IAstNode; var - N: IRecordLiteralNode; // Use interface i: Integer; newFields: TArray; hasChanged: Boolean; begin - N := Node; - SetLength(newFields, Length(N.Fields)); + SetLength(newFields, Length(Node.Fields)); hasChanged := False; - for i := 0 to High(N.Fields) do + for i := 0 to High(Node.Fields) do begin - newFields[i].Key := Accept(N.Fields[i].Key).AsKeyword; - newFields[i].Value := Accept(N.Fields[i].Value); - if (newFields[i].Key <> N.Fields[i].Key) or (newFields[i].Value <> N.Fields[i].Value) then + 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 hasChanged := True; end; @@ -720,13 +718,7 @@ begin begin // Rebuild the node, preserving its specific type and definitions // Use TAst factory - Result := TAst.RecordLiteral(newFields, N.StaticType); - - // Copy runtime properties (casts are valid) - if N is TGenericRecordLiteralNode then - (Result as TGenericRecordLiteralNode).GenericDefinition := (N as TGenericRecordLiteralNode).GenericDefinition - else - (Result as TRecordLiteralNode).Definition := (N as TRecordLiteralNode).Definition; + Result := TAst.RecordLiteral(newFields, Node.ScalarDefinition, Node.GenericDefinition, Node.StaticType); end; end; diff --git a/Src/AST/Myc.Ast.pas b/Src/AST/Myc.Ast.pas index b7e68ee..98084ec 100644 --- a/Src/AST/Myc.Ast.pas +++ b/Src/AST/Myc.Ast.pas @@ -120,6 +120,8 @@ type ): IMemberAccessNode; static; class function RecordLiteral( const AFields: TArray; + const AScalarDefinition: IScalarRecordDefinition = nil; + const AGenericDefinition: IGenericRecordDefinition = nil; const AStaticType: IStaticType = nil ): IRecordLiteralNode; static; class function CreateSeries(const ADefinition: String; const AStaticType: IStaticType = nil): ICreateSeriesNode; static; @@ -133,6 +135,14 @@ type end; +implementation + +uses + System.Generics.Defaults; + +type + // --- Concrete Class Definitions moved to implementation --- + // Common base class for AST nodes to reduce boilerplate. TAstNode = class(TInterfacedObject, IAstNode) private @@ -186,38 +196,6 @@ type property StaticType: IStaticType read GetStaticType; end; - // TRecordLiteralNode is still needed in the interface - // for TGenericRecordLiteralNode - TRecordLiteralNode = class(TAstTypedNode, IRecordLiteralNode) - private - FFields: TArray; - FDefinition: IScalarRecordDefinition; - function GetFields: TArray; - function GetKind: TAstNodeKind; override; - public - constructor Create(const AFields: TArray; const AStaticType: IStaticType); - function Accept(const Visitor: IAstVisitor): TDataValue; override; - function AsRecordLiteral: IRecordLiteralNode; override; - property Fields: TArray read FFields; - property Definition: IScalarRecordDefinition read FDefinition write FDefinition; - end; - - TGenericRecordLiteralNode = class(TRecordLiteralNode, IRecordLiteralNode) - private - FGenericDefinition: IGenericRecordDefinition; - public - constructor Create(const AFields: TArray; const AStaticType: IStaticType); - property GenericDefinition: IGenericRecordDefinition read FGenericDefinition write FGenericDefinition; - end; - -implementation - -uses - System.Generics.Defaults; - -type - // --- Concrete Class Definitions moved to implementation --- - TLambdaExpressionNode = class(TAstTypedNode, ILambdaExpressionNode) private FParameters: TArray; @@ -580,6 +558,31 @@ type property Series: IIdentifierNode read FSeries; end; + // TRecordLiteralNode is still needed in the interface + // for TGenericRecordLiteralNode + TRecordLiteralNode = class(TAstTypedNode, IRecordLiteralNode) + private + FFields: TArray; + FScalarDefinition: IScalarRecordDefinition; + FGenericDefinition: IGenericRecordDefinition; + function GetFields: TArray; + function GetGenericDefinition: IGenericRecordDefinition; + function GetKind: TAstNodeKind; override; + function GetScalarDefinition: IScalarRecordDefinition; + public + constructor Create( + const AFields: TArray; + const AScalarDefinition: IScalarRecordDefinition; + const AGenericDefinition: IGenericRecordDefinition; + const AStaticType: IStaticType + ); + function Accept(const Visitor: IAstVisitor): TDataValue; override; + function AsRecordLiteral: IRecordLiteralNode; override; + property Fields: TArray read FFields; + property ScalarDefinition: IScalarRecordDefinition read GetScalarDefinition; + property GenericDefinition: IGenericRecordDefinition read GetGenericDefinition; + end; + TNopNode = class(TAstNode, INopNode) private function GetKind: TAstNodeKind; override; @@ -882,14 +885,18 @@ begin ); end; -class function TAst.RecordLiteral(const AFields: TArray; const AStaticType: IStaticType = nil): IRecordLiteralNode; +class function TAst.RecordLiteral( + const AFields: TArray; + const AScalarDefinition: IScalarRecordDefinition = nil; + const AGenericDefinition: IGenericRecordDefinition = nil; + const AStaticType: IStaticType = nil +): IRecordLiteralNode; begin - // By default, the parser creates the generic (most flexible) node type. - // The TypeChecker (Phase 3) is responsible for "lowering" this to a - // TRecordLiteralNode if it determines all fields are scalar. Result := - TGenericRecordLiteralNode.Create( + TRecordLiteralNode.Create( AFields, + AScalarDefinition, + AGenericDefinition, if AStaticType <> nil then AStaticType else TTypes.Unknown ); @@ -1870,10 +1877,17 @@ end; { TRecordLiteralNode } -constructor TRecordLiteralNode.Create(const AFields: TArray; const AStaticType: IStaticType); +constructor TRecordLiteralNode.Create( + const AFields: TArray; + const AScalarDefinition: IScalarRecordDefinition; + const AGenericDefinition: IGenericRecordDefinition; + const AStaticType: IStaticType +); begin inherited Create(AStaticType); FFields := AFields; + FGenericDefinition := AGenericDefinition; + FScalarDefinition := AScalarDefinition; end; function TRecordLiteralNode.Accept(const Visitor: IAstVisitor): TDataValue; @@ -1891,16 +1905,19 @@ begin Result := FFields; end; +function TRecordLiteralNode.GetGenericDefinition: IGenericRecordDefinition; +begin + Result := FGenericDefinition; +end; + function TRecordLiteralNode.GetKind: TAstNodeKind; begin Result := akRecordLiteral; end; -{ TGenericRecordLiteralNode } - -constructor TGenericRecordLiteralNode.Create(const AFields: TArray; const AStaticType: IStaticType); +function TRecordLiteralNode.GetScalarDefinition: IScalarRecordDefinition; begin - inherited Create(AFields, AStaticType); + Result := FScalarDefinition; end; { TCreateSeriesNode }