Binder refactoring - extracted type checking

This commit is contained in:
Michael Schimmel
2025-11-01 14:14:17 +01:00
parent 6826b75c19
commit 734b7b1d5e
2 changed files with 68 additions and 95 deletions
+62 -71
View File
@@ -38,12 +38,13 @@ 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;
FMX.ListView, // Added for platform-independent dialogs
Myc.Ast.MacroExpander,
Myc.Ast.TypeChecker;
type
// A test record
@@ -120,7 +121,9 @@ type
FScriptUpdate: Boolean;
procedure WorkspaceMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Single);
function CreateEvaluator(const Scope: IExecutionScope): IEvaluatorVisitor;
// Helper function to encapsulate the Bind -> Evaluate pattern
// Helper function to encapsulate the 3-stage compilation pipeline
function CompileAst(const ANode: IAstNode; const AParentScope: IExecutionScope; out ADescriptor: IScopeDescriptor): IAstNode;
// Helper function to encapsulate the Compile -> Evaluate pattern
function ExecuteAst(const ANode: IAstNode; const AParentScope: IExecutionScope): TDataValue;
procedure UpdateScript;
procedure ShowVizualization(X, Y: Single);
@@ -207,36 +210,43 @@ begin
RegisterNativeFunctions(FGScope);
end;
function TForm1.ExecuteAst(const ANode: IAstNode; const AParentScope: IExecutionScope): TDataValue;
function TForm1.CompileAst(const ANode: IAstNode; const AParentScope: IExecutionScope; out ADescriptor: IScopeDescriptor): IAstNode;
var
boundAst: IAstNode;
descriptor: IScopeDescriptor; // This is the final (binder) descriptor
evalScope: IExecutionScope;
visitor: IEvaluatorVisitor;
macroDescriptor: IScopeDescriptor; // This is the macro-pass descriptor
expandedAst: IAstNode;
macroDescriptor: IScopeDescriptor;
expandedAst, boundAst: IAstNode;
begin
// The new, streamlined pipeline: The binder handles macro expansion internally.
FCurrUnboundAst := ANode;
// 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);
// 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);
// Step 2: Bind names and addresses (Phase 2)
// The binder uses the parent scope, but produces the final descriptor
boundAst := TAstBinder.Bind(AParentScope, expandedAst, ADescriptor);
// Step 3: Check and infer types (Phase 3)
// The type checker runs on the bound AST and the final descriptor
Result := TTypeChecker.CheckTypes(boundAst, ADescriptor);
end;
function TForm1.ExecuteAst(const ANode: IAstNode; const AParentScope: IExecutionScope): TDataValue;
var
descriptor: IScopeDescriptor;
evalScope: IExecutionScope;
visitor: IEvaluatorVisitor;
typedAst: IAstNode;
begin
FCurrUnboundAst := ANode;
// Call the new helper function for the 3-stage pipeline
typedAst := CompileAst(ANode, AParentScope, descriptor);
// Store the final bound AST for visualization and debugging.
FCurrAst := boundAst;
FCurrAst := typedAst;
FCurrDesc := descriptor;
// 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.
// Create the final scope and evaluator for runtime execution.
evalScope := descriptor.CreateScope(AParentScope);
visitor := CreateEvaluator(evalScope);
Result := visitor.Execute(boundAst);
Result := visitor.Execute(typedAst);
end;
procedure TForm1.FormCreate(Sender: TObject);
@@ -250,10 +260,8 @@ begin
TAst.RegisterLibrary(
procedure(const Scope: IExecutionScope)
var
smaAst, boundSmaAst, expandedAst: IAstNode;
// binder: IAstBinder; // <-- REMOVED
smaAst, typedAst: IAstNode;
smaDescriptor: IScopeDescriptor;
macroDescriptor: IScopeDescriptor;
smaScope: IExecutionScope;
smaVisitor: IEvaluatorVisitor;
begin
@@ -308,14 +316,15 @@ begin
)
);
// Phase 1: Expand
expandedAst := TMacroExpander.ExpandMacros(Scope, smaAst, macroDescriptor, CreateEvaluator);
// Phase 2: Bind
boundSmaAst := TAstBinder.Bind(Scope, expandedAst, smaDescriptor);
// Run the full pipeline
typedAst := CompileAst(smaAst, Scope, smaDescriptor);
smaScope := smaDescriptor.CreateScope(Scope);
smaVisitor := CreateEvaluator(smaScope);
// Execute the typed AST to define the function
smaVisitor.Execute(typedAst);
Scope.Define(
'print',
TDataValue(
@@ -543,8 +552,6 @@ var
jsonString: string;
jsonObj: TJSONObject;
converter: IJsonAstConverter;
macroDescriptor: IScopeDescriptor;
expandedAst: IAstNode;
begin
Memo1.Lines.BeginUpdate;
try
@@ -566,12 +573,10 @@ begin
try
var unboundAst := converter.Deserialize(jsonObj);
// Phase 1: Expand
expandedAst := TMacroExpander.ExpandMacros(FGScope, unboundAst, macroDescriptor, CreateEvaluator);
// Phase 2: Bind
FCurrAst := TAstBinder.Bind(FGScope, expandedAst, FCurrDesc);
// Run the full pipeline
FCurrAst := CompileAst(unboundAst, FGScope, FCurrDesc);
Memo1.Lines.Add('AST deserialized successfully from JSON.');
Memo1.Lines.Add('AST deserialized and bound successfully from JSON.');
Memo1.Lines.Add('You can now visualize it (Middle Mouse Click) or pretty-print it.');
finally
jsonObj.Free;
@@ -625,32 +630,31 @@ end;
procedure TForm1.DumpButtonClick(Sender: TObject);
var
boundNode: IAstNode;
macroDescriptor: IScopeDescriptor;
typedNode: IAstNode;
begin
Memo1.Lines.Clear;
Memo1.Lines.Add('--- AST Dump ---');
if not Assigned(FCurrAst) then
if not Assigned(FCurrUnboundAst) then
begin
Memo1.Lines.Add('No AST has been generated yet. Click a test button first.');
exit;
end;
// 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);
// Re-run the full pipeline for the dump
typedNode := CompileAst(FCurrUnboundAst, FGScope, FCurrDesc);
TAstDumper.Dump(typedNode, Memo1.Lines);
end;
procedure TForm1.FibonacciButtonClick(Sender: TObject);
var
root, fibAst, boundFibAst: IAstNode;
root, fibAst, typedAst: IAstNode;
result: TDataValue;
sw: TStopwatch;
fibScope: IExecutionScope;
visitor: IEvaluatorVisitor;
macroDescriptor: IScopeDescriptor;
desc: IScopeDescriptor;
begin
fibAst :=
TAst.Block(
@@ -680,15 +684,12 @@ begin
]
);
// Phase 1: Expand
var expandedAst := TMacroExpander.ExpandMacros(FGScope, fibAst, macroDescriptor, CreateEvaluator);
// Phase 2: Bind
var desc: IScopeDescriptor;
boundFibAst := TAstBinder.Bind(FGScope, expandedAst, desc);
// Run the full pipeline
typedAst := CompileAst(fibAst, FGScope, desc);
fibScope := desc.CreateScope(FGScope);
visitor := CreateEvaluator(fibScope);
visitor.Execute(boundFibAst);
visitor.Execute(typedAst);
Memo1.Lines.Clear;
Memo1.Lines.Add('--- Naive recursive fib with AST---');
@@ -920,8 +921,8 @@ var
scope: IExecutionScope;
values: TArray<TScalar.TValue>;
recordValue: TScalarRecord;
setupAst, boundSetupAst, callAst, boundCallAst: IAstNode;
setupDescriptor, callDescriptor, macroDescriptor: IScopeDescriptor;
setupAst, boundCallAst, callAst, typedAst: IAstNode;
setupDescriptor, callDescriptor: IScopeDescriptor;
seriesAddress: TResolvedSymbol; // Use TResolvedSymbol
begin
Memo1.Lines.Clear;
@@ -976,26 +977,19 @@ begin
]
);
// 1. Bind and execute the setup script.
// Phase 1
var expandedAst := TMacroExpander.ExpandMacros(FGScope, setupAst, macroDescriptor, CreateEvaluator);
// Phase 2
boundSetupAst := TAstBinder.Bind(FGScope, expandedAst, setupDescriptor);
// 1. Compile and execute the setup script.
typedAst := CompileAst(setupAst, FGScope, setupDescriptor);
scope := setupDescriptor.CreateScope(FGScope);
var setupVisitor := CreateEvaluator(scope);
setupVisitor.Execute(boundSetupAst);
setupVisitor.Execute(typedAst);
// 2. Prepare for the simulation loop
scope.Define('current_series', TDataValue.Void);
var currentSeriesIdent := TAst.Identifier('current_series');
callAst := TAst.FunctionCall(TAst.Identifier('maCrossStrategy'), [currentSeriesIdent]);
// 3. Re-bind the call AST within the now-populated scope to resolve the new variable.
// Phase 1
var expandedAst2 := TMacroExpander.ExpandMacros(scope, callAst, macroDescriptor, CreateEvaluator);
// Phase 2
boundCallAst := TAstBinder.Bind(scope, expandedAst2, callDescriptor);
// 3. Re-compile the call AST within the now-populated scope to resolve the new variable.
boundCallAst := CompileAst(callAst, scope, callDescriptor);
// 4. Get the address of 'current_series' from the new descriptor.
seriesAddress := callDescriptor.FindSymbol('current_series');
@@ -1101,7 +1095,6 @@ procedure TForm1.CreateTriggerExampleButtonClick(Sender: TObject);
var
blk: IAstNode;
visitor: IEvaluatorVisitor;
macroDescriptor: IScopeDescriptor;
begin
Memo1.Lines.Clear;
Memo1.Lines.Add('--- Creating Trigger Blueprint ---');
@@ -1122,10 +1115,8 @@ begin
]
);
// Phase 1
var expandedAst := TMacroExpander.ExpandMacros(FGScope, blk, macroDescriptor, CreateEvaluator);
// Phase 2
FCurrAst := TAstBinder.Bind(FGScope, expandedAst, FCurrDesc);
// Run the pipeline
FCurrAst := CompileAst(blk, FGScope, FCurrDesc);
FTriggerScope := FCurrDesc.CreateScope(FGScope);
visitor := CreateEvaluator(FTriggerScope);
@@ -1305,7 +1296,7 @@ begin
jsonObj := jsonValue as TJSONObject;
converter := TJsonAstConverter.Create;
FCurrAst := converter.Deserialize(jsonObj);
FCurrUnboundAst := converter.Deserialize(jsonObj); // <-- Store unbound AST
// Update the UI
UpdateScript; // This will print to ScriptMemo and show visualization