AST refactoring
This commit is contained in:
@@ -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}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -20,6 +20,7 @@ uses
|
||||
FMX.Controls.Presentation,
|
||||
Myc.Data.Types,
|
||||
Myc.Ast,
|
||||
Myc.Ast.Scope,
|
||||
Myc.Ast.Evaluator,
|
||||
Myc.Ast.Printer;
|
||||
|
||||
|
||||
@@ -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.
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
Reference in New Issue
Block a user