Ast environments

This commit is contained in:
Michael Schimmel
2025-11-07 19:40:35 +01:00
parent 4ccf3bb5fd
commit c0f871ce02
12 changed files with 722 additions and 364 deletions
+199 -260
View File
@@ -28,26 +28,18 @@ uses
Myc.Ast.Scope,
Myc.Ast,
Myc.Ast.Visitor,
Myc.Ast.Evaluator,
Myc.Ast.Dumper,
Myc.Data.Decimal,
Myc.Ast.RTL,
Myc.Ast.Script,
FMX.Layouts,
FMX.Objects,
Myc.Ast.Debugger,
Myc.Fmx.AstEditor.Node,
Myc.Fmx.AstEditor.Workspace,
Myc.Ast.Compiler.Macros,
Myc.Ast.Compiler.Binder,
Myc.Ast.Compiler.TypeChecker,
Myc.Ast.Compiler.Lowering,
Myc.Ast.Compiler.TCO,
FMX.DialogService,
FMX.ListView.Types,
FMX.ListView.Appearances,
FMX.ListView.Adapters.Base,
FMX.ListView;
FMX.ListView,
Myc.Ast.Environment; // Added Environment
type
// A test record
@@ -94,6 +86,7 @@ type
procedure ClearButtonClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure CreateTriggerExampleButtonClick(Sender: TObject);
procedure DebugBoxChange(Sender: TObject);
procedure DoTrigger2ButtonClick(Sender: TObject);
procedure DoTriggerButtonClick(Sender: TObject);
procedure DumpButtonClick(Sender: TObject);
@@ -116,18 +109,16 @@ type
procedure RTLListViewChange(Sender: TObject);
private
FCurrUnboundAst: IAstNode;
FCurrAst: IAstNode;
FCurrDesc: IScopeDescriptor;
FGScope: IExecutionScope;
FCurrExec: IExecutable; // Replaced FCurrAst and FCurrDesc
FEnvironment: TAstEnvironment;
FWorkspace: TAuraWorkspace;
FTriggerScope: IExecutionScope;
FScriptUpdate: Boolean;
FTriggerTest: TAstEnvironment;
procedure WorkspaceMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Single);
function CreateEvaluator(const Scope: IExecutionScope): IEvaluatorVisitor;
// Helper function to encapsulate the compilation pipeline
function CompileAst(const ANode: IAstNode; const AParentScope: IExecutionScope; out ADescriptor: IScopeDescriptor): IAstNode;
function CreateEnvironment: TAstEnvironment;
// Helper function to encapsulate the Compile -> Evaluate pattern
function ExecuteAst(const ANode: IAstNode; const AParentScope: IExecutionScope): TDataValue;
function ExecuteAst(const ANode: IAstNode): TDataValue;
procedure UpdateScript;
procedure ShowVizualization(X, Y: Single);
procedure PrintScript(const Node: IAstNode);
@@ -149,7 +140,10 @@ uses
System.TimeSpan,
Myc.Ast.Json, // For TAstJson serialization
Myc.Ast.Types, // Needed for TTypeRules
System.IOUtils; // For TFile
System.IOUtils, // For TFile
Myc.Ast.Dumper, // Needed for DumpButtonClick
Myc.Ast.RTL,
Myc.Ast.Compiler.Macros; // Needed for MacroRegistry access
{$R *.fmx}
@@ -199,7 +193,7 @@ begin
]
);
resultValue := ExecuteAst(mainBlock, FGScope);
resultValue := ExecuteAst(mainBlock);
Assert(TScalar.FromInt64(15) = resultValue.AsScalar, 'The final result should be 15, but is ' + resultValue.AsScalar.ToString);
UpdateScript;
@@ -209,67 +203,64 @@ procedure TForm1.ClearButtonClick(Sender: TObject);
begin
FWorkspace.DeleteChildren;
FWorkspace.Repaint;
FGScope := TAst.CreateScope(nil);
end;
function TForm1.CompileAst(const ANode: IAstNode; const AParentScope: IExecutionScope; out ADescriptor: IScopeDescriptor): IAstNode;
var
expandedAst, boundAst, typedAst, loweredAst: IAstNode;
function TForm1.CreateEnvironment: TAstEnvironment;
begin
// Step 1: Expand macros (Phase 1)
expandedAst := TMacroExpander.ExpandMacros(AParentScope, ANode, CreateEvaluator);
// Step 2: Bind names and addresses (Phase 2)
boundAst := TAstBinder.Bind(AParentScope, expandedAst, ADescriptor);
// Step 3: Check and infer types (Phase 3)
typedAst := TTypeChecker.CheckTypes(boundAst, ADescriptor);
// Step 4: Lowering / Canonicalization (Phase 4)
loweredAst := TAstLowerer.Lower(typedAst);
// Step 5: Tail Call Optimization (Phase 5)
Result := TAstTCO.Optimize(loweredAst);
if DebugBox.IsChecked then
Result.SetDebugMode(Memo1.Lines, ShowScopeBox.IsChecked)
else
Result.SetStandardMode;
end;
function TForm1.ExecuteAst(const ANode: IAstNode; const AParentScope: IExecutionScope): TDataValue;
var
descriptor: IScopeDescriptor;
evalScope: IExecutionScope;
visitor: IEvaluatorVisitor;
compiledAst: IAstNode;
// Simplified ExecuteAst using TEnvironment
function TForm1.ExecuteAst(const ANode: IAstNode): TDataValue;
begin
FCurrUnboundAst := ANode;
FCurrExec := nil; // Clear previous
// Call the helper function for the full 4-stage pipeline
compiledAst := CompileAst(ANode, AParentScope, descriptor);
// 1. Set strategy based on UI
if DebugBox.IsChecked then
FEnvironment.SetDebugMode(Memo1.Lines, ShowScopeBox.IsChecked)
else
FEnvironment.SetStandardMode;
// Store the final bound AST for visualization and debugging.
FCurrAst := compiledAst; // <-- Store the fully compiled AST
FCurrDesc := descriptor;
try
// 2. Compile (Pipeline is inside Environment)
// We store the executable artifact in the form
FCurrExec := FEnvironment.Compile(ANode);
// Create the final scope and evaluator for runtime execution.
evalScope := descriptor.CreateScope(AParentScope);
visitor := CreateEvaluator(evalScope);
Result := visitor.Execute(compiledAst);
// 3. Execute
Result := FEnvironment.Execute(FCurrExec);
except
on E: Exception do
begin
Memo1.Lines.Add('--- ERROR ---');
Memo1.Lines.Add(E.ClassName + ': ' + E.Message);
Result := TDataValue.Void;
end;
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
FEnvironment := CreateEnvironment;
FWorkspace := TAuraWorkspace.Create(Panel2);
FWorkspace.Parent := Panel2;
FWorkspace.Align := TAlignLayout.Client;
FWorkspace.ClipChildren := true;
FWorkspace.OnMouseDown := WorkspaceMouseDown;
TAst.RegisterLibrary(
RegisterRtlFunctions(FEnvironment.RootScope);
// Register the SMA factory into the environment
var RegFunc :=
procedure(const Scope: IExecutionScope)
var
smaAst, typedAst: IAstNode;
smaDescriptor: IScopeDescriptor;
smaScope: IExecutionScope;
smaVisitor: IEvaluatorVisitor;
smaAst: IAstNode;
smaExec: IExecutable;
begin
smaAst :=
TAst.LambdaExpr(
@@ -322,14 +313,15 @@ begin
)
);
// Run the full pipeline
typedAst := CompileAst(smaAst, Scope, smaDescriptor);
// Compile the SMA factory using the environment itself
// Note: We pass FEnvironment.RootScope as the parent scope
smaExec := FEnvironment.Compile(smaAst);
smaScope := smaDescriptor.CreateScope(Scope);
smaVisitor := CreateEvaluator(smaScope);
// Execute the compiled AST to define the 'CreateSMA' function in the target scope
// We must use the *target* scope (Scope) as the parent for execution.
var smaFactory := FEnvironment.Execute(smaExec);
// Execute the typed AST to define the function
smaVisitor.Execute(typedAst);
Scope.Define('CreateSMA', smaFactory); // Define the factory
Scope.Define(
'print',
@@ -360,22 +352,24 @@ begin
Result := TStopwatch.GetTimeStamp div TTimeSpan.TicksPerMillisecond;
end
);
end;
TMacroExpander.GlobalMacroRegistry.Define(
TAstScript
.Parse(
'''
(defmacro stopwatch [body]
`(do
(def start-time (timestamp))
(def result ~body)
(print "Stopwatch: " (- (timestamp) start-time) "ms" )
result
))
''')
.AsMacroDefinition
);
end
RegFunc(FEnvironment.RootScope);
// Register the 'stopwatch' macro into the global registry
FEnvironment.MacroRegistry.Define(
TAstScript
.Parse(
'''
(defmacro stopwatch [body]
`(do
(def start-time (timestamp))
(def result ~body)
(print "(calculated in " (- (timestamp) start-time) " ms)" )
result
))
''')
.AsMacroDefinition
);
ClearButtonClick(Self);
@@ -406,7 +400,7 @@ begin
try
Memo1.Lines.Add(Format('--- Loading User Library from %s ---', [ExtractFileName(UserLibName)]));
var scopeDescr := FGScope.CreateDescriptor;
var scopeDescr := FEnvironment.RootScope.CreateDescriptor;
// Populate the list view with loaded functions and macros.
RTLListView.Items.BeginUpdate;
try
@@ -424,15 +418,15 @@ begin
// Distinguish between loading a macro and loading a function.
if funcAst.Kind = akMacroDefinition then
begin
// Macros are stored as raw AST nodes in the scope.
FGScope.Define(pair.JsonString.Value, TDataValue.FromIntf<IAstNode>(funcAst));
// Macros are stored in the *global compile-time registry*.
FEnvironment.MacroRegistry.Define(funcAst.AsMacroDefinition);
Memo1.Lines.Add(Format('Defined macro "%s"', [pair.JsonString.Value]));
end
else
begin
// Functions (lambdas) must be executed to create a callable closure.
funcValue := ExecuteAst(funcAst, FGScope);
FGScope.Define(pair.JsonString.Value, funcValue);
funcValue := ExecuteAst(funcAst);
FEnvironment.RootScope.Define(pair.JsonString.Value, funcValue);
Memo1.Lines.Add(Format('Defined function "%s"', [pair.JsonString.Value]));
end;
end
@@ -602,8 +596,14 @@ begin
try
var unboundAst := converter.Deserialize(jsonObj);
// Set strategy based on UI
if DebugBox.IsChecked then
FEnvironment.SetDebugMode(Memo1.Lines, ShowScopeBox.IsChecked)
else
FEnvironment.SetStandardMode;
// Run the full pipeline
FCurrAst := CompileAst(unboundAst, FGScope, FCurrDesc);
FCurrExec := FEnvironment.Compile(unboundAst);
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.');
@@ -613,8 +613,7 @@ begin
except
on E: Exception do
begin
FCurrAst := nil;
FCurrDesc := nil;
FCurrExec := nil;
Memo1.Lines.Add('Error deserializing AST from JSON:');
Memo1.Lines.Add(E.Message);
Memo1.Lines.Add('--- Original JSON ---');
@@ -634,7 +633,7 @@ var
begin
Memo1.Lines.Clear;
if not Assigned(FCurrAst) then
if not Assigned(FCurrExec) then
begin
Memo1.Lines.Add('No AST available to serialize. Please generate one first.');
exit;
@@ -642,7 +641,7 @@ begin
try
converter := TJsonAstConverter.Create;
jsonObj := converter.Serialize(FCurrAst);
jsonObj := converter.Serialize(FCurrExec.CompiledAst);
try
Memo1.Lines.Text := jsonObj.Format(4);
finally
@@ -658,92 +657,74 @@ begin
end;
procedure TForm1.DumpButtonClick(Sender: TObject);
var
typedNode: IAstNode;
begin
Memo1.Lines.Clear;
Memo1.Lines.Add('--- AST Dump ---');
if not Assigned(FCurrUnboundAst) then
if not Assigned(FCurrExec) then // Check FCurrExec
begin
Memo1.Lines.Add('No AST has been generated yet. Click a test button first.');
Memo1.Lines.Add('No *compiled* AST has been generated yet. Click a test button first.');
exit;
end;
// Re-run the full pipeline for the dump
typedNode := CompileAst(FCurrUnboundAst, FGScope, FCurrDesc);
TAstDumper.Dump(typedNode, Memo1.Lines);
// Dump the compiled AST from the executable
TAstDumper.Dump(FCurrExec.CompiledAst, Memo1.Lines);
end;
procedure TForm1.FibonacciButtonClick(Sender: TObject);
var
root, fibAst, typedAst: IAstNode;
result: TDataValue;
sw: TStopwatch;
fibScope: IExecutionScope;
visitor: IEvaluatorVisitor;
desc: IScopeDescriptor;
fibDataValue: TDataValue;
begin
fibAst :=
TAst.Block(
[
TAst.VarDecl(TAst.Identifier('fib')),
TAst.Assign(
TAst.Identifier('fib'),
TAst.LambdaExpr(
[TAst.Identifier('n')],
TAst.TernaryExpr(
TAst.BinaryExpr(TAst.Identifier('n'), TScalar.TBinaryOp.Less, TAst.Constant(2)),
TAst.Identifier('n'),
TAst.BinaryExpr(
TAst.FunctionCall(
TAst.Identifier('fib'),
[TAst.BinaryExpr(TAst.Identifier('n'), TScalar.TBinaryOp.Subtract, TAst.Constant(1))]
),
TScalar.TBinaryOp.Add,
TAst.FunctionCall(
TAst.Identifier('fib'),
[TAst.BinaryExpr(TAst.Identifier('n'), TScalar.TBinaryOp.Subtract, TAst.Constant(2))]
)
)
)
)
)
]
Memo1.Lines.Clear;
FCurrUnboundAst :=
TAstScript.Parse(
'''
(do
(def fib)
(assign fib (fn [n]
(? (< n 2)
n
(+ (fib (- n 1)) (fib (- n 2)))
)
))
)
'''
);
// Run the full pipeline
typedAst := CompileAst(fibAst, FGScope, desc);
fibScope := desc.CreateScope(FGScope);
var TestEnv := FEnvironment.CreateEnvironment;
visitor := CreateEvaluator(fibScope);
visitor.Execute(typedAst);
TestEnv.Define('fib', TAst.Block(FCurrUnboundAst));
Memo1.Lines.Clear;
Memo1.Lines.Add('--- Naive recursive fib with AST---');
sw := TStopwatch.StartNew;
root := TAst.FunctionCall(TAst.Identifier('fib'), [TAst.Constant(25)]);
result := ExecuteAst(root, fibScope);
var fibExec := TestEnv.Compile(TAst.FunctionCall(TAst.Identifier('fib'), [TAst.Constant(25)]));
sw := TStopwatch.StartNew;
result := TestEnv.Execute(fibExec);
sw.Stop;
Memo1.Lines.Add(Format('Result: fib(25) %s (calculated in %d ms)', [result.ToString, sw.ElapsedMilliseconds]));
Memo1.Lines.Add('');
Memo1.Lines.Add('--- Memoized naive fib with AST (using global fib)---');
var memoizeTest := TestEnv.CreateEnvironment;
memoizeTest.Define('fib', TAstScript.Parse('(Memoize fib)'));
var memoizeExec := memoizeTest.Compile(TAstScript.Parse('(fib 25)'));
sw := TStopwatch.StartNew;
root :=
TAst.Block(
[
TAst.Assign(TAst.Identifier('fib'), TAst.FunctionCall(TAst.Identifier('Memoize'), [TAst.Identifier('fib')])),
TAst.FunctionCall(TAst.Identifier('fib'), [TAst.Constant(25)])
]
);
result := ExecuteAst(root, fibScope);
result := memoizeTest.Execute(memoizeExec);
sw.Stop;
Memo1.Lines.Add(Format('Result: fib(25) %s (calculated in %d ms)', [result.ToString, sw.ElapsedMilliseconds]));
Memo1.Lines.Add(Format('Result 1st call: fib(25) %s (calculated in %d ms)', [result.ToString, sw.ElapsedMilliseconds]));
sw := TStopwatch.StartNew;
result := memoizeTest.Execute(memoizeExec);
sw.Stop;
Memo1.Lines.Add(Format('Result 2nd call: fib(25) %s (calculated in %d ms)', [result.ToString, sw.ElapsedMilliseconds]));
UpdateScript;
end;
@@ -753,13 +734,14 @@ begin
Memo1.Lines.Clear;
Memo1.Lines.Add('--- AST Pretty Print ---');
if not Assigned(FCurrAst) then
// We print the *compiled* AST
if not Assigned(FCurrExec) then
begin
Memo1.Lines.Add('No AST has been generated yet.');
exit;
end;
Memo1.Lines.Add(TAstScript.Print(FCurrAst));
Memo1.Lines.Add(TAstScript.Print(FCurrExec.CompiledAst));
end;
procedure TForm1.RecursionButtonClick(Sender: TObject);
@@ -772,7 +754,6 @@ begin
Memo1.Lines.Add('--- Tail-Recursive factorial(20) ---');
sw := TStopwatch.StartNew;
// Rewritten to be tail-recursive to use 'recur'
root :=
TAst.Block(
[
@@ -806,8 +787,8 @@ begin
]
);
// Execute with FGScope as parent.
result := ExecuteAst(root, FGScope);
// Execute with RootScope as parent.
result := ExecuteAst(root);
sw.Stop;
Memo1.Lines.Add(Format('Result: %s (calculated in %d ms)', [result.ToString, sw.ElapsedMilliseconds]));
@@ -821,13 +802,11 @@ var
series: TScalarRecordSeries;
recordDef: IScalarRecordDefinition;
i: Integer;
scope: IExecutionScope;
values: TArray<TScalar.TValue>;
begin
Memo1.Lines.Clear;
Memo1.Lines.Add('--- Series Test ---');
scope := TAst.CreateScope(FGScope);
recordDef := TRttiAstHelper.JsonToRecordDefinition(TRttiAstHelper.RecordDefinitionToJson<TOHLCV>);
series := TScalarRecordSeries.Create(recordDef);
SetLength(values, 6);
@@ -842,7 +821,10 @@ begin
values[5].AsInt64 := 10000 * (i + 1);
series.Add(TScalarRecord.Create(recordDef, values));
end;
scope.Define('ohlcvSeries', TDataValue.FromRecordSeries(series));
var testEnv := FEnvironment.CreateEnvironment;
testEnv.RootScope.Define('ohlcvSeries', TDataValue.FromRecordSeries(series));
ast :=
TAst.LambdaExpr(
@@ -852,7 +834,6 @@ begin
TAst.VarDecl(
TAst.Identifier('closeColumn'),
TAst.FunctionCall(TAst.Keyword('Close'), [TAst.Identifier('ohlcvSeries')])
// TAst.MemberAccess(TAst.Identifier('ohlcvSeries'), TAst.Identifier('Close'))
),
TAst.Indexer(TAst.Identifier('closeColumn'), TAst.Constant(1))
]
@@ -860,7 +841,8 @@ begin
);
callAst := TAst.FunctionCall(ast, []);
resultValue := ExecuteAst(callAst, scope);
resultValue := testEnv.CompileAndExecute(callAst);
Memo1.Lines.Add(Format('Result of script: %s', [resultValue.ToString]));
UpdateScript;
@@ -920,7 +902,7 @@ begin
]
);
result := ExecuteAst(root, FGScope);
result := ExecuteAst(root);
sw.Stop;
Memo1.Lines.Add(Format('Result of the final expression: %s (calculated in %d ms)', [result.ToString, sw.ElapsedMilliseconds]));
@@ -941,90 +923,46 @@ const
smaSlowLength = 20;
smaFastLength = 10;
var
scope: IExecutionScope;
values: TArray<TScalar.TValue>;
recordValue: TScalarRecord;
setupAst, boundCallAst, callAst, typedAst: IAstNode;
setupDescriptor, callDescriptor: IScopeDescriptor;
seriesAddress: TResolvedSymbol; // Use TResolvedSymbol
setupAst: IAstNode;
callExec: IExecutable;
begin
Memo1.Lines.Clear;
Memo1.Lines.Add(Format('--- Simulating O(1) SMA Crossover Strategy for %d ticks ---', [numRecs]));
var sw := TStopwatch.StartNew;
setupAst :=
TAst.Block(
[
TAst.VarDecl(TAst.Identifier('smaFast'), TAst.FunctionCall(TAst.Identifier('CreateSMA'), [TAst.Constant(smaFastLength)])),
TAst.VarDecl(TAst.Identifier('smaSlow'), TAst.FunctionCall(TAst.Identifier('CreateSMA'), [TAst.Constant(smaSlowLength)])),
TAst.VarDecl(
TAst.Identifier('maCrossStrategy'),
TAst.LambdaExpr(
[TAst.Identifier('ohlcv')],
TAst.Block(
[
TAst.VarDecl(
TAst.Identifier('closeSeries'),
TAst.MemberAccess(TAst.Identifier('ohlcv'), TAst.Keyword('Close'))
),
TAst.VarDecl(
TAst.Identifier('currentClose'),
TAst.Indexer(TAst.Identifier('closeSeries'), TAst.Constant(0))
),
TAst.VarDecl(
TAst.Identifier('valSmaFast'),
TAst.FunctionCall(
TAst.Identifier('smaFast'),
[TAst.Identifier('closeSeries'), TAst.Identifier('currentClose')]
)
),
TAst.VarDecl(
TAst.Identifier('valSmaSlow'),
TAst.FunctionCall(
TAst.Identifier('smaSlow'),
[TAst.Identifier('closeSeries'), TAst.Identifier('currentClose')]
)
),
TAst.TernaryExpr(
TAst.BinaryExpr(
TAst.Identifier('valSmaFast'),
TScalar.TBinaryOp.Greater,
TAst.Identifier('valSmaSlow')
),
TAst.Constant(1),
TAst.Constant(-1)
)
]
)
)
TAstScript.Parse(
'''
(do
(def smaFast (CreateSMA 10))
(def smaSlow (CreateSMA 20))
(fn [ohlcv]
(do
(def closeSeries (:Close ohlcv))
(def currentClose (get closeSeries 0))
(def valSmaFast (smaFast closeSeries currentClose))
(def valSmaSlow (smaSlow closeSeries currentClose))
(? (> valSmaFast valSmaSlow) 1 -1)
)
]
)
)
'''
);
// 1. Compile and execute the setup script.
typedAst := CompileAst(setupAst, FGScope, setupDescriptor);
scope := setupDescriptor.CreateScope(FGScope);
var setupVisitor := CreateEvaluator(scope);
setupVisitor.Execute(typedAst);
FCurrUnboundAst := setupAst;
UpdateScript;
Application.ProcessMessages;
// 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]);
var env := FEnvironment.CreateEnvironment;
// 3. Re-compile the call AST within the now-populated scope to resolve the new variable.
boundCallAst := CompileAst(callAst, scope, callDescriptor);
env.Define('maCrossStrategy', setupAst);
// 4. Get the address of 'current_series' from the new descriptor.
seriesAddress := callDescriptor.FindSymbol('current_series');
// Check seriesAddress.Address.Kind instead of seriesAddress.Kind
if seriesAddress.Address.Kind = akUnresolved then
raise Exception.Create('Could not resolve current_series address.');
var seriesAddress := env.RootScope.Define('current_series', TDataValue.Void);
// 5. Create the final scope and visitor for the simulation loop.
var loopScope := callDescriptor.CreateScope(scope);
var visitor := CreateEvaluator(loopScope);
callExec := env.Compile(TAst.FunctionCall(TAst.Identifier('maCrossStrategy'), [TAst.Identifier('current_series')]));
// 6. Simulation Loop
// Simulation Loop
Memo1.Lines.Clear;
Memo1.Lines.Add('Starting simulation...');
Application.ProcessMessages;
@@ -1058,9 +996,10 @@ begin
if series.AsRecordSeries.TotalCount >= smaSlowLength then
begin
// Use seriesAddress.Address instead of seriesAddress
loopScope[seriesAddress.Address] := series;
var resultValue := visitor.Execute(boundCallAst);
env.RootScope[seriesAddress] := series;
var resultValue := env.Execute(callExec);
if i mod 50 = 0 then
begin
Memo1.Lines.Add(Format('Tick %d/%d: Close = %.2f, Signal = %s', [i, numRecs, ohlcvRec.Close, resultValue.ToString]));
@@ -1106,7 +1045,7 @@ begin
]
);
result := ExecuteAst(root, FGScope);
result := ExecuteAst(root);
sw.Stop;
Memo1.Lines.Add(Format('Result: %s', [result.ToString]));
@@ -1117,7 +1056,6 @@ end;
procedure TForm1.CreateTriggerExampleButtonClick(Sender: TObject);
var
blk: IAstNode;
visitor: IEvaluatorVisitor;
begin
Memo1.Lines.Clear;
Memo1.Lines.Add('--- Creating Trigger Blueprint ---');
@@ -1138,33 +1076,37 @@ begin
]
);
// Run the pipeline
FCurrAst := CompileAst(blk, FGScope, FCurrDesc);
FTriggerScope := FCurrDesc.CreateScope(FGScope);
FTriggerTest := FEnvironment.CreateEnvironment;
visitor := CreateEvaluator(FTriggerScope);
visitor.Execute(FCurrAst);
FTriggerTest.Define('X', TAst.VarDecl(TAst.Identifier('X'), TAst.Constant(0)));
FTriggerTest.Define(
'tickHandler',
TAst.LambdaExpr(
[TAst.Identifier('summand')],
TAst.Assign(TAst.Identifier('X'), TAst.BinaryExpr(TAst.Identifier('X'), TScalar.TBinaryOp.Add, TAst.Identifier('summand')))
)
);
Memo1.Lines.Add('Variable "X" and function "tickHandler" defined in persistent scope.');
Memo1.Lines.Add('Click "Do Trigger" to execute.');
UpdateScript;
end;
function TForm1.CreateEvaluator(const Scope: IExecutionScope): IEvaluatorVisitor;
procedure TForm1.DebugBoxChange(Sender: TObject);
begin
if DebugBox.IsChecked then
Result := TDebugEvaluatorVisitor.Create(Scope, Memo1.Lines, ShowScopeBox.IsChecked)
FEnvironment.SetDebugMode(Memo1.Lines, ShowScopeBox.IsChecked)
else
Result := TEvaluatorVisitor.Create(Scope);
FEnvironment.SetStandardMode;
end;
procedure TForm1.DoTriggerButtonClick(Sender: TObject);
var
callAst: IFunctionCallNode;
callAst: IAstNode;
begin
callAst := TAst.FunctionCall(TAst.Identifier('tickHandler'), [TAst.Constant(1)]);
var X := ExecuteAst(callAst, FTriggerScope);
var X := FTriggerTest.CompileAndExecute(callAst);
Memo1.Lines.Add(Format('Tick(1)! New value of X: %s', [X.ToString]));
UpdateScript;
@@ -1172,11 +1114,11 @@ end;
procedure TForm1.DoTrigger2ButtonClick(Sender: TObject);
var
callAst: IFunctionCallNode;
callAst: IAstNode;
begin
callAst := TAst.FunctionCall(TAst.Identifier('tickHandler'), [TAst.Constant(2)]);
var X := ExecuteAst(callAst, FTriggerScope);
var X := FTriggerTest.CompileAndExecute(callAst);
Memo1.Lines.Add(Format('Tick(2)! New value of X: %s', [X.ToString]));
UpdateScript;
@@ -1184,16 +1126,13 @@ end;
procedure TForm1.ExternalFuncButtonClick(Sender: TObject);
var
scope: IExecutionScope;
callAst: IAstNode;
resultValue: TDataValue;
begin
Memo1.Lines.Clear;
Memo1.Lines.Add('--- Calling external Delphi function from AST ---');
scope := TAst.CreateScope(FGScope);
scope.Define(
FEnvironment.RootScope.Define(
'delphiAdd',
function(const ArgNodes: TArray<TDataValue>): TDataValue
var
@@ -1209,7 +1148,7 @@ begin
callAst := TAst.FunctionCall(TAst.Identifier('delphiAdd'), [TAst.Constant(100), TAst.Constant(123)]);
resultValue := ExecuteAst(callAst, scope);
resultValue := ExecuteAst(callAst);
Memo1.Lines.Add(Format('Result from delphiAdd(100, 123): %s', [resultValue.ToString]));
UpdateScript;
end;
@@ -1246,7 +1185,7 @@ begin
Memo1.Lines.Clear;
Memo1.Lines.Add('--- Executing Corrected Upvalue Test ---');
resultValue := ExecuteAst(mainBlock, FGScope);
resultValue := ExecuteAst(mainBlock);
var res := resultValue.AsScalar.Value.AsInt64;
if res = 15 then
@@ -1347,7 +1286,7 @@ begin
try
try
// Execute the entire script block when it changes
var result := ExecuteAst(TAstScript.Parse(ScriptMemo.Lines.Text), FGScope);
var result := ExecuteAst(TAstScript.Parse(ScriptMemo.Lines.Text));
Memo1.Lines.Add(Format('Script executed. Final result: %s', [result.ToString]));
finally
ShowVizualization(14, 14);
@@ -1372,14 +1311,14 @@ begin
FWorkspace.Build(FCurrUnboundAst, TPointF.Create(X, Y));
//
// if FCurrAst <> nil then
// begin
// FWorkspace.Build(FCurrAst, TPointF.Create(X, Y));
// end
// else if FCurrUnboundAst <> nil then
// begin
// FWorkspace.Build(FCurrUnboundAst, TPointF.Create(X, Y));
// end;
// if FCurrExec <> nil then
// begin
// FWorkspace.Build(FCurrExec.CompiledAst, TPointF.Create(X, Y));
// end
// else if FCurrUnboundAst <> nil then
// begin
// FWorkspace.Build(FCurrUnboundAst, TPointF.Create(X, Y));
// end;
end;
end.