Ast environment refactoring

This commit is contained in:
Michael Schimmel
2025-11-08 15:57:12 +01:00
parent c16f47c6d5
commit 93dc19497c
5 changed files with 109 additions and 166 deletions
+15 -7
View File
@@ -48,19 +48,27 @@ uses
constructor TAstLowerer.Create;
var
op: TScalar.TBinaryOp;
uOp: TScalar.TUnaryOp; // Added for unary
uOp: TScalar.TUnaryOp;
begin
inherited Create;
// Operator folding maps
FBinaryOperators := TDictionary<string, TScalar.TBinaryOp>.Create;
for op := Low(TScalar.TBinaryOp) to High(TScalar.TBinaryOp) do
FBinaryOperators.Add(op.ToString, op);
FBinaryOperators.Add('+', TScalar.TBinaryOp.Add);
FBinaryOperators.Add('-', TScalar.TBinaryOp.Subtract);
FBinaryOperators.Add('*', TScalar.TBinaryOp.Multiply);
FBinaryOperators.Add('/', TScalar.TBinaryOp.Divide);
FBinaryOperators.Add('=', TScalar.TBinaryOp.Equal);
FBinaryOperators.Add('<>', TScalar.TBinaryOp.NotEqual);
FBinaryOperators.Add('<', TScalar.TBinaryOp.Less);
FBinaryOperators.Add('<=', TScalar.TBinaryOp.LessOrEqual);
FBinaryOperators.Add('>', TScalar.TBinaryOp.Greater);
FBinaryOperators.Add('>=', TScalar.TBinaryOp.GreaterOrEqual);
FUnaryOperators := TDictionary<string, TScalar.TUnaryOp>.Create;
for uOp := Low(TScalar.TUnaryOp) to High(TScalar.TUnaryOp) do // Changed
FUnaryOperators.Add(uOp.ToString, uOp);
// Note: '-' is handled as a special case in VisitFunctionCall
FUnaryOperators.Add('not', TScalar.TUnaryOp.Not);
end;
destructor TAstLowerer.Destroy;
@@ -95,7 +103,7 @@ begin
if Node.Callee.Kind = akIdentifier then
begin
calleeIdentifier := Node.Callee.AsIdentifier;
nodeType := Node.StaticType;
nodeType := Node.StaticType; // Get type from (un-lowered) node
if (Length(Node.Arguments) = 2) then
begin
-1
View File
@@ -371,7 +371,6 @@ begin
boundSubAst: IAstNode;
begin
// This is the compile-time evaluation (Binder + Evaluator)
// Es wird der 'expansionScope' verwendet, der die AST-Argumente enthaelt
boundSubAst := TAstBinder.Bind(expansionScope.CreateDescriptor, ANodeToEvaluate, subDescriptor);
// Create eval scope from the new descriptor, parented to the expansion scope
+61 -111
View File
@@ -15,7 +15,6 @@ type
IMacroRegistry = interface; // Forward
IEnvironment = interface; // Forward
IExecutionStrategy = interface; // Forward
IExecutable = interface; // Forward
// Defines the Strategy for creating an Evaluator.
IExecutionStrategy = interface
@@ -34,20 +33,6 @@ type
property Parent: IMacroRegistry read GetParent;
end;
// Represents a compiled, ready-to-run script.
// This encapsulates the compiled AST and its internal layout (Descriptor).
IExecutable = interface
{$region 'private'}
function GetCompiledAst: IAstNode;
function GetDescriptor: IScopeDescriptor;
{$endregion}
// The final, compiled AST (for debugging, serialization, etc.)
property CompiledAst: IAstNode read GetCompiledAst;
// The internal layout (hidden from TForm1, used by IEnvironment.Execute)
property Descriptor: IScopeDescriptor read GetDescriptor;
end;
// Defines the central environment for compilation and execution.
IEnvironment = interface
{$region 'private'}
@@ -57,11 +42,7 @@ type
procedure SetExecutionStrategy(const AStrategy: IExecutionStrategy);
// Compiles source AST into a runnable script.
function Compile(const ANode: IAstNode): IExecutable; // Changed return type
// Executes a previously compiled script.
function Execute(const AScript: IExecutable): TDataValue;
function Compile(const ANode: IAstNode; const Params: TArray<IIdentifierNode> = []): TDataValue.TFunc;
function CreateEnvironment: IEnvironment;
@@ -87,10 +68,8 @@ type
procedure SetStandardMode;
procedure SetDebugMode(ALog: TStrings; AShowScope: Boolean);
function Compile(const ANode: IAstNode): IExecutable;
function Execute(const AScript: IExecutable): TDataValue;
function CompileAndExecute(const AScript: IAstNode): TDataValue;
function Run(const ANode: IAstNode; const Params: TArray<IIdentifierNode> = []; const Args: TArray<TDataValue> = []): TDataValue;
function Compile(const ANode: IAstNode; const Params: TArray<IIdentifierNode> = []): TDataValue.TFunc;
procedure Define(const Name: String; const AScript: IAstNode);
@@ -141,17 +120,6 @@ type
function CreateChildRegistry: IMacroRegistry;
end;
{ TExecutable }
TExecutable = class(TInterfacedObject, IExecutable)
private
FCompiledAst: IAstNode;
FDescriptor: IScopeDescriptor;
function GetCompiledAst: IAstNode;
function GetDescriptor: IScopeDescriptor;
public
constructor Create(ACompiledAst: IAstNode; ADescriptor: IScopeDescriptor);
end;
{ TEnvironment }
TEnvironment = class(TInterfacedObject, IEnvironment)
private
@@ -172,8 +140,8 @@ type
procedure SetExecutionStrategy(const AStrategy: IExecutionStrategy);
function Compile(const ANode: IAstNode): IExecutable;
function Execute(const AScript: IExecutable): TDataValue;
function Compile(const ANode: IAstNode; out Descriptor: IScopeDescriptor): IAstNode; overload;
function Compile(const ANode: IAstNode; const Params: TArray<IIdentifierNode> = []): TDataValue.TFunc; overload;
end;
// --- Factory Implementations ---
@@ -211,14 +179,9 @@ begin
Result := A.FEnvironment;
end;
function TAstEnvironment.Compile(const ANode: IAstNode): IExecutable;
function TAstEnvironment.Compile(const ANode: IAstNode; const Params: TArray<IIdentifierNode> = []): TDataValue.TFunc;
begin
Result := FEnvironment.Compile(ANode);
end;
function TAstEnvironment.CompileAndExecute(const AScript: IAstNode): TDataValue;
begin
Result := Execute(Compile(AScript));
Result := FEnvironment.Compile(ANode, Params);
end;
function TAstEnvironment.CreateEnvironment: TAstEnvironment;
@@ -228,12 +191,8 @@ end;
procedure TAstEnvironment.Define(const Name: String; const AScript: IAstNode);
begin
RootScope.Define(Name, CompileAndExecute(AScript));
end;
function TAstEnvironment.Execute(const AScript: IExecutable): TDataValue;
begin
Result := FEnvironment.Execute(AScript);
var exec := Compile(AScript);
RootScope.Define(Name, exec([]));
end;
function TAstEnvironment.GetRootScope: IExecutionScope;
@@ -246,6 +205,16 @@ begin
Result := FEnvironment.GetMacroRegistry;
end;
function TAstEnvironment.Run(
const ANode: IAstNode;
const Params: TArray<IIdentifierNode> = [];
const Args: TArray<TDataValue> = []
): TDataValue;
begin
var exec := Compile(ANode, Params);
Result := exec(Args);
end;
{ TStandardExecutionStrategy }
function TStandardExecutionStrategy.CreateVisitor(const AScope: IExecutionScope): IEvaluatorVisitor;
@@ -311,25 +280,6 @@ begin
Result := TMacroRegistryImpl.Create(Self);
end;
{ TExecutable }
constructor TExecutable.Create(ACompiledAst: IAstNode; ADescriptor: IScopeDescriptor);
begin
inherited Create;
FCompiledAst := ACompiledAst;
FDescriptor := ADescriptor;
end;
function TExecutable.GetCompiledAst: IAstNode;
begin
Result := FCompiledAst;
end;
function TExecutable.GetDescriptor: IScopeDescriptor;
begin
Result := FDescriptor;
end;
{ TEnvironment }
constructor TEnvironment.Create(
@@ -364,56 +314,56 @@ begin
FExecutionStrategy := AStrategy;
end;
function TEnvironment.Compile(const ANode: IAstNode): IExecutable;
var
expandedAst, boundAst, typedAst, loweredAst: IAstNode;
descriptor: IScopeDescriptor;
begin
// Step 1: Expand macros
expandedAst :=
TMacroExpander.ExpandMacros(
FMacroRegistry,
FRootScope,
ANode,
function(const Scope: IExecutionScope): IEvaluatorVisitor begin Result := FExecutionStrategy.CreateVisitor(Scope); end
);
// Step 2: Bind names
boundAst := TAstBinder.Bind(FRootScope.CreateDescriptor, expandedAst, descriptor);
// Step 3: Check types
typedAst := TTypeChecker.CheckTypes(boundAst, descriptor);
// Step 4: Lowering
loweredAst := TAstLowerer.Lower(typedAst);
// Step 5: TCO
var compiledAst := TAstTCO.Optimize(loweredAst);
// Step 6: Create the executable package
Result := TExecutable.Create(compiledAst, descriptor);
end;
function TEnvironment.CreateEnvironment: IEnvironment;
begin
Result := TEnvironment.Create(TScope.CreateScope(FRootScope, nil, nil), TMacroRegistryImpl.Create(FMacroRegistry), FExecutionStrategy);
end;
function TEnvironment.Execute(const AScript: IExecutable): TDataValue;
var
evalScope: IExecutionScope;
visitor: IEvaluatorVisitor;
function TEnvironment.Compile(const ANode: IAstNode; out Descriptor: IScopeDescriptor): IAstNode;
begin
// 1. Create the runtime scope *directly from the descriptor*.
// This creates the raw scope with the correct slot count for local variables,
// parented to FRootScope.
evalScope := AScript.Descriptor.CreateScope(FRootScope);
// Step 1: Expand macros
var cExecutionStrategy := FExecutionStrategy;
var expandedAst :=
TMacroExpander.ExpandMacros(
FMacroRegistry,
FRootScope,
ANode,
function(const Scope: IExecutionScope): IEvaluatorVisitor begin Result := cExecutionStrategy.CreateVisitor(Scope); end
);
// 2. Use the strategy to create the final evaluator
visitor := FExecutionStrategy.CreateVisitor(evalScope);
// Step 2: Bind names
var boundAst := TAstBinder.Bind(FRootScope.CreateDescriptor, expandedAst, Descriptor);
// 3. Execute the compiled AST
Result := visitor.Execute(AScript.CompiledAst);
// Step 3: Check types
var typedAst := TTypeChecker.CheckTypes(boundAst, Descriptor);
// Step 4: Lowering
var loweredAst := TAstLowerer.Lower(typedAst);
// Step 5: TCO
var compiledAst := TAstTCO.Optimize(loweredAst);
Result := compiledAst;
end;
function TEnvironment.Compile(const ANode: IAstNode; const Params: TArray<IIdentifierNode> = []): TDataValue.TFunc;
var
desc: IScopeDescriptor;
begin
var prg := TAst.LambdaExpr(Params, ANode);
var compiled := Compile(prg, desc);
var visitor := FExecutionStrategy.CreateVisitor(desc.CreateScope(FRootScope));
var func := compiled.Accept(visitor).AsMethod;
Result :=
function(const Args: TArray<TDataValue>): TDataValue
begin
Result := func(Args);
TEvaluatorVisitor.HandleTCO(Result);
end;
end;
end.
+2 -2
View File
@@ -19,7 +19,6 @@ type
private
FScope: IExecutionScope;
class var
procedure HandleTCO(var ResultValue: TDataValue);
protected
// IAstVisitor methods made virtual for TDebugEvaluatorVisitor to override
@@ -60,6 +59,7 @@ type
// Executes an AST with proper TCO handling. This is the main entry point.
function Execute(const RootNode: IAstNode): TDataValue;
class procedure HandleTCO(var ResultValue: TDataValue); static;
end;
implementation
@@ -149,7 +149,7 @@ begin
Result := function(const AScope: IExecutionScope): IEvaluatorVisitor begin Result := TEvaluatorVisitor.Create(AScope); end;
end;
procedure TEvaluatorVisitor.HandleTCO(var ResultValue: TDataValue);
class procedure TEvaluatorVisitor.HandleTCO(var ResultValue: TDataValue);
begin
// This is the central trampoline loop for Tail Call Optimization.
// It runs as long as the evaluation returns a thunk.