diff --git a/ASTPlayground/MainForm.pas b/ASTPlayground/MainForm.pas index 22a8d7c..c39e6b0 100644 --- a/ASTPlayground/MainForm.pas +++ b/ASTPlayground/MainForm.pas @@ -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; 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 diff --git a/Src/AST/Myc.Ast.Binding.pas b/Src/AST/Myc.Ast.Binding.pas index 8a30fca..8f94e2d 100644 --- a/Src/AST/Myc.Ast.Binding.pas +++ b/Src/AST/Myc.Ast.Binding.pas @@ -25,12 +25,7 @@ type TAstBinder = class(TAstTransformer, IAstBinder) private type - TUpvalueMapping = class - public - Map: TDictionary; - constructor Create; - destructor Destroy; override; - end; + TUpvalueMapping = TDictionary; private FInitialScope: IExecutionScope; FCurrentDescriptor: IScopeDescriptor; @@ -110,19 +105,6 @@ begin Result := Result * 23 + Value.SlotIndex; end; -{ TAstBinder.TUpvalueMapping } -constructor TAstBinder.TUpvalueMapping.Create; -begin - inherited Create; - Map := TDictionary.Create(TResolvedAddressComparer.Create); -end; - -destructor TAstBinder.TUpvalueMapping.Destroy; -begin - Map.Free; - inherited Destroy; -end; - { TAstBinder } constructor TAstBinder.Create(const AInitialScope: IExecutionScope); @@ -501,7 +483,7 @@ var lastNestedLambdaCount: Integer; boundLambda: ILambdaExpressionNode; begin - FUpvalueStack.Push(TUpvalueMapping.Create); + FUpvalueStack.Push(TUpvalueMapping.Create(TResolvedAddressComparer.Create)); try EnterScope; try @@ -529,7 +511,7 @@ begin // Upvalue mapping extraction remains the same var upvalueMapping := FUpvalueStack.Peek; - var sortedPairs := upvalueMapping.Map.ToArray; + var sortedPairs := upvalueMapping.ToArray; TArray.Sort>( sortedPairs, TComparer>.Construct( @@ -610,10 +592,10 @@ begin var upvalue := FUpvalueStack.Peek; dec(adr.ScopeDepth); // Adjust address to be relative to the lambda's parent var upvalueIndex: Integer; - if not upvalue.Map.TryGetValue(adr, upvalueIndex) then + if not upvalue.TryGetValue(adr, upvalueIndex) then begin - upvalueIndex := upvalue.Map.Count; - upvalue.Map.Add(adr, upvalueIndex); + upvalueIndex := upvalue.Count; + upvalue.Add(adr, upvalueIndex); end; boundNode := TBoundIdentifierNode.Create(Node, TResolvedAddress.Create(akUpvalue, 0, upvalueIndex)); end