diff --git a/ASTPlayground/ASTPlayground.dpr b/ASTPlayground/ASTPlayground.dpr index 347f1c1..f118608 100644 --- a/ASTPlayground/ASTPlayground.dpr +++ b/ASTPlayground/ASTPlayground.dpr @@ -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} diff --git a/ASTPlayground/ASTPlayground.dproj b/ASTPlayground/ASTPlayground.dproj index 41ba722..1eff804 100644 --- a/ASTPlayground/ASTPlayground.dproj +++ b/ASTPlayground/ASTPlayground.dproj @@ -70,7 +70,7 @@ $(BDS)\bin\delphi_PROJECTICON.ico $(BDS)\bin\delphi_PROJECTICNS.icns ASTPlayground - T:\Myc\Src\Data;T:\Myc\Src\AST;$(DCC_UnitSearchPath) + T:\Myc\Src\Data;T:\Myc\Src\AST;T:\Myc\Src;$(DCC_UnitSearchPath) 1031 CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= @@ -137,6 +137,9 @@ + + + Base diff --git a/ASTPlayground/MainForm.pas b/ASTPlayground/MainForm.pas index b0f3b52..6d19a6b 100644 --- a/ASTPlayground/MainForm.pas +++ b/ASTPlayground/MainForm.pas @@ -20,6 +20,7 @@ uses FMX.Controls.Presentation, Myc.Data.Types, Myc.Ast, + Myc.Ast.Scope, Myc.Ast.Evaluator, Myc.Ast.Printer; diff --git a/Src/AST/Myc.Ast.Closure.pas b/Src/AST/Myc.Ast.Closure.pas new file mode 100644 index 0000000..b60b947 --- /dev/null +++ b/Src/AST/Myc.Ast.Closure.pas @@ -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; + function GetClosureScope: TExecutionScope; + {$endregion} + property Body: IExpressionNode read GetBody; + property Parameters: TList read GetParameters; + property ClosureScope: TExecutionScope read GetClosureScope; + end; + +// Factory function to create a new closure value. +function CreateDataClosureValue( + AMethodType: IDataMethodType; + ABody: IExpressionNode; + AParameters: TList; + 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; + FClosureScope: TExecutionScope; + // IDataValue + function GetDataType: IDataType; + function GetAsString: string; + // IDataMethodValue + function GetValue: TDataMethodProc; + // IDataClosureValue + function GetBody: IExpressionNode; + function GetParameters: TList; + function GetClosureScope: TExecutionScope; + public + constructor Create( + AMethodType: IDataMethodType; + ABody: IExpressionNode; + AParameters: TList; + ACClosureScope: TExecutionScope + ); + end; + +function CreateDataClosureValue( + AMethodType: IDataMethodType; + ABody: IExpressionNode; + AParameters: TList; + AClosureScope: TExecutionScope +): IDataClosureValue; +begin + Result := TDataClosureValueImpl.Create(AMethodType, ABody, AParameters, AClosureScope); +end; + +{ TDataClosureValueImpl } + +constructor TDataClosureValueImpl.Create( + AMethodType: IDataMethodType; + ABody: IExpressionNode; + AParameters: TList; + 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 := ''; +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; +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. diff --git a/Src/AST/Myc.Ast.Evaluator.pas b/Src/AST/Myc.Ast.Evaluator.pas index 03c55ec..87c8858 100644 --- a/Src/AST/Myc.Ast.Evaluator.pas +++ b/Src/AST/Myc.Ast.Evaluator.pas @@ -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; - // 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; - function GetClosureScope: TExecutionScope; - {$endregion} - property Body: IExpressionNode read GetBody; - property Parameters: TList 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; - FClosureScope: TExecutionScope; - // IDataValue - function GetDataType: IDataType; - function GetAsString: string; - // IDataMethodValue - function GetValue: TDataMethodProc; - // IDataClosureValue - function GetBody: IExpressionNode; - function GetParameters: TList; - function GetClosureScope: TExecutionScope; - public - constructor Create( - AMethodType: IDataMethodType; - ABody: IExpressionNode; - AParameters: TList; - ACClosureScope: TExecutionScope - ); - end; - -constructor TDataClosureValueImpl.Create( - AMethodType: IDataMethodType; - ABody: IExpressionNode; - AParameters: TList; - 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 := ''; -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; -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.Create; -end; - -destructor TExecutionScope.Destroy; -begin - FVariables.Free; - inherited Destroy; -end; - -procedure TExecutionScope.DumpScope(const ABuilder: TStringBuilder; AIndent: Integer); -var - pair: TPair; - 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; diff --git a/Src/AST/Myc.Ast.Scope.pas b/Src/AST/Myc.Ast.Scope.pas new file mode 100644 index 0000000..b06ac59 --- /dev/null +++ b/Src/AST/Myc.Ast.Scope.pas @@ -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; + // 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.Create; +end; + +destructor TExecutionScope.Destroy; +begin + FVariables.Free; + inherited Destroy; +end; + +procedure TExecutionScope.DumpScope(const ABuilder: TStringBuilder; AIndent: Integer); +var + pair: TPair; + 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.