diff --git a/ASTPlayground/ASTPlayground.dproj b/ASTPlayground/ASTPlayground.dproj
index b988ee1..4b373f7 100644
--- a/ASTPlayground/ASTPlayground.dproj
+++ b/ASTPlayground/ASTPlayground.dproj
@@ -4,7 +4,7 @@
20.3
FMX
True
- Release
+ Debug
Win64
ASTPlayground
2
diff --git a/ASTPlayground/MainForm.pas b/ASTPlayground/MainForm.pas
index 9650752..ee31f8b 100644
--- a/ASTPlayground/MainForm.pas
+++ b/ASTPlayground/MainForm.pas
@@ -31,7 +31,7 @@ uses
FMX.Layouts,
FMX.Objects,
Myc.Ast.Scope,
- Myc.Ast.Debugger; // Added for TDebugSession
+ Myc.Ast.Debugger;
type
// A test record
@@ -86,6 +86,7 @@ type
FGScope: IExecutionScope;
FWorkspace: TAuraWorkspace;
procedure WorkspaceMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Single);
+ function CreateVisitor(Scope: IExecutionScope): IAstVisitor;
// Helper function to encapsulate the Bind -> Evaluate pattern
function ExecuteAst(const ANode: IAstNode; const AParentScope: IExecutionScope): TDataValue;
public
@@ -116,31 +117,12 @@ begin
end;
function TForm1.ExecuteAst(const ANode: IAstNode; const AParentScope: IExecutionScope): TDataValue;
-var
- scriptScope: IExecutionScope;
begin
// This helper function handles simple, one-off script executions.
// It binds the AST and then decides whether to run a debug session or a standard evaluation.
- scriptScope := TAst.Bind(ANode, AParentScope);
-
- if DebugBox.IsChecked then
- begin
- // Debug path: Create, run, and destroy a debug session.
- var session := TDebugSession.Create(ShowScopeBox.IsChecked);
- try
- Result := session.Execute(ANode, scriptScope);
- Memo1.Lines.Add('--- DEBUG LOG ---');
- Memo1.Lines.AddStrings(session.Log);
- finally
- session.Free;
- end;
- end
- else
- begin
- // Production path: Create and use the standard evaluator.
- var visitor := TEvaluatorVisitor.Create(scriptScope);
- Result := ANode.Accept(visitor);
- end;
+ var scriptScope := TAst.Bind(ANode, AParentScope);
+ var visitor := CreateVisitor(scriptScope);
+ Result := ANode.Accept(visitor);
end;
procedure TForm1.FormCreate(Sender: TObject);
@@ -407,9 +389,7 @@ const
smaSlowLength = 20;
smaFastLength = 10;
var
- visitor: IAstVisitor;
scope: IExecutionScope;
- session: TDebugSession;
begin
Memo1.Lines.Clear;
Memo1.Lines.Add(Format('--- Simulating O(1) SMA Crossover Strategy for %d ticks ---', [numRecs]));
@@ -518,25 +498,8 @@ begin
scope := TAst.Bind(setupAst, FGScope);
// This is a temporary visitor just for the setup execution.
- var setupVisitor: IAstVisitor;
- if DebugBox.IsChecked then
- begin
- session := TDebugSession.Create(ShowScopeBox.IsChecked);
- try
- // Execute setup script within a temporary session.
- session.Execute(setupAst, scope);
- Memo1.Lines.Add('--- DEBUG LOG (Setup) ---');
- Memo1.Lines.AddStrings(session.Log);
- session.Log.Clear; // Clear log for the next phase
- finally
- session.Free;
- end;
- end
- else
- begin
- setupVisitor := TEvaluatorVisitor.Create(scope);
- setupAst.Accept(setupVisitor);
- end;
+ var setupVisitor := CreateVisitor(scope);
+ setupAst.Accept(setupVisitor);
// 2. Prepare for the simulation loop by modifying the now-populated scope.
scope.Define('current_series', TDataValue.Void);
@@ -547,73 +510,57 @@ begin
scope := TAst.Bind(callAst, scope);
var seriesAddress := currentSeriesIdent.Address;
- // 4. NOW create the visitor for the simulation loop, using the final scope.
- session := nil;
- try
- if DebugBox.IsChecked then
- begin
- // Create a new session that will live for the duration of the loop.
- session := TDebugSession.Create(ShowScopeBox.IsChecked);
- visitor := TDebugEvaluatorVisitor.Create(scope, session.Log, ShowScopeBox.IsChecked, 1);
- end
- else
- begin
- visitor := TEvaluatorVisitor.Create(scope);
- end;
+ var visitor := CreateVisitor(scope);
- // 5. Simulation Loop
- Memo1.Lines.Add('Starting simulation...');
- Application.ProcessMessages;
+ // 5. Simulation Loop
+ Memo1.Lines.Add('Starting simulation...');
+ Application.ProcessMessages;
- var lastClose := 1000.0;
- Randomize;
- var recDef := TRttiAstHelper.JsonToRecordDefinition(TRttiAstHelper.RecordDefinitionToJson);
- var series := TDataValue.FromRecordSeries(TScalarRecordSeries.Create(recDef));
- var nw := Now;
- var ohlcvRec: TOHLCV;
- for var i := 1 to numRecs do
+ var lastClose := 1000.0;
+ Randomize;
+ var recDef := TRttiAstHelper.JsonToRecordDefinition(TRttiAstHelper.RecordDefinitionToJson);
+ var series := TDataValue.FromRecordSeries(TScalarRecordSeries.Create(recDef));
+ var nw := Now;
+ var ohlcvRec: TOHLCV;
+ for var i := 1 to numRecs do
+ begin
+ // ... (simulation logic is unchanged) ...
+ ohlcvRec.Timestamp := nw + (i / (24 * 60));
+ ohlcvRec.Open := lastClose + (Random * 0.05);
+ ohlcvRec.Close := lastClose + (Random - 0.49) * 2;
+ ohlcvRec.High := Max(ohlcvRec.Open, ohlcvRec.Close) + Random;
+ ohlcvRec.Low := Min(ohlcvRec.Open, ohlcvRec.Close) - Random;
+ ohlcvRec.Volume := RandomRange(1000, 50000);
+ lastClose := ohlcvRec.Close;
+ var recordValue :=
+ TScalarRecord.Create(
+ recDef,
+ [
+ TScalarValue.FromDateTime(ohlcvRec.Timestamp),
+ TScalarValue.FromDouble(ohlcvRec.Open),
+ TScalarValue.FromDouble(ohlcvRec.High),
+ TScalarValue.FromDouble(ohlcvRec.Low),
+ TScalarValue.FromDouble(ohlcvRec.Close),
+ TScalarValue.FromInt64(ohlcvRec.Volume)
+ ]
+ );
+ series.AsRecordSeries.Value.Add(recordValue, lookback);
+
+ if series.AsRecordSeries.Value.TotalCount >= smaSlowLength then
begin
- // ... (simulation logic is unchanged) ...
- ohlcvRec.Timestamp := nw + (i / (24 * 60));
- ohlcvRec.Open := lastClose + (Random * 0.05);
- ohlcvRec.Close := lastClose + (Random - 0.49) * 2;
- ohlcvRec.High := Max(ohlcvRec.Open, ohlcvRec.Close) + Random;
- ohlcvRec.Low := Min(ohlcvRec.Open, ohlcvRec.Close) - Random;
- ohlcvRec.Volume := RandomRange(1000, 50000);
- lastClose := ohlcvRec.Close;
- var recordValue :=
- TScalarRecord.Create(
- recDef,
- [
- TScalarValue.FromDateTime(ohlcvRec.Timestamp),
- TScalarValue.FromDouble(ohlcvRec.Open),
- TScalarValue.FromDouble(ohlcvRec.High),
- TScalarValue.FromDouble(ohlcvRec.Low),
- TScalarValue.FromDouble(ohlcvRec.Close),
- TScalarValue.FromInt64(ohlcvRec.Volume)
- ]
- );
- series.AsRecordSeries.Value.Add(recordValue, lookback);
-
- if series.AsRecordSeries.Value.TotalCount >= smaSlowLength then
+ scope[seriesAddress].Value := series;
+ var resultValue := callAst.Accept(visitor);
+ if i mod 50 = 0 then
begin
- scope[seriesAddress].Value := series;
- var resultValue := callAst.Accept(visitor);
- if i mod 50 = 0 then
- begin
- Memo1.Lines.Add(Format('Tick %d/%d: Close = %.2f, Signal = %s', [i, numRecs, ohlcvRec.Close, resultValue.ToString]));
- Application.ProcessMessages;
- end;
+ Memo1.Lines.Add(Format('Tick %d/%d: Close = %.2f, Signal = %s', [i, numRecs, ohlcvRec.Close, resultValue.ToString]));
+ Application.ProcessMessages;
end;
end;
-
- sw.Stop;
- Memo1.Lines.Add('--- Simulation Finished ---');
- Memo1.Lines.Add(Format('Total time: %d ms', [sw.ElapsedMilliseconds]));
- finally
- if Assigned(session) then
- session.Free;
end;
+
+ sw.Stop;
+ Memo1.Lines.Add('--- Simulation Finished ---');
+ Memo1.Lines.Add(Format('Total time: %d ms', [sw.ElapsedMilliseconds]));
end;
var
@@ -622,7 +569,6 @@ var
procedure TForm1.CreateTriggerExampleButtonClick(Sender: TObject);
var
blk: IAstNode;
- visitor: IAstVisitor;
begin
Memo1.Lines.Clear;
Memo1.Lines.Add('--- Creating Trigger Blueprint ---');
@@ -643,22 +589,8 @@ begin
TriggerScope := TAst.Bind(blk, FGScope);
// This case is simple enough to just inline the logic from ExecuteAst
- if DebugBox.IsChecked then
- begin
- var session := TDebugSession.Create(ShowScopeBox.IsChecked);
- try
- session.Execute(blk, TriggerScope);
- Memo1.Lines.Add('--- DEBUG LOG ---');
- Memo1.Lines.AddStrings(session.Log);
- finally
- session.Free;
- end;
- end
- else
- begin
- visitor := TEvaluatorVisitor.Create(TriggerScope);
- blk.Accept(visitor);
- end;
+ var visitor := CreateVisitor(TriggerScope);
+ blk.Accept(visitor);
FLastAst := blk;
@@ -666,6 +598,14 @@ begin
Memo1.Lines.Add('Click "Do Trigger" to execute.');
end;
+function TForm1.CreateVisitor(Scope: IExecutionScope): IAstVisitor;
+begin
+ if DebugBox.IsChecked then
+ Result := TDebugEvaluatorVisitor.CreateVisitor(Scope, Memo1.Lines, ShowScopeBox.IsChecked)
+ else
+ Result := TEvaluatorVisitor.Create(Scope);
+end;
+
procedure TForm1.DoTriggerButtonClick(Sender: TObject);
var
callAst: IFunctionCallNode;
diff --git a/Src/AST/Myc.Ast.Debugger.pas b/Src/AST/Myc.Ast.Debugger.pas
index 1f3ec2c..8254ddb 100644
--- a/Src/AST/Myc.Ast.Debugger.pas
+++ b/Src/AST/Myc.Ast.Debugger.pas
@@ -13,26 +13,6 @@ uses
Myc.Ast.Evaluator;
type
- TDebugSession = class
- private
- class var
- FCurrent: TDebugSession;
- private
- FLog: TStrings;
- FShowScope: Boolean;
- FVisitorStack: TStack;
- function GetLog: TStrings;
- public
- constructor Create(AShowScope: Boolean);
- destructor Destroy; override;
-
- class property Current: TDebugSession read FCurrent;
-
- function Execute(const ANode: IAstNode; const AScriptScope: IExecutionScope): TDataValue;
- property Log: TStrings read GetLog;
- property VisitorStack: TStack read FVisitorStack;
- end;
-
TDebugEvaluatorVisitor = class(TEvaluatorVisitor)
private
FLog: TStrings;
@@ -44,9 +24,18 @@ type
procedure ShowScope;
protected
// Overridden to provide the debug-specific visitor factory.
- function GetVisitorFactory: TVisitorFactory; override;
+ function CreateVisitorFactory: TVisitorFactory; override;
+
public
constructor Create(const AScope: IExecutionScope; ALog: TStrings; AShowScope: Boolean; AInitialIndent: Integer = 0);
+
+ class function CreateVisitor(
+ const AScope: IExecutionScope;
+ ALog: TStrings;
+ AShowScope: Boolean;
+ AInitialIndent: Integer = 0
+ ): IAstVisitor;
+
function VisitFunctionCall(const Node: IFunctionCallNode): TDataValue; override;
// The logging overrides for other Visit... methods
function VisitConstant(const Node: IConstantNode): TDataValue; override;
@@ -71,44 +60,9 @@ uses
System.TypInfo,
Myc.Ast.Printer;
-{ TDebugSession }
-
-constructor TDebugSession.Create(AShowScope: Boolean);
-begin
- inherited Create;
- if Assigned(FCurrent) then
- raise EInvalidOpException.Create('Another debug session is already active.');
-
- FLog := TStringList.Create;
- FShowScope := AShowScope;
- FVisitorStack := TStack.Create;
- FCurrent := Self;
-end;
-
-destructor TDebugSession.Destroy;
-begin
- FLog.Free;
- FVisitorStack.Free;
- FCurrent := nil;
- inherited Destroy;
-end;
-
-function TDebugSession.Execute(const ANode: IAstNode; const AScriptScope: IExecutionScope): TDataValue;
-var
- initialVisitor: IAstVisitor;
-begin
- initialVisitor := TDebugEvaluatorVisitor.Create(AScriptScope, FLog, FShowScope, 0);
- Result := ANode.Accept(initialVisitor);
-end;
-
-function TDebugSession.GetLog: TStrings;
-begin
- Result := FLog;
-end;
-
{ TDebugEvaluatorVisitor }
-constructor TDebugEvaluatorVisitor.Create(const AScope: IExecutionScope; ALog: TStrings; AShowScope: Boolean; AInitialIndent: Integer);
+constructor TDebugEvaluatorVisitor.Create(const AScope: IExecutionScope; ALog: TStrings; AShowScope: Boolean; AInitialIndent: Integer = 0);
begin
inherited Create(AScope);
Assert(Assigned(ALog));
@@ -118,41 +72,32 @@ begin
ShowScope;
end;
-function TDebugEvaluatorVisitor.GetVisitorFactory: TVisitorFactory;
+function TDebugEvaluatorVisitor.CreateVisitorFactory: TVisitorFactory;
begin
- // Return a closure that creates a new Debug visitor,
- // using the context from the currently active session stack.
+ // Return a closure that creates a new Debug visitor, using the current context
+
+ var callingVisitor := Self as IAstVisitor;
Result :=
function(const AScope: IExecutionScope): IAstVisitor
begin
- if (not Assigned(TDebugSession.Current)) or (TDebugSession.Current.VisitorStack.Count = 0) then
- raise EInvalidOpException.Create('Visitor stack is empty during function invocation.');
-
- var callingVisitor := TDebugSession.Current.VisitorStack.Peek as TDebugEvaluatorVisitor;
- Result := TDebugEvaluatorVisitor.Create(AScope, callingVisitor.FLog, callingVisitor.FShowScope, callingVisitor.FIndentLevel);
+ var visitor := callingVisitor as TDebugEvaluatorVisitor;
+ Result := TDebugEvaluatorVisitor.Create(AScope, visitor.FLog, visitor.FShowScope, visitor.FIndentLevel);
end;
end;
function TDebugEvaluatorVisitor.VisitFunctionCall(const Node: IFunctionCallNode): TDataValue;
begin
- Assert(Assigned(TDebugSession.Current), 'No active debug session.');
- TDebugSession.Current.VisitorStack.Push(Self);
+ AppendLine('FunctionCall{');
+ Indent;
try
- AppendLine('FunctionCall{');
- Indent;
- try
- ShowScope;
- Result := inherited VisitFunctionCall(Node);
- finally
- Unindent;
- end;
- AppendLine(Format('} -> %s', [Result.ToString]));
+ ShowScope;
+ Result := inherited VisitFunctionCall(Node);
finally
- TDebugSession.Current.VisitorStack.Pop;
+ Unindent;
end;
+ AppendLine(Format('} -> %s', [Result.ToString]));
end;
-// ... The rest of the TDebugEvaluatorVisitor logging methods are unchanged ...
procedure TDebugEvaluatorVisitor.Indent;
begin
inc(FIndentLevel);
@@ -170,12 +115,20 @@ var
begin
pad := '';
for i := 0 to FIndentLevel - 1 do
- begin
pad := pad + ':' + ''.PadLeft(3);
- end;
FLog.Add(pad + S);
end;
+class function TDebugEvaluatorVisitor.CreateVisitor(
+ const AScope: IExecutionScope;
+ ALog: TStrings;
+ AShowScope: Boolean;
+ AInitialIndent: Integer = 0
+): IAstVisitor;
+begin
+ Result := TDebugEvaluatorVisitor.Create(AScope, ALog, AShowScope, AInitialIndent);
+end;
+
procedure TDebugEvaluatorVisitor.ShowScope;
var
scopeDump: TArray;
diff --git a/Src/AST/Myc.Ast.Evaluator.pas b/Src/AST/Myc.Ast.Evaluator.pas
index 111a1ce..27256e8 100644
--- a/Src/AST/Myc.Ast.Evaluator.pas
+++ b/Src/AST/Myc.Ast.Evaluator.pas
@@ -22,8 +22,9 @@ type
FScope: IExecutionScope;
protected
function IsTruthy(const AValue: TDataValue): Boolean;
+
// Returns a closure that can create the correct type of visitor for a lambda's body.
- function GetVisitorFactory: TVisitorFactory; virtual;
+ function CreateVisitorFactory: TVisitorFactory; virtual;
// Creates the callable closure for a lambda expression. No longer virtual.
function CreateLambdaClosure(
@@ -103,7 +104,7 @@ begin
FScope := AScope;
end;
-function TEvaluatorVisitor.GetVisitorFactory: TVisitorFactory;
+function TEvaluatorVisitor.CreateVisitorFactory: TVisitorFactory;
begin
// The production visitor returns a factory that creates another production visitor.
Result := function(const AScope: IExecutionScope): IAstVisitor begin Result := TEvaluatorVisitor.Create(AScope); end;
@@ -137,7 +138,7 @@ var
visitorFactory: TVisitorFactory;
begin
// Get the appropriate factory via virtual dispatch.
- visitorFactory := GetVisitorFactory();
+ visitorFactory := CreateVisitorFactory();
closureValue :=
TDataValue.TFunc(
@@ -184,6 +185,8 @@ var
i: Integer;
sourceAddresses: TArray;
closureScope: IExecutionScope;
+ closureValue: TDataValue;
+ visitorFactory: TVisitorFactory;
begin
sourceAddresses := Node.Upvalues;
SetLength(capturedCells, Length(sourceAddresses));
@@ -195,7 +198,48 @@ begin
else
closureScope := nil;
- Result := CreateLambdaClosure(Node, capturedCells, closureScope);
+ // Get the appropriate factory via virtual dispatch.
+ visitorFactory := CreateVisitorFactory();
+
+ var scopeDescriptor := Node.ScopeDescriptor;
+ var params := Node.Parameters;
+
+ closureValue :=
+ TDataValue.TFunc(
+ function(const ArgValues: TArray): TDataValue
+ var
+ lambdaScope: IExecutionScope;
+ bodyVisitor: IAstVisitor;
+ i: Integer;
+ adr: TResolvedAddress;
+ begin
+ if (Length(ArgValues) <> Length(params)) then
+ raise EArgumentException.CreateFmt('Argument count mismatch: expected %d, got %d', [Length(params), Length(ArgValues)]);
+
+ lambdaScope := TExecutionScope.Create(closureScope, scopeDescriptor, capturedCells);
+
+ adr.Kind := akLocalOrParent;
+ adr.ScopeDepth := 0;
+
+ // Set 'Self' (slot 0) to the captured closureValue for recursion.
+ adr.SlotIndex := 0;
+ lambdaScope.Cell[adr].Value := closureValue;
+
+ // Populate the actual parameters.
+ for i := 0 to High(ArgValues) do
+ begin
+ adr.SlotIndex := params[i].Address.SlotIndex;
+ lambdaScope.Cell[adr].Value := ArgValues[i];
+ end;
+
+ // Use the injected factory to create the visitor for the lambda's body.
+ bodyVisitor := visitorFactory(lambdaScope);
+
+ Result := Node.Body.Accept(bodyVisitor);
+ end
+ );
+
+ Result := closureValue;
end;
function TEvaluatorVisitor.VisitFunctionCall(const Node: IFunctionCallNode): TDataValue;