AST refactoring

This commit is contained in:
Michael Schimmel
2025-08-29 09:44:17 +02:00
parent 5593761551
commit fa9328a183
9 changed files with 524 additions and 514 deletions
+6 -409
View File
@@ -4,211 +4,10 @@ interface
uses
System.SysUtils,
System.Classes,
System.Generics.Collections,
Myc.Data.POD;
Myc.Data.POD,
Myc.Ast.Nodes;
type
// Operators are now type-safe enums
TBinaryOperator = (boAdd, boSubtract, boMultiply, boDivide, boEqual, boNotEqual, boLess, boGreater, boLessOrEqual, boGreaterOrEqual);
TUnaryOperator = (uoNegate, uoNot);
// Helper to convert operators to string
TBinaryOperatorHelper = record helper for TBinaryOperator
function ToString: string;
end;
TUnaryOperatorHelper = record helper for TUnaryOperator
function ToString: string;
end;
// Defines the kind of value stored in a TAstValue record.
TAstValueKind = (
avkUndefined, // Represents a void, null or uninitialized value.
avkScalar, // The value is a POD scalar (TScalar).
avkClosure // The value is a managed closure (IEvaluatorClosure).
);
// --- Forward Declarations to break cycles ---
IAstVisitor = interface;
IAstNode = interface;
IExpressionNode = interface;
IIdentifierNode = interface;
IConstantNode = interface;
IBinaryExpressionNode = interface;
IUnaryExpressionNode = interface;
IIfExpressionNode = interface;
ILambdaExpressionNode = interface;
IFunctionCallNode = interface;
IBlockExpressionNode = interface;
IVariableDeclarationNode = interface;
IAssignmentNode = interface; // Represents an assignment to an existing variable.
IEvaluatorClosure = interface;
TExecutionScope = class;
// --- Concrete Type Definitions ---
// Represents a closure value for the TAstValue-based evaluator.
IEvaluatorClosure = interface(IInterface)
{$region 'private'}
function GetBody: IExpressionNode;
function GetParameters: TArray<IIdentifierNode>;
function GetClosureScope: TExecutionScope;
{$endregion}
property Body: IExpressionNode read GetBody;
property Parameters: TArray<IIdentifierNode> read GetParameters;
property ClosureScope: TExecutionScope read GetClosureScope;
end;
// A universal value container for the AST evaluator.
TAstValue = record
private
FKind: TAstValueKind;
FScalar: TScalar;
FClosure: IEvaluatorClosure;
function GetKind: TAstValueKind; inline;
function GetIsScalar: Boolean; inline;
function GetIsClosure: Boolean; inline;
function GetIsUndefined: Boolean; inline;
public
// Managed record operators to handle the lifetime of FObject.
class operator Initialize(out Dest: TAstValue);
// Factory methods for clean creation.
class function Undefined: TAstValue; static;
class function FromScalar(const AValue: TScalar): TAstValue; static;
class function FromClosure(const AValue: IEvaluatorClosure): TAstValue; static;
// Accessors for the stored values.
function AsScalar: TScalar;
function AsClosure: IEvaluatorClosure;
function ToString: String;
// Properties for convenient access and type checking.
property Kind: TAstValueKind read GetKind;
property IsScalar: Boolean read GetIsScalar;
property IsClosure: Boolean read GetIsClosure;
property IsUndefined: Boolean read GetIsUndefined;
end;
// The visitor pattern interface for traversing the AST.
IAstVisitor = interface
['{A58B0A8E-F438-4217-A964-6E35624A9A4A}']
function VisitConstant(const Node: IConstantNode): TAstValue;
function VisitIdentifier(const Node: IIdentifierNode): TAstValue;
function VisitBinaryExpression(const Node: IBinaryExpressionNode): TAstValue;
function VisitUnaryExpression(const Node: IUnaryExpressionNode): TAstValue;
function VisitIfExpression(const Node: IIfExpressionNode): TAstValue;
function VisitLambdaExpression(const Node: ILambdaExpressionNode): TAstValue;
function VisitFunctionCall(const Node: IFunctionCallNode): TAstValue;
function VisitBlockExpression(const Node: IBlockExpressionNode): TAstValue;
function VisitVariableDeclaration(const Node: IVariableDeclarationNode): TAstValue;
function VisitAssignment(const Node: IAssignmentNode): TAstValue;
end;
// --- AST Node Interfaces (now using TAstValue) ---
// Base interface for all AST nodes.
IAstNode = interface(IInterface)
function Accept(const Visitor: IAstVisitor): TAstValue;
end;
// Abstract interface for all nodes that evaluate to a value.
IExpressionNode = interface(IAstNode)
end;
IConstantNode = interface(IExpressionNode)
{$region 'private'}
function GetValue: TScalar;
{$endregion}
property Value: TScalar read GetValue;
end;
IIdentifierNode = interface(IExpressionNode)
{$region 'private'}
function GetName: string;
{$endregion}
property Name: string read GetName;
end;
IBinaryExpressionNode = interface(IExpressionNode)
{$region 'private'}
function GetLeft: IExpressionNode;
function GetOperator: TBinaryOperator;
function GetRight: IExpressionNode;
{$endregion}
property Left: IExpressionNode read GetLeft;
property Operator: TBinaryOperator read GetOperator;
property Right: IExpressionNode read GetRight;
end;
IUnaryExpressionNode = interface(IExpressionNode)
{$region 'private'}
function GetOperator: TUnaryOperator;
function GetRight: IExpressionNode;
{$endregion}
property Operator: TUnaryOperator read GetOperator;
property Right: IExpressionNode read GetRight;
end;
IIfExpressionNode = interface(IExpressionNode)
{$region 'private'}
function GetCondition: IExpressionNode;
function GetThenBranch: IExpressionNode;
function GetElseBranch: IExpressionNode;
{$endregion}
property Condition: IExpressionNode read GetCondition;
property ThenBranch: IExpressionNode read GetThenBranch;
property ElseBranch: IExpressionNode read GetElseBranch;
end;
ILambdaExpressionNode = interface(IExpressionNode)
{$region 'private'}
function GetParameters: TArray<IIdentifierNode>;
function GetBody: IExpressionNode;
{$endregion}
property Parameters: TArray<IIdentifierNode> read GetParameters;
property Body: IExpressionNode read GetBody;
end;
IFunctionCallNode = interface(IExpressionNode)
{$region 'private'}
function GetCallee: IExpressionNode;
function GetArguments: TList<IExpressionNode>;
{$endregion}
property Callee: IExpressionNode read GetCallee;
property Arguments: TList<IExpressionNode> read GetArguments;
end;
// A block is an expression that returns the value of its last expression.
IBlockExpressionNode = interface(IExpressionNode)
{$region 'private'}
function GetExpressions: TList<IExpressionNode>;
{$endregion}
property Expressions: TList<IExpressionNode> read GetExpressions;
end;
// A variable declaration is an expression that returns an undefined value.
IVariableDeclarationNode = interface(IExpressionNode)
{$region 'private'}
function GetIdentifier: IIdentifierNode;
function GetInitializer: IExpressionNode; // Can be nil
{$endregion}
property Identifier: IIdentifierNode read GetIdentifier;
property Initializer: IExpressionNode read GetInitializer;
end;
// An assignment expression that returns the assigned value.
IAssignmentNode = interface(IExpressionNode)
{$region 'private'}
function GetIdentifier: IIdentifierNode;
function GetValue: IExpressionNode;
{$endregion}
property Identifier: IIdentifierNode read GetIdentifier;
property Value: IExpressionNode read GetValue;
end;
// Record acting as a namespace for the factory functions.
TAst = record
class function Constant(AValue: TScalar): IConstantNode; static;
@@ -230,26 +29,12 @@ type
class function Assign(const AIdentifier: IIdentifierNode; const AValue: IExpressionNode): IAssignmentNode; static;
end;
// Manages the scope of execution, holding variables and their values.
TExecutionScope = class
private
FParent: TExecutionScope;
FVariables: TDictionary<string, TAstValue>;
procedure DumpScope(const ABuilder: TStringBuilder; AIndent: Integer);
public
constructor Create(AParent: TExecutionScope = nil);
destructor Destroy; override;
procedure Clear;
function FindValue(const Name: string; out Value: TAstValue): Boolean;
// Sets or updates a value in the current scope. Used for declarations.
procedure SetValue(const Name: string; const Value: TAstValue);
// Finds an existing variable in the scope chain and updates its value. Used for assignments.
procedure AssignValue(const Name: string; const Value: TAstValue);
function Dump: string;
end;
implementation
uses
System.Classes,
System.Generics.Collections;
type
{ TAstNode }
// Common base class for AST nodes to reduce boilerplate.
@@ -378,111 +163,6 @@ type
function Accept(const Visitor: IAstVisitor): TAstValue; override;
end;
{ TAstValue }
class operator TAstValue.Initialize(out Dest: TAstValue);
begin
// Ensures the record is in a clean state when created.
Dest.FKind := avkUndefined;
end;
function TAstValue.AsClosure: IEvaluatorClosure;
begin
if (FKind <> avkClosure) then
raise EInvalidCast.Create('Cannot read value as a Closure.');
Result := FClosure;
end;
function TAstValue.AsScalar: TScalar;
begin
if (FKind <> avkScalar) then
raise EInvalidCast.Create('Cannot read value as a Scalar.');
Result := FScalar;
end;
class function TAstValue.FromClosure(const AValue: IEvaluatorClosure): TAstValue;
begin
Result.FKind := avkClosure;
Result.FClosure := AValue;
Result.FScalar := Default(TScalar);
end;
class function TAstValue.FromScalar(const AValue: TScalar): TAstValue;
begin
Result.FKind := avkScalar;
Result.FScalar := AValue;
Result.FClosure := nil;
end;
function TAstValue.GetIsClosure: Boolean;
begin
Result := FKind = avkClosure;
end;
function TAstValue.GetIsScalar: Boolean;
begin
Result := FKind = avkScalar;
end;
function TAstValue.GetIsUndefined: Boolean;
begin
Result := FKind = avkUndefined;
end;
function TAstValue.GetKind: TAstValueKind;
begin
Result := FKind;
end;
function TAstValue.ToString: String;
begin
case FKind of
avkScalar: Result := FScalar.ToString;
avkClosure: Result := '<closure>';
avkUndefined: Result := '<void>';
else
Result := '[Unknown AstValue]';
end;
end;
class function TAstValue.Undefined: TAstValue;
begin
// Returns a default-initialized record, which is avkUndefined.
Result := Default(TAstValue);
end;
{ TBinaryOperatorHelper }
function TBinaryOperatorHelper.ToString: string;
begin
case Self of
boAdd: Result := '+';
boSubtract: Result := '-';
boMultiply: Result := '*';
boDivide: Result := '/';
boEqual: Result := '==';
boNotEqual: Result := '!=';
boLess: Result := '<';
boGreater: Result := '>';
boLessOrEqual: Result := '<=';
boGreaterOrEqual: Result := '>=';
else
Result := '?';
end;
end;
{ TUnaryOperatorHelper }
function TUnaryOperatorHelper.ToString: string;
begin
case Self of
uoNegate: Result := '-';
uoNot: Result := 'not';
else
Result := '?';
end;
end;
{ TConstantNode }
constructor TConstantNode.Create(AValue: TScalar);
@@ -789,87 +469,4 @@ begin
Result := TVariableDeclarationNode.Create(AIdentifier, AInitializer);
end;
{ TExecutionScope }
constructor TExecutionScope.Create(AParent: TExecutionScope);
begin
inherited Create;
FParent := AParent;
FVariables := TDictionary<string, TAstValue>.Create;
end;
destructor TExecutionScope.Destroy;
begin
FVariables.Free;
inherited Destroy;
end;
procedure TExecutionScope.AssignValue(const Name: string; const Value: TAstValue);
begin
if FVariables.ContainsKey(Name) then
FVariables.AddOrSetValue(Name, Value)
else if Assigned(FParent) then
FParent.AssignValue(Name, Value)
else
raise Exception.CreateFmt('Cannot assign to undeclared variable "%s".', [Name]);
end;
procedure TExecutionScope.Clear;
begin
FVariables.Clear;
if Assigned(FParent) then
FParent.Clear;
end;
procedure TExecutionScope.DumpScope(const ABuilder: TStringBuilder; AIndent: Integer);
var
pair: TPair<string, TAstValue>;
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.ToString]));
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: TAstValue): 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: TAstValue);
begin
FVariables.AddOrSetValue(Name, Value);
end;
end.