AST refactoring

This commit is contained in:
Michael Schimmel
2025-08-28 15:14:08 +02:00
parent 6fcf8c26c8
commit 9419f5cd03
6 changed files with 235 additions and 175 deletions
+4 -1
View File
@@ -5,7 +5,10 @@ uses
FMX.Forms,
MainForm in 'MainForm.pas' {Form1},
Myc.Ast.Evaluator in '..\Src\AST\Myc.Ast.Evaluator.pas',
Myc.Ast.Printer in '..\Src\AST\Myc.Ast.Printer.pas';
Myc.Ast.Printer in '..\Src\AST\Myc.Ast.Printer.pas',
Myc.Ast.Types in '..\Src\AST\Myc.Ast.Types.pas',
Myc.Ast.Scope in '..\Src\AST\Myc.Ast.Scope.pas',
Myc.Ast.Closure in '..\Src\AST\Myc.Ast.Closure.pas';
{$R *.res}
+4 -1
View File
@@ -70,7 +70,7 @@
<Icon_MainIcon>$(BDS)\bin\delphi_PROJECTICON.ico</Icon_MainIcon>
<Icns_MainIcns>$(BDS)\bin\delphi_PROJECTICNS.icns</Icns_MainIcns>
<SanitizedProjectName>ASTPlayground</SanitizedProjectName>
<DCC_UnitSearchPath>T:\Myc\Src\Data;T:\Myc\Src\AST;$(DCC_UnitSearchPath)</DCC_UnitSearchPath>
<DCC_UnitSearchPath>T:\Myc\Src\Data;T:\Myc\Src\AST;T:\Myc\Src;$(DCC_UnitSearchPath)</DCC_UnitSearchPath>
<VerInfo_Locale>1031</VerInfo_Locale>
<VerInfo_Keys>CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=</VerInfo_Keys>
</PropertyGroup>
@@ -137,6 +137,9 @@
</DCCReference>
<DCCReference Include="..\Src\AST\Myc.Ast.Evaluator.pas"/>
<DCCReference Include="..\Src\AST\Myc.Ast.Printer.pas"/>
<DCCReference Include="..\Src\AST\Myc.Ast.Types.pas"/>
<DCCReference Include="..\Src\AST\Myc.Ast.Scope.pas"/>
<DCCReference Include="..\Src\AST\Myc.Ast.Closure.pas"/>
<BuildConfiguration Include="Base">
<Key>Base</Key>
</BuildConfiguration>
+1
View File
@@ -20,6 +20,7 @@ uses
FMX.Controls.Presentation,
Myc.Data.Types,
Myc.Ast,
Myc.Ast.Scope,
Myc.Ast.Evaluator,
Myc.Ast.Printer;
+124
View File
@@ -0,0 +1,124 @@
unit Myc.Ast.Closure;
interface
uses
System.Classes,
System.Generics.Collections,
Myc.Data.Types,
Myc.Ast,
Myc.Ast.Scope;
type
// A closure is a specific kind of method value that is defined by the evaluator.
// It holds the AST body and its captured scope.
IDataClosureValue = interface(IDataMethodValue)
['{2704586B-E4BD-47AA-B05D-FD441AE6D818}']
{$region 'private'}
function GetBody: IExpressionNode;
function GetParameters: TList<IIdentifierNode>;
function GetClosureScope: TExecutionScope;
{$endregion}
property Body: IExpressionNode read GetBody;
property Parameters: TList<IIdentifierNode> read GetParameters;
property ClosureScope: TExecutionScope read GetClosureScope;
end;
// Factory function to create a new closure value.
function CreateDataClosureValue(
AMethodType: IDataMethodType;
ABody: IExpressionNode;
AParameters: TList<IIdentifierNode>;
AClosureScope: TExecutionScope
): IDataClosureValue;
implementation
uses
System.SysUtils;
type
{ TDataClosureValueImpl }
// Concrete implementation of the IDataClosureValue interface.
TDataClosureValueImpl = class(TInterfacedObject, IDataValue, IDataClosureValue)
private
FMethodType: IDataMethodType;
FBody: IExpressionNode;
FParameters: TList<IIdentifierNode>;
FClosureScope: TExecutionScope;
// IDataValue
function GetDataType: IDataType;
function GetAsString: string;
// IDataMethodValue
function GetValue: TDataMethodProc;
// IDataClosureValue
function GetBody: IExpressionNode;
function GetParameters: TList<IIdentifierNode>;
function GetClosureScope: TExecutionScope;
public
constructor Create(
AMethodType: IDataMethodType;
ABody: IExpressionNode;
AParameters: TList<IIdentifierNode>;
ACClosureScope: TExecutionScope
);
end;
function CreateDataClosureValue(
AMethodType: IDataMethodType;
ABody: IExpressionNode;
AParameters: TList<IIdentifierNode>;
AClosureScope: TExecutionScope
): IDataClosureValue;
begin
Result := TDataClosureValueImpl.Create(AMethodType, ABody, AParameters, AClosureScope);
end;
{ TDataClosureValueImpl }
constructor TDataClosureValueImpl.Create(
AMethodType: IDataMethodType;
ABody: IExpressionNode;
AParameters: TList<IIdentifierNode>;
ACClosureScope: TExecutionScope
);
begin
inherited Create;
FMethodType := AMethodType;
FBody := ABody;
FParameters := AParameters; // Note: We are taking ownership of the list reference
FClosureScope := ACClosureScope;
end;
function TDataClosureValueImpl.GetAsString: string;
begin
Result := '<CLOSURE>';
end;
function TDataClosureValueImpl.GetBody: IExpressionNode;
begin
Result := FBody;
end;
function TDataClosureValueImpl.GetClosureScope: TExecutionScope;
begin
Result := FClosureScope;
end;
function TDataClosureValueImpl.GetDataType: IDataType;
begin
Result := FMethodType;
end;
function TDataClosureValueImpl.GetParameters: TList<IIdentifierNode>;
begin
Result := FParameters;
end;
function TDataClosureValueImpl.GetValue: TDataMethodProc;
begin
// This direct execution path is no longer used for closures.
raise ENotSupportedException.Create('Cannot get raw method proc from a closure object.');
end;
end.
+5 -173
View File
@@ -1,5 +1,3 @@
// https://g.co/gemini/share/f8965e72f968
unit Myc.Ast.Evaluator;
interface
@@ -9,39 +7,11 @@ uses
System.Classes, // For TStrings
System.Generics.Collections,
Myc.Data.Types,
Myc.Ast;
Myc.Ast,
Myc.Ast.Scope,
Myc.Ast.Closure;
type
// Manages the scope of execution, holding variables and their values.
TExecutionScope = class
private
FParent: TExecutionScope;
FVariables: TDictionary<string, IDataValue>;
// Added for recursive dumping
procedure DumpScope(const ABuilder: TStringBuilder; AIndent: Integer);
public
constructor Create(AParent: TExecutionScope = nil);
destructor Destroy; override;
function FindValue(const Name: string; out Value: IDataValue): Boolean;
procedure SetValue(const Name: string; const Value: IDataValue);
// Dumps the content of this scope and all parent scopes to a string.
function Dump: string;
end;
// A closure is a specific kind of method value that is defined by the evaluator.
// It holds the AST body and its captured scope.
IDataClosureValue = interface(IDataMethodValue)
['{2704586B-E4BD-47AA-B05D-FD441AE6D818}']
{$region 'private'}
function GetBody: IExpressionNode;
function GetParameters: TList<IIdentifierNode>;
function GetClosureScope: TExecutionScope;
{$endregion}
property Body: IExpressionNode read GetBody;
property Parameters: TList<IIdentifierNode> read GetParameters;
property ClosureScope: TExecutionScope read GetClosureScope;
end;
// TEvaluatorVisitor is the base implementation for evaluating an AST.
TEvaluatorVisitor = class(TInterfacedObject, IAstVisitor)
protected // Changed to protected to be accessible by descendants
@@ -91,145 +61,6 @@ type
implementation
type
{ TDataClosureValueImpl }
// Concrete implementation of the IDataClosureValue interface.
TDataClosureValueImpl = class(TInterfacedObject, IDataValue, IDataClosureValue)
private
FMethodType: IDataMethodType;
FBody: IExpressionNode;
FParameters: TList<IIdentifierNode>;
FClosureScope: TExecutionScope;
// IDataValue
function GetDataType: IDataType;
function GetAsString: string;
// IDataMethodValue
function GetValue: TDataMethodProc;
// IDataClosureValue
function GetBody: IExpressionNode;
function GetParameters: TList<IIdentifierNode>;
function GetClosureScope: TExecutionScope;
public
constructor Create(
AMethodType: IDataMethodType;
ABody: IExpressionNode;
AParameters: TList<IIdentifierNode>;
ACClosureScope: TExecutionScope
);
end;
constructor TDataClosureValueImpl.Create(
AMethodType: IDataMethodType;
ABody: IExpressionNode;
AParameters: TList<IIdentifierNode>;
ACClosureScope: TExecutionScope
);
begin
inherited Create;
FMethodType := AMethodType;
FBody := ABody;
FParameters := AParameters; // Note: We are taking ownership of the list reference
FClosureScope := ACClosureScope;
end;
function TDataClosureValueImpl.GetAsString: string;
begin
Result := '<CLOSURE>';
end;
function TDataClosureValueImpl.GetBody: IExpressionNode;
begin
Result := FBody;
end;
function TDataClosureValueImpl.GetClosureScope: TExecutionScope;
begin
Result := FClosureScope;
end;
function TDataClosureValueImpl.GetDataType: IDataType;
begin
Result := FMethodType;
end;
function TDataClosureValueImpl.GetParameters: TList<IIdentifierNode>;
begin
Result := FParameters;
end;
function TDataClosureValueImpl.GetValue: TDataMethodProc;
begin
// This direct execution path is no longer used for closures.
raise ENotSupportedException.Create('Cannot get raw method proc from a closure object.');
end;
{ TExecutionScope }
constructor TExecutionScope.Create(AParent: TExecutionScope = nil);
begin
inherited Create;
FParent := AParent;
FVariables := TDictionary<string, IDataValue>.Create;
end;
destructor TExecutionScope.Destroy;
begin
FVariables.Free;
inherited Destroy;
end;
procedure TExecutionScope.DumpScope(const ABuilder: TStringBuilder; AIndent: Integer);
var
pair: TPair<string, IDataValue>;
indentStr: string;
begin
indentStr := ''.PadLeft(AIndent);
if FVariables.Count > 0 then
begin
for pair in FVariables do
ABuilder.AppendLine(indentStr + Format(' %s: %s', [pair.Key, pair.Value.AsString]));
end
else
begin
ABuilder.AppendLine(indentStr + ' (empty)');
end;
if Assigned(FParent) then
begin
ABuilder.AppendLine(indentStr + '[Parent Scope]');
FParent.DumpScope(ABuilder, AIndent + 2);
end;
end;
function TExecutionScope.Dump: string;
var
builder: TStringBuilder;
begin
builder := TStringBuilder.Create;
try
builder.AppendLine('[Current Scope]');
DumpScope(builder, 0);
Result := builder.ToString.TrimRight;
finally
builder.Free;
end;
end;
function TExecutionScope.FindValue(const Name: string; out Value: IDataValue): Boolean;
begin
Result := FVariables.TryGetValue(Name, Value);
if not Result and Assigned(FParent) then
begin
Result := FParent.FindValue(Name, Value);
end;
end;
procedure TExecutionScope.SetValue(const Name: string; const Value: IDataValue);
begin
// This defines a variable in the current scope. It can shadow a parent variable.
FVariables.AddOrSetValue(Name, Value);
end;
{ TEvaluatorVisitor }
constructor TEvaluatorVisitor.Create(AScope: TExecutionScope);
@@ -252,7 +83,8 @@ begin
// Instead of creating an anonymous method, we now create a data object
// that holds all information required to execute the lambda later.
methodType := TDataType.MethodOf(TDataType.Ordinal, TDataType.Ordinal); // TODO: Infer this
Result := TDataClosureValueImpl.Create(methodType, Node.Body, Node.Parameters, FScope);
// Use the factory function from the Myc.Ast.Closure unit.
Result := CreateDataClosureValue(methodType, Node.Body, Node.Parameters, FScope);
end;
function TEvaluatorVisitor.VisitFunctionCall(const Node: IFunctionCallNode): IDataValue;
+97
View File
@@ -0,0 +1,97 @@
unit Myc.Ast.Scope;
interface
uses
System.SysUtils,
System.Classes,
System.Generics.Collections,
Myc.Data.Types;
type
// Manages the scope of execution, holding variables and their values.
TExecutionScope = class
private
FParent: TExecutionScope;
FVariables: TDictionary<string, IDataValue>;
// Added for recursive dumping
procedure DumpScope(const ABuilder: TStringBuilder; AIndent: Integer);
public
constructor Create(AParent: TExecutionScope = nil);
destructor Destroy; override;
function FindValue(const Name: string; out Value: IDataValue): Boolean;
procedure SetValue(const Name: string; const Value: IDataValue);
// Dumps the content of this scope and all parent scopes to a string.
function Dump: string;
end;
implementation
{ TExecutionScope }
constructor TExecutionScope.Create(AParent: TExecutionScope = nil);
begin
inherited Create;
FParent := AParent;
FVariables := TDictionary<string, IDataValue>.Create;
end;
destructor TExecutionScope.Destroy;
begin
FVariables.Free;
inherited Destroy;
end;
procedure TExecutionScope.DumpScope(const ABuilder: TStringBuilder; AIndent: Integer);
var
pair: TPair<string, IDataValue>;
indentStr: string;
begin
indentStr := ''.PadLeft(AIndent);
if (FVariables.Count > 0) then
begin
for pair in FVariables do
ABuilder.AppendLine(indentStr + Format(' %s: %s', [pair.Key, pair.Value.AsString]));
end
else
begin
ABuilder.AppendLine(indentStr + ' (empty)');
end;
if Assigned(FParent) then
begin
ABuilder.AppendLine(indentStr + '[Parent Scope]');
FParent.DumpScope(ABuilder, AIndent + 2);
end;
end;
function TExecutionScope.Dump: string;
var
builder: TStringBuilder;
begin
builder := TStringBuilder.Create;
try
builder.AppendLine('[Current Scope]');
DumpScope(builder, 0);
Result := builder.ToString.TrimRight;
finally
builder.Free;
end;
end;
function TExecutionScope.FindValue(const Name: string; out Value: IDataValue): Boolean;
begin
Result := FVariables.TryGetValue(Name, Value);
if not Result and Assigned(FParent) then
begin
Result := FParent.FindValue(Name, Value);
end;
end;
procedure TExecutionScope.SetValue(const Name: string; const Value: IDataValue);
begin
// This defines a variable in the current scope. It can shadow a parent variable.
FVariables.AddOrSetValue(Name, Value);
end;
end.