AST testing

This commit is contained in:
Michael Schimmel
2025-11-23 00:24:43 +01:00
parent c5167b8550
commit a052dfb20f
23 changed files with 792 additions and 1260 deletions
+1 -1
View File
@@ -212,7 +212,7 @@ begin
// 2. Define the variable in the CURRENT scope
// Store the Node reference so we can add it to FBoxedDeclarations if captured.
FCurrentScope.Define(Node.Identifier.Name, Node);
FCurrentScope.Define(Node.Target.AsIdentifier.Name, Node);
Result := Node;
end;
+6 -5
View File
@@ -256,10 +256,11 @@ var
newIdent: IIdentifierNode;
isBoxed: Boolean;
begin
if not IsValidIdentifier(Node.Identifier.Name) then
raise Exception.CreateFmt('Invalid identifier name: "%s".', [Node.Identifier.Name]);
var identifier := Node.Target.AsIdentifier;
if not IsValidIdentifier(identifier.Name) then
raise Exception.CreateFmt('Invalid identifier name: "%s".', [identifier.Name]);
slot := FCurrentBuilder.Define(Node.Identifier.Name);
slot := FCurrentBuilder.Define(identifier.Name);
addr := TResolvedAddress.Create(akLocalOrParent, 0, slot);
if Assigned(Node.Initializer) then
@@ -267,7 +268,7 @@ begin
else
newInit := nil;
newIdent := TAst.Identifier(Node.Identifier.Name, addr, TTypes.Unknown);
newIdent := TAst.Identifier(identifier.Name, addr, TTypes.Unknown);
isBoxed := (FBoxedDeclarations <> nil) and FBoxedDeclarations.Contains(Node);
Result := TAst.VarDecl(newIdent, newInit, TTypes.Unknown, isBoxed);
@@ -281,7 +282,7 @@ var
newIdent: IAstNode;
newValue: IAstNode;
begin
newIdent := Accept(Node.Identifier);
newIdent := Accept(Node.Target);
newValue := Accept(Node.Value);
Result := TAst.Assign(newIdent.AsIdentifier, newValue, TTypes.Unknown);
+71 -5
View File
@@ -87,6 +87,19 @@ type
): IAstNode; static;
end;
TMacroRegistry = class(TInterfacedObject, IMacroRegistry)
private
FParent: IMacroRegistry;
FMacros: TDictionary<string, IMacroDefinitionNode>;
function GetParent: IMacroRegistry;
public
constructor Create(AParent: IMacroRegistry);
destructor Destroy; override;
procedure Define(const Node: IMacroDefinitionNode);
function Find(const Name: string): IMacroDefinitionNode;
function CreateChildRegistry: IMacroRegistry;
end;
implementation
uses
@@ -193,14 +206,23 @@ end;
function TExpansionVisitor.VisitVariableDeclaration(const Node: IVariableDeclarationNode): IAstNode;
var
newTarget, newInit: IAstNode;
newName: string;
newIdent: IIdentifierNode;
newInit: IAstNode;
begin
newInit := Accept(Node.Initializer);
newName := Gensym(Node.Identifier.Name);
newIdent := TAst.Identifier(newName, TTypes.Unknown);
Result := TAst.VarDecl(newIdent, newInit, TTypes.Unknown);
// Check if target is identifier before trying to rename
if Node.Target.Kind = akIdentifier then
begin
newName := Gensym(Node.Target.AsIdentifier.Name);
newTarget := TAst.Identifier(newName, TTypes.Unknown);
end
else
begin
newTarget := Accept(Node.Target); // Recursively expand unquotes in target position!
end;
Result := TAst.VarDecl(newTarget, newInit, TTypes.Unknown);
end;
function TExpansionVisitor.VisitLambdaExpression(const Node: ILambdaExpressionNode): IAstNode;
@@ -438,4 +460,48 @@ begin
raise Exception.Create('Unquote-splicing (`~@`) can only be used inside a quasiquote.');
end;
{ TMacroRegistry }
constructor TMacroRegistry.Create(AParent: IMacroRegistry);
begin
inherited Create;
FParent := AParent;
FMacros := TDictionary<string, IMacroDefinitionNode>.Create;
end;
destructor TMacroRegistry.Destroy;
begin
FMacros.Free;
inherited Destroy;
end;
function TMacroRegistry.GetParent: IMacroRegistry;
begin
Result := FParent;
end;
procedure TMacroRegistry.Define(const Node: IMacroDefinitionNode);
begin
FMacros.AddOrSetValue(Node.Name.Name, Node);
end;
function TMacroRegistry.Find(const Name: string): IMacroDefinitionNode;
var
current: IMacroRegistry;
begin
current := Self;
while Assigned(current) do
begin
if (current as TMacroRegistry).FMacros.TryGetValue(Name, Result) then
exit;
current := current.Parent;
end;
Result := nil;
end;
function TMacroRegistry.CreateChildRegistry: IMacroRegistry;
begin
Result := TMacroRegistry.Create(Self);
end;
end.
+3 -3
View File
@@ -240,7 +240,7 @@ var
placeholderType: IStaticType;
i: Integer;
begin
adr := Node.Identifier.Address;
adr := Node.Target.AsIdentifier.Address;
initType := TTypes.Unknown;
// Recursive lambda bootstrap logic
@@ -274,7 +274,7 @@ begin
if initType.Kind <> stUnknown then
FCurrentContext.SetType(adr.SlotIndex, initType);
newIdent := TAst.Identifier(Node.Identifier.Name, adr, initType);
newIdent := TAst.Identifier(Node.Target.AsIdentifier.Name, adr, initType);
Result := TAst.VarDecl(newIdent.AsIdentifier, newInitializer, initType, Node.IsBoxed);
end;
@@ -288,7 +288,7 @@ var
placeholderType: IStaticType;
i: Integer;
begin
newIdent := Accept(Node.Identifier);
newIdent := Accept(Node.Target);
targetType := newIdent.AsTypedNode.StaticType;
adr := newIdent.AsIdentifier.Address;
+2 -2
View File
@@ -162,7 +162,7 @@ end;
function TDebugEvaluatorVisitor.VisitAssignment(const Node: IAssignmentNode): TDataValue;
begin
AppendLine(Format('Assignment to "%s" {', [Node.Identifier.Name]));
AppendLine(Format('Assignment to "%s" {', [Node.Target.AsIdentifier.Name]));
Indent;
try
Result := inherited VisitAssignment(Node);
@@ -278,7 +278,7 @@ end;
function TDebugEvaluatorVisitor.VisitVariableDeclaration(const Node: IVariableDeclarationNode): TDataValue;
begin
AppendLine(Format('VarDecl %s :=', [Node.Identifier.Name]));
AppendLine(Format('VarDecl %s :=', [Node.Target.AsIdentifier.Name]));
Indent;
try
Result := inherited VisitVariableDeclaration(Node);
+2 -2
View File
@@ -355,7 +355,7 @@ procedure TAstDumper.VisitVariableDeclaration(const Node: IVariableDeclarationNo
begin
LogFmt('VariableDeclaration (IsBoxed: %s)', [Node.IsBoxed.ToString(TUseBoolStrs.True)], Node);
Indent;
Node.Identifier.Accept(Self);
Node.Target.Accept(Self);
if Assigned(Node.Initializer) then
begin
Log('Initializer:');
@@ -368,7 +368,7 @@ procedure TAstDumper.VisitAssignment(const Node: IAssignmentNode);
begin
Log('Assignment', Node);
Indent;
Node.Identifier.Accept(Self);
Node.Target.Accept(Self);
Log('Value:');
Node.Value.Accept(Self);
Unindent;
+2 -60
View File
@@ -228,20 +228,6 @@ type
function CreateVisitor(const AScope: IExecutionScope): IEvaluatorVisitor;
end;
{ TMacroRegistryImpl }
TMacroRegistryImpl = class(TInterfacedObject, IMacroRegistry)
private
FParent: IMacroRegistry;
FMacros: TDictionary<string, IMacroDefinitionNode>;
function GetParent: IMacroRegistry;
public
constructor Create(AParent: IMacroRegistry);
destructor Destroy; override;
procedure Define(const Node: IMacroDefinitionNode);
function Find(const Name: string): IMacroDefinitionNode;
function CreateChildRegistry: IMacroRegistry;
end;
TFunctionDefinitionRegistry = class(TInterfacedObject, IFunctionDefinitionRegistry)
private
FMap: TDictionary<TResolvedAddress, IFunctionDefinition>;
@@ -336,7 +322,7 @@ begin
// Initialize root scope with library registration
RootScope := TAst.CreateScope(nil, nil, True);
Result.Create(TEnvironment.Create(RootScope, TMacroRegistryImpl.Create(nil), TStandardExecutionStrategy.Create));
Result.Create(TEnvironment.Create(RootScope, TMacroRegistry.Create(nil), TStandardExecutionStrategy.Create));
end;
function TAstEnvironment.CreateEnvironment: TAstEnvironment;
@@ -393,50 +379,6 @@ begin
Result := TDebugEvaluatorVisitor.Create(AScope, FLog, FShowScope, 0);
end;
{ TMacroRegistryImpl }
constructor TMacroRegistryImpl.Create(AParent: IMacroRegistry);
begin
inherited Create;
FParent := AParent;
FMacros := TDictionary<string, IMacroDefinitionNode>.Create;
end;
destructor TMacroRegistryImpl.Destroy;
begin
FMacros.Free;
inherited Destroy;
end;
function TMacroRegistryImpl.GetParent: IMacroRegistry;
begin
Result := FParent;
end;
procedure TMacroRegistryImpl.Define(const Node: IMacroDefinitionNode);
begin
FMacros.AddOrSetValue(Node.Name.Name, Node);
end;
function TMacroRegistryImpl.Find(const Name: string): IMacroDefinitionNode;
var
current: IMacroRegistry;
begin
current := Self;
while Assigned(current) do
begin
if (current as TMacroRegistryImpl).FMacros.TryGetValue(Name, Result) then
exit;
current := current.Parent;
end;
Result := nil;
end;
function TMacroRegistryImpl.CreateChildRegistry: IMacroRegistry;
begin
Result := TMacroRegistryImpl.Create(Self);
end;
{ TFunctionDefinitionRegistry }
constructor TFunctionDefinitionRegistry.Create;
@@ -514,7 +456,7 @@ end;
function TEnvironment.CreateEnvironment: IEnvironment;
begin
Result := TEnvironment.Create(TAst.CreateScope(FRootScope), TMacroRegistryImpl.Create(FMacroRegistry), FExecutionStrategy);
Result := TEnvironment.Create(TAst.CreateScope(FRootScope), TMacroRegistry.Create(FMacroRegistry), FExecutionStrategy);
end;
function TEnvironment.Compile(
+11 -4
View File
@@ -168,6 +168,7 @@ begin
TScalar.TKind.Ordinal: Result := AValue.AsScalar.Value.AsInt64 <> 0;
TScalar.TKind.Float: Result := AValue.AsScalar.Value.AsDouble <> 0.0;
TScalar.TKind.Keyword: Result := AValue.AsScalar.Value.AsInt64 <> 0;
TScalar.TKind.Boolean: Result := AValue.AsScalar.Value.AsInt64 <> 0;
else
Result := false;
end;
@@ -392,10 +393,13 @@ end;
function TEvaluatorVisitor.VisitAssignment(const Node: IAssignmentNode): TDataValue;
begin
// Evaluate the new value.
if Node.Target.Kind <> akIdentifier then
raise ETypeException.Create('Runtime Error: Assignment target must be an identifier.');
// Evaluate value
Result := Node.Value.Accept(Self);
// Assign it.
FScope[Node.Identifier.Address] := Result;
// Assign
FScope[Node.Target.AsIdentifier.Address] := Result;
end;
function TEvaluatorVisitor.VisitConstant(const Node: IConstantNode): TDataValue;
@@ -561,6 +565,9 @@ function TEvaluatorVisitor.VisitVariableDeclaration(const Node: IVariableDeclara
var
address: TResolvedAddress;
begin
if Node.Target.Kind <> akIdentifier then
raise ETypeException.Create('Runtime Error: Variable declaration target must be an identifier.');
// 1. Evaluate Initializer
if Assigned(Node.Initializer) then
Result := Node.Initializer.Accept(Self)
@@ -568,7 +575,7 @@ begin
Result := TDataValue.Void;
// 2. Get Address (assigned by Binder)
address := Node.Identifier.Address;
address := Node.Target.AsIdentifier.Address;
// 3. Store Value
if Node.IsBoxed then
+2 -2
View File
@@ -319,7 +319,7 @@ function TJsonAstConverter.VisitVariableDeclaration(const Node: IVariableDeclara
var
identObj, initObj: TJSONObject;
begin
identObj := Accept(Node.Identifier);
identObj := Accept(Node.Target);
initObj := Accept(Node.Initializer); // Accept handles nil
Result := TJSONObject.Create;
@@ -336,7 +336,7 @@ function TJsonAstConverter.VisitAssignment(const Node: IAssignmentNode): TJSONOb
var
identObj, valueObj: TJSONObject;
begin
identObj := Accept(Node.Identifier);
identObj := Accept(Node.Target);
valueObj := Accept(Node.Value);
Result := TJSONObject.Create;
+4 -4
View File
@@ -287,21 +287,21 @@ type
IVariableDeclarationNode = interface(IAstTypedNode)
{$region 'private'}
function GetIdentifier: IIdentifierNode;
function GetTarget: IAstNode;
function GetInitializer: IAstNode;
function GetIsBoxed: Boolean;
{$endregion}
property Identifier: IIdentifierNode read GetIdentifier;
property Target: IAstNode read GetTarget;
property Initializer: IAstNode read GetInitializer;
property IsBoxed: Boolean read GetIsBoxed;
end;
IAssignmentNode = interface(IAstTypedNode)
{$region 'private'}
function GetIdentifier: IIdentifierNode;
function GetTarget: IAstNode;
function GetValue: IAstNode;
{$endregion}
property Identifier: IIdentifierNode read GetIdentifier;
property Target: IAstNode read GetTarget;
property Value: IAstNode read GetValue;
end;
+9 -14
View File
@@ -574,20 +574,15 @@ begin
end
else if SameText(head.Token.Text, 'def') then
begin
// Validate argument count for 'def' special form.
// Identifier check removed to support macros (e.g. def ~x 1)
if not (Length(tailNodes) in [1, 2]) then
raise Exception.CreateFmt(
'Syntax Error: ''def'' requires an identifier and an optional initializer (1 or 2 arguments), but got %d.',
[Length(tailNodes)]);
if tailTokens[0].Kind <> tkIdentifier then
raise Exception.Create('Syntax Error: Expected an identifier for def statement.');
raise Exception.CreateFmt('Syntax Error: ''def'' requires a target and an optional initializer.', []);
initializer := nil;
if Length(tailNodes) = 2 then
initializer := tailNodes[1];
Result := TAst.VarDecl(IIdentifierNode(tailNodes[0]), initializer);
Result := TAst.VarDecl(tailNodes[0], initializer);
end
else if SameText(head.Token.Text, 'defmacro') then
begin
@@ -606,11 +601,11 @@ begin
end
else if SameText(head.Token.Text, 'assign') then
begin
// Identifier check removed
if Length(tailNodes) <> 2 then
raise Exception.Create('Syntax Error: ''assign'' requires exactly 2 arguments (identifier and value).');
if tailTokens[0].Kind <> tkIdentifier then
raise Exception.Create('Syntax Error: Expected an identifier for assignment.');
Result := TAst.Assign(IIdentifierNode(tailNodes[0]), tailNodes[1]);
raise Exception.Create('Syntax Error: ''assign'' requires exactly 2 arguments (target and value).');
Result := TAst.Assign(tailNodes[0], tailNodes[1]);
end
else if SameText(head.Token.Text, 'fn') then
begin
@@ -988,7 +983,7 @@ end;
procedure TPrettyPrintVisitor.VisitVariableDeclaration(const Node: IVariableDeclarationNode);
begin
Append('(def ');
Node.Identifier.Accept(Self);
Node.Target.Accept(Self);
if Assigned(Node.Initializer) then
begin
Append(' ');
@@ -1000,7 +995,7 @@ end;
procedure TPrettyPrintVisitor.VisitAssignment(const Node: IAssignmentNode);
begin
Append('(assign ');
Node.Identifier.Accept(Self);
Node.Target.Accept(Self);
Append(' ');
Node.Value.Accept(Self);
Append(')');
+9 -12
View File
@@ -545,35 +545,32 @@ end;
function TAstTransformer.VisitVariableDeclaration(const Node: IVariableDeclarationNode): IAstNode;
var
newIdent: IIdentifierNode;
newTarget: IAstNode;
newInit: IAstNode;
begin
// No longer cast to concrete class, use interface
newIdent := Accept(Node.Identifier).AsIdentifier;
newInit := Accept(Node.Initializer); // Accept handles nil
newTarget := Accept(Node.Target);
newInit := Accept(Node.Initializer);
if (newIdent = Node.Identifier) and (newInit = Node.Initializer) then
if (newTarget = Node.Target) and (newInit = Node.Initializer) then
Result := Node
else
begin
// Use TAst factory and copy properties via interface getters
Result := TAst.VarDecl(newIdent, newInit, Node.StaticType, Node.IsBoxed);
Result := TAst.VarDecl(newTarget, newInit, Node.StaticType, Node.IsBoxed);
end;
end;
function TAstTransformer.VisitAssignment(const Node: IAssignmentNode): IAstNode;
var
newIdent: IIdentifierNode;
newTarget: IAstNode;
newValue: IAstNode;
begin
newTarget := Accept(Node.Target);
newValue := Accept(Node.Value);
newIdent := Accept(Node.Identifier).AsIdentifier;
if (newValue = Node.Value) and (newIdent = Node.Identifier) then
if (newTarget = Node.Target) and (newValue = Node.Value) then
Result := Node
else
// Use TAst factory
Result := TAst.Assign(newIdent, newValue, Node.StaticType);
Result := TAst.Assign(newTarget, newValue, Node.StaticType);
end;
function TAstTransformer.VisitMacroDefinition(const Node: IMacroDefinitionNode): IAstNode;
+20 -34
View File
@@ -95,17 +95,13 @@ type
class function Block(const AExpressions: array of IAstNode; const AStaticType: IStaticType = nil): IBlockExpressionNode; static;
class function VarDecl(
const AIdentifier: IIdentifierNode;
const AIdentifier: IAstNode;
AInitializer: IAstNode = nil;
const AStaticType: IStaticType = nil;
const AIsBoxed: Boolean = False
): IVariableDeclarationNode; static;
class function Assign(
const AIdentifier: IIdentifierNode;
const AValue: IAstNode;
const AStaticType: IStaticType = nil
): IAssignmentNode; static;
class function Assign(const ATarget, AValue: IAstNode; const AStaticType: IStaticType = nil): IAssignmentNode; static;
class function AssignResult(const AValue: IAstNode): IAssignmentNode; static; deprecated;
class function Indexer(const ABase: IAstNode; const AIndex: IAstNode; const AStaticType: IStaticType = nil): IIndexerNode; static;
class function MemberAccess(
@@ -249,23 +245,17 @@ type
function AsFunctionCall: IFunctionCallNode; override;
end;
// ... (Other node definitions unchanged) ...
TVariableDeclarationNode = class(TAstTypedNode, IVariableDeclarationNode)
private
FIdentifier: IIdentifierNode;
FInitializer: IAstNode;
FTarget: IAstNode;
FIsBoxed: Boolean;
function GetIdentifier: IIdentifierNode;
function GetTarget: IAstNode;
function GetInitializer: IAstNode;
function GetIsBoxed: Boolean;
function GetKind: TAstNodeKind; override;
public
constructor Create(
const AIdentifier: IIdentifierNode;
AInitializer: IAstNode;
const AStaticType: IStaticType;
const AIsBoxed: Boolean
);
constructor Create(const ATarget: IAstNode; AInitializer: IAstNode; const AStaticType: IStaticType; const AIsBoxed: Boolean);
function Accept(const Visitor: IAstVisitor): TDataValue; override;
function AsVariableDeclaration: IVariableDeclarationNode; override;
end;
@@ -440,16 +430,16 @@ type
TAssignmentNode = class(TAstTypedNode, IAssignmentNode)
private
FIdentifier: IIdentifierNode;
FTarget: IAstNode;
FValue: IAstNode;
function GetIdentifier: IIdentifierNode;
function GetTarget: IAstNode;
function GetValue: IAstNode;
function GetKind: TAstNodeKind; override;
public
constructor Create(const AIdentifier: IIdentifierNode; const AValue: IAstNode; const AStaticType: IStaticType);
constructor Create(const ATarget: IAstNode; const AValue: IAstNode; const AStaticType: IStaticType);
function Accept(const Visitor: IAstVisitor): TDataValue; override;
function AsAssignment: IAssignmentNode; override;
property Identifier: IIdentifierNode read FIdentifier;
property Target: IAstNode read FTarget;
property Value: IAstNode read FValue;
end;
@@ -619,15 +609,11 @@ begin
);
end;
class function TAst.Assign(
const AIdentifier: IIdentifierNode;
const AValue: IAstNode;
const AStaticType: IStaticType = nil
): IAssignmentNode;
class function TAst.Assign(const ATarget, AValue: IAstNode; const AStaticType: IStaticType = nil): IAssignmentNode;
begin
Result :=
TAssignmentNode.Create(
AIdentifier,
ATarget,
AValue,
if AStaticType <> nil then AStaticType
else TTypes.Unknown
@@ -884,7 +870,7 @@ begin
end;
class function TAst.VarDecl(
const AIdentifier: IIdentifierNode;
const AIdentifier: IAstNode;
AInitializer: IAstNode = nil;
const AStaticType: IStaticType = nil;
const AIsBoxed: Boolean = False
@@ -1614,14 +1600,14 @@ end;
{ TVariableDeclarationNode }
constructor TVariableDeclarationNode.Create(
const AIdentifier: IIdentifierNode;
const ATarget: IAstNode;
AInitializer: IAstNode;
const AStaticType: IStaticType;
const AIsBoxed: Boolean
);
begin
inherited Create(AStaticType);
FIdentifier := AIdentifier;
FTarget := ATarget;
FInitializer := AInitializer;
FIsBoxed := AIsBoxed;
end;
@@ -1636,9 +1622,9 @@ begin
Result := Self;
end;
function TVariableDeclarationNode.GetIdentifier: IIdentifierNode;
function TVariableDeclarationNode.GetTarget: IAstNode;
begin
Result := FIdentifier;
Result := FTarget;
end;
function TVariableDeclarationNode.GetInitializer: IAstNode;
@@ -1658,10 +1644,10 @@ end;
{ TAssignmentNode }
constructor TAssignmentNode.Create(const AIdentifier: IIdentifierNode; const AValue: IAstNode; const AStaticType: IStaticType);
constructor TAssignmentNode.Create(const ATarget: IAstNode; const AValue: IAstNode; const AStaticType: IStaticType);
begin
inherited Create(AStaticType);
FIdentifier := AIdentifier;
FTarget := ATarget;
FValue := AValue;
end;
@@ -1675,9 +1661,9 @@ begin
Result := Self;
end;
function TAssignmentNode.GetIdentifier: IIdentifierNode;
function TAssignmentNode.GetTarget: IAstNode;
begin
Result := FIdentifier;
Result := FTarget;
end;
function TAssignmentNode.GetValue: IAstNode;