Binder refactoring - extracted macro expander

This commit is contained in:
Michael Schimmel
2025-11-01 13:36:10 +01:00
parent 6ab51816d1
commit 4687ecb9ca
7 changed files with 394 additions and 249 deletions
File diff suppressed because one or more lines are too long
+2 -1
View File
@@ -22,7 +22,8 @@ uses
Myc.Ast.Script in '..\Src\AST\Myc.Ast.Script.pas',
Myc.Ast.Types in '..\Src\AST\Myc.Ast.Types.pas',
Myc.Data.Keyword in '..\Src\Data\Myc.Data.Keyword.pas',
Myc.Ast.Binding.Nodes in '..\Src\AST\Myc.Ast.Binding.Nodes.pas';
Myc.Ast.Binding.Nodes in '..\Src\AST\Myc.Ast.Binding.Nodes.pas',
Myc.Ast.MacroExpander in '..\Src\AST\Myc.Ast.MacroExpander.pas';
{$R *.res}
+1
View File
@@ -153,6 +153,7 @@
<DCCReference Include="..\Src\AST\Myc.Ast.Types.pas"/>
<DCCReference Include="..\Src\Data\Myc.Data.Keyword.pas"/>
<DCCReference Include="..\Src\AST\Myc.Ast.Binding.Nodes.pas"/>
<DCCReference Include="..\Src\AST\Myc.Ast.MacroExpander.pas"/>
<BuildConfiguration Include="Base">
<Key>Base</Key>
</BuildConfiguration>
+55 -30
View File
@@ -38,11 +38,12 @@ uses
Myc.Ast.Debugger,
Myc.Fmx.AstEditor.Node,
Myc.Fmx.AstEditor.Workspace,
Myc.Ast.MacroExpander,
FMX.DialogService,
FMX.ListView.Types,
FMX.ListView.Appearances,
FMX.ListView.Adapters.Base,
FMX.ListView; // Added for platform-independent dialogs
FMX.ListView;
type
// A test record
@@ -208,25 +209,31 @@ end;
function TForm1.ExecuteAst(const ANode: IAstNode; const AParentScope: IExecutionScope): TDataValue;
var
binder: IAstBinder;
boundAst: IAstNode;
descriptor: IScopeDescriptor;
descriptor: IScopeDescriptor; // This is the final (binder) descriptor
evalScope: IExecutionScope;
visitor: IEvaluatorVisitor;
macroDescriptor: IScopeDescriptor; // This is the macro-pass descriptor
expandedAst: IAstNode;
begin
// The new, streamlined pipeline: The binder handles macro expansion internally.
FCurrUnboundAst := ANode;
// Step 1: Create a binder, passing the evaluator factory for compile-time evaluations.
binder := TAstBinder.Create(AParentScope, CreateEvaluator);
// Step 1: Expand macros (Phase 1)
// The expander uses the parent scope to find macros and the factory to eval unquotes.
expandedAst := TMacroExpander.ExpandMacros(AParentScope, ANode, macroDescriptor, CreateEvaluator);
boundAst := binder.Execute(ANode, descriptor);
// Step 2: Bind names and types (Phase 2)
// The binder runs on the *expanded* AST. It uses the same parent scope.
// It no longer needs the factory.
boundAst := TAstBinder.Bind(AParentScope, expandedAst, descriptor);
// Store the final bound AST for visualization and debugging.
FCurrAst := boundAst;
FCurrDesc := descriptor;
// Step 2: Create the final scope and evaluator for runtime execution.
// Step 3: Create the final scope and evaluator for runtime execution.
// The runtime scope is created from the *binder's* descriptor, using the parent scope.
evalScope := descriptor.CreateScope(AParentScope);
visitor := CreateEvaluator(evalScope);
Result := visitor.Execute(boundAst);
@@ -243,9 +250,10 @@ begin
TAst.RegisterLibrary(
procedure(const Scope: IExecutionScope)
var
smaAst, boundSmaAst: IAstNode;
binder: IAstBinder;
smaAst, boundSmaAst, expandedAst: IAstNode;
// binder: IAstBinder; // <-- REMOVED
smaDescriptor: IScopeDescriptor;
macroDescriptor: IScopeDescriptor;
smaScope: IExecutionScope;
smaVisitor: IEvaluatorVisitor;
begin
@@ -300,8 +308,10 @@ begin
)
);
binder := TAstBinder.Create(Scope, CreateEvaluator);
boundSmaAst := binder.Execute(smaAst, smaDescriptor);
// Phase 1: Expand
expandedAst := TMacroExpander.ExpandMacros(Scope, smaAst, macroDescriptor, CreateEvaluator);
// Phase 2: Bind
boundSmaAst := TAstBinder.Bind(Scope, expandedAst, smaDescriptor);
smaScope := smaDescriptor.CreateScope(Scope);
smaVisitor := CreateEvaluator(smaScope);
@@ -310,8 +320,10 @@ begin
'print',
TDataValue(
function(const Args: TArray<TDataValue>): TDataValue
var
str: TStringBuilder;
begin
var str := TStringBuilder.Create;
str := TStringBuilder.Create;
try
for var i := 0 to High(Args) do
begin
@@ -531,6 +543,8 @@ var
jsonString: string;
jsonObj: TJSONObject;
converter: IJsonAstConverter;
macroDescriptor: IScopeDescriptor;
expandedAst: IAstNode;
begin
Memo1.Lines.BeginUpdate;
try
@@ -550,9 +564,13 @@ begin
raise EJSONParseException.Create('Invalid JSON format.');
try
var binder := TAstBinder.Create(FGScope, CreateEvaluator);
var unboundAst := converter.Deserialize(jsonObj);
FCurrAst := binder.Execute(unboundAst, FCurrDesc);
// Phase 1: Expand
expandedAst := TMacroExpander.ExpandMacros(FGScope, unboundAst, macroDescriptor, CreateEvaluator);
// Phase 2: Bind
FCurrAst := TAstBinder.Bind(FGScope, expandedAst, FCurrDesc);
Memo1.Lines.Add('AST deserialized successfully from JSON.');
Memo1.Lines.Add('You can now visualize it (Middle Mouse Click) or pretty-print it.');
finally
@@ -607,8 +625,8 @@ end;
procedure TForm1.DumpButtonClick(Sender: TObject);
var
binder: IAstBinder;
boundNode: IAstNode;
macroDescriptor: IScopeDescriptor;
begin
Memo1.Lines.Clear;
Memo1.Lines.Add('--- AST Dump ---');
@@ -619,9 +637,9 @@ begin
exit;
end;
// Re-bind the current AST against the global scope to ensure up-to-date binding info for the dump.
binder := TAstBinder.Create(FGScope, CreateEvaluator);
boundNode := binder.Execute(FCurrAst, FCurrDesc);
// Re-expand and re-bind the *unbound* AST (FCurrUnboundAst)
var expandedAst := TMacroExpander.ExpandMacros(FGScope, FCurrUnboundAst, macroDescriptor, CreateEvaluator);
boundNode := TAstBinder.Bind(FGScope, expandedAst, FCurrDesc);
TAstDumper.Dump(boundNode, Memo1.Lines);
end;
@@ -632,7 +650,7 @@ var
sw: TStopwatch;
fibScope: IExecutionScope;
visitor: IEvaluatorVisitor;
binder: IAstBinder;
macroDescriptor: IScopeDescriptor;
begin
fibAst :=
TAst.Block(
@@ -662,9 +680,11 @@ begin
]
);
binder := TAstBinder.Create(FGScope, CreateEvaluator);
// Phase 1: Expand
var expandedAst := TMacroExpander.ExpandMacros(FGScope, fibAst, macroDescriptor, CreateEvaluator);
// Phase 2: Bind
var desc: IScopeDescriptor;
boundFibAst := binder.Execute(fibAst, desc);
boundFibAst := TAstBinder.Bind(FGScope, expandedAst, desc);
fibScope := desc.CreateScope(FGScope);
visitor := CreateEvaluator(fibScope);
@@ -900,9 +920,8 @@ var
scope: IExecutionScope;
values: TArray<TScalar.TValue>;
recordValue: TScalarRecord;
binder: IAstBinder;
setupAst, boundSetupAst, callAst, boundCallAst: IAstNode;
setupDescriptor, callDescriptor: IScopeDescriptor;
setupDescriptor, callDescriptor, macroDescriptor: IScopeDescriptor;
seriesAddress: TResolvedSymbol; // Use TResolvedSymbol
begin
Memo1.Lines.Clear;
@@ -958,8 +977,10 @@ begin
);
// 1. Bind and execute the setup script.
binder := TAstBinder.Create(FGScope, CreateEvaluator);
boundSetupAst := binder.Execute(setupAst, setupDescriptor);
// Phase 1
var expandedAst := TMacroExpander.ExpandMacros(FGScope, setupAst, macroDescriptor, CreateEvaluator);
// Phase 2
boundSetupAst := TAstBinder.Bind(FGScope, expandedAst, setupDescriptor);
scope := setupDescriptor.CreateScope(FGScope);
var setupVisitor := CreateEvaluator(scope);
@@ -971,8 +992,10 @@ begin
callAst := TAst.FunctionCall(TAst.Identifier('maCrossStrategy'), [currentSeriesIdent]);
// 3. Re-bind the call AST within the now-populated scope to resolve the new variable.
binder := TAstBinder.Create(scope, CreateEvaluator);
boundCallAst := binder.Execute(callAst, callDescriptor);
// Phase 1
var expandedAst2 := TMacroExpander.ExpandMacros(scope, callAst, macroDescriptor, CreateEvaluator);
// Phase 2
boundCallAst := TAstBinder.Bind(scope, expandedAst2, callDescriptor);
// 4. Get the address of 'current_series' from the new descriptor.
seriesAddress := callDescriptor.FindSymbol('current_series');
@@ -1077,8 +1100,8 @@ end;
procedure TForm1.CreateTriggerExampleButtonClick(Sender: TObject);
var
blk: IAstNode;
binder: IAstBinder;
visitor: IEvaluatorVisitor;
macroDescriptor: IScopeDescriptor;
begin
Memo1.Lines.Clear;
Memo1.Lines.Add('--- Creating Trigger Blueprint ---');
@@ -1099,8 +1122,10 @@ begin
]
);
binder := TAstBinder.Create(FGScope, CreateEvaluator);
FCurrAst := binder.Execute(blk, FCurrDesc);
// Phase 1
var expandedAst := TMacroExpander.ExpandMacros(FGScope, blk, macroDescriptor, CreateEvaluator);
// Phase 2
FCurrAst := TAstBinder.Bind(FGScope, expandedAst, FCurrDesc);
FTriggerScope := FCurrDesc.CreateScope(FGScope);
visitor := CreateEvaluator(FTriggerScope);