unit MainForm; interface uses System.SysUtils, System.Types, System.TypInfo, System.UITypes, System.Classes, System.Variants, System.Generics.Collections, System.Math, FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.Memo.Types, FMX.StdCtrls, FMX.ScrollBox, FMX.Memo, FMX.Controls.Presentation, Myc.Fmx.AstEditor, Myc.Fmx.AstEditor.Node, Myc.Fmx.AstEditor.Workspace, Myc.Data.Scalar, Myc.Data.Value, Myc.Ast.Nodes, Myc.Ast, Myc.Ast.Evaluator, Myc.Ast.Printer, FMX.Layouts, FMX.Objects, Myc.Ast.Debugger; type // A test record TOHLCV = record Timestamp: TDateTime; Open: Double; High: Double; Low: Double; Close: Double; Volume: Int64; end; TForm1 = class(TForm) Panel1: TPanel; Memo1: TMemo; Test1Button: TButton; Test2Button: TButton; PrettyPrintButton: TButton; RecursionButton: TButton; ShowScopeBox: TCheckBox; FibonacciButton: TButton; CrerateTriggerExampleButton: TButton; DoTriggerButton: TButton; DoTrigger2Button: TButton; Panel2: TPanel; ClearButton: TButton; FlowOnlyBox: TCheckBox; SeriesTestButton: TButton; OHLCButton: TButton; DebugBox: TCheckBox; FromJSONButton: TButton; ToJSONButton: TButton; ExternalFuncButton: TButton; procedure ClearButtonClick(Sender: TObject); procedure FormCreate(Sender: TObject); procedure CreateTriggerExampleButtonClick(Sender: TObject); procedure DoTrigger2ButtonClick(Sender: TObject); procedure DoTriggerButtonClick(Sender: TObject); procedure ExternalFuncButtonClick(Sender: TObject); procedure FibonacciButtonClick(Sender: TObject); procedure OHLCButtonClick(Sender: TObject); procedure PrettyPrintButtonClick(Sender: TObject); procedure RecursionButtonClick(Sender: TObject); procedure SeriesTestButtonClick(Sender: TObject); procedure Test1ButtonClick(Sender: TObject); procedure Test2ButtonClick(Sender: TObject); procedure FromJSONButtonClick(Sender: TObject); procedure ToJSONButtonClick(Sender: TObject); private // Stores the last AST generated by Test1 or Test2 button clicks. FLastAst: IAstNode; 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 { Public declarations } end; var Form1: TForm1; implementation uses Myc.Data.Scalar.JSON, Myc.Data.Decimal, Myc.Ast.Binding, System.Diagnostics, // For TStopwatch Myc.Ast.Json; // For TAstJson serialization {$R *.fmx} procedure TForm1.ClearButtonClick(Sender: TObject); begin FWorkspace.DeleteChildren; FWorkspace.Repaint; // Create and prepare the global scope once FGScope := TAst.CreateScope(nil); RegisterNativeFunctions(FGScope); end; function TForm1.ExecuteAst(const ANode: IAstNode; const AParentScope: IExecutionScope): TDataValue; 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. var scriptScope := TAstBinder.Bind(ANode, AParentScope).CreateScope(AParentScope); var visitor := CreateVisitor(scriptScope); Result := ANode.Accept(visitor); end; procedure TForm1.FormCreate(Sender: TObject); begin FWorkspace := TAuraWorkspace.Create(Panel2); FWorkspace.Parent := Panel2; FWorkspace.Align := TAlignLayout.Client; FWorkspace.ClipChildren := true; FWorkspace.OnMouseDown := WorkspaceMouseDown; ClearButtonClick(Self); end; procedure TForm1.FibonacciButtonClick(Sender: TObject); var root: IAstNode; result: TDataValue; sw: TStopwatch; begin Memo1.Lines.Clear; Memo1.Lines.Add('--- Recursive fib with AST---'); sw := TStopwatch.StartNew; root := TAst.Block( [ TAst.VarDecl( TAst.Identifier('fib'), TAst.LambdaExpr( [TAst.Identifier('n')], TAst.TernaryExpr( TAst.BinaryExpr(TAst.Identifier('n'), boLess, TAst.Constant(TScalar.FromInt64(2))), TAst.Identifier('n'), TAst.BinaryExpr( TAst.FunctionCall( TAst.Identifier('Self'), [TAst.BinaryExpr(TAst.Identifier('n'), boSubtract, TAst.Constant(TScalar.FromInt64(1)))] ), boAdd, TAst.FunctionCall( TAst.Identifier('Self'), [TAst.BinaryExpr(TAst.Identifier('n'), boSubtract, TAst.Constant(TScalar.FromInt64(2)))] ) ) ) ) ), TAst.FunctionCall(TAst.Identifier('fib'), [TAst.Constant(TScalar.FromInt64(30))]) ] ); FLastAst := root; // Execute with FGScope as parent. The helper function handles debug/prod switching. result := ExecuteAst(root, FGScope); sw.Stop; Memo1.Lines.Add(Format('Result: fib(30) %s (calculated in %d ms)', [result.ToString, sw.ElapsedMilliseconds])); end; procedure TForm1.PrettyPrintButtonClick(Sender: TObject); var visitor: TPrettyPrintVisitor; begin Memo1.Lines.Clear; Memo1.Lines.Add('--- AST Pretty Print ---'); if not Assigned(FLastAst) then begin Memo1.Lines.Add('No AST has been generated yet.'); exit; end; visitor := TPrettyPrintVisitor.Create; FLastAst.Accept(visitor); Memo1.Lines.Add(visitor.GetResult); end; procedure TForm1.RecursionButtonClick(Sender: TObject); var root: IAstNode; result: TDataValue; sw: TStopwatch; begin Memo1.Lines.Clear; Memo1.Lines.Add('--- Recursive factorial(20) ---'); sw := TStopwatch.StartNew; root := TAst.Block( [ TAst.VarDecl( TAst.Identifier('factorial'), TAst.LambdaExpr( [TAst.Identifier('n')], TAst.TernaryExpr( TAst.BinaryExpr(TAst.Identifier('n'), boLess, TAst.Constant(TScalar.FromInt64(2))), TAst.Constant(TScalar.FromInt64(1)), TAst.BinaryExpr( TAst.Identifier('n'), boMultiply, TAst.FunctionCall( TAst.Identifier('Self'), [TAst.BinaryExpr(TAst.Identifier('n'), boSubtract, TAst.Constant(TScalar.FromInt64(1)))] ) ) ) ) ), TAst.FunctionCall(TAst.Identifier('factorial'), [TAst.Constant(TScalar.FromInt64(20))]) ] ); FLastAst := root; // Execute with FGScope as parent. result := ExecuteAst(root, FGScope); sw.Stop; Memo1.Lines.Add(Format('Result: %s (calculated in %d ms)', [result.ToString, sw.ElapsedMilliseconds])); end; procedure TForm1.SeriesTestButtonClick(Sender: TObject); var ast, callAst: IAstNode; resultValue: TDataValue; series: TScalarRecordSeries; recordDef: TScalarRecordDefinition; i: Integer; scope: IExecutionScope; begin Memo1.Lines.Clear; Memo1.Lines.Add('--- Series Test ---'); scope := TAst.CreateScope(FGScope); recordDef := TRttiAstHelper.JsonToRecordDefinition(TRttiAstHelper.RecordDefinitionToJson); series := TScalarRecordSeries.Create(recordDef); for i := 0 to 4 do begin series.Add( TScalarRecord.Create( recordDef, [ TScalarValue.FromDateTime(Now + i), TScalarValue.FromDouble(100.0 + i), TScalarValue.FromDouble(105.0 + i), TScalarValue.FromDouble(98.0 + i), TScalarValue.FromDouble(102.0 + i), TScalarValue.FromInt64(10000 * (i + 1)) ] ) ); end; scope.Define('ohlcvSeries', TDataValue.FromRecordSeries(series)); ast := TAst.LambdaExpr( [], TAst.Block( [ TAst.VarDecl( TAst.Identifier('closeColumn'), TAst.MemberAccess(TAst.Identifier('ohlcvSeries'), TAst.Identifier('Close')) ), TAst.Indexer(TAst.Identifier('closeColumn'), TAst.Constant(TScalar.FromInt64(1))) ] ) ); FLastAst := ast; callAst := TAst.FunctionCall(ast, []); resultValue := ExecuteAst(callAst, scope); Memo1.Lines.Add(Format('Result of script: %s', [resultValue.ToString])); end; procedure TForm1.Test1ButtonClick(Sender: TObject); var main, callAst: IAstNode; result: TDataValue; sw: TStopwatch; begin Memo1.Lines.Clear; Memo1.Lines.Add('--- Simple AST Execution ---'); sw := TStopwatch.StartNew; main := TAst.LambdaExpr( [], TAst.Block( [ TAst.VarDecl(TAst.Identifier('a'), TAst.Constant(TScalar.FromInt64(10))), TAst.VarDecl( TAst.Identifier('b'), TAst.BinaryExpr(TAst.Identifier('a'), boMultiply, TAst.Constant(TScalar.FromInt64(2))) ), TAst.BinaryExpr(TAst.Identifier('a'), boAdd, TAst.Identifier('b')) ] ) ); FLastAst := main; callAst := TAst.FunctionCall(main, []); result := ExecuteAst(callAst, FGScope); sw.Stop; Memo1.Lines.Add(Format('Result: %s (calculated in %d ms)', [result.ToString, sw.ElapsedMilliseconds])); end; procedure TForm1.Test2ButtonClick(Sender: TObject); var root: IAstNode; result: TDataValue; sw: TStopwatch; begin Memo1.Lines.Clear; Memo1.Lines.Add('--- Factory Pattern Demo ---'); sw := TStopwatch.StartNew; root := TAst.Block( [ TAst.VarDecl( TAst.Identifier('createStrategyInstance'), TAst.LambdaExpr( [TAst.Identifier('offset')], TAst.Block( [ TAst.VarDecl(TAst.Identifier('baseValue'), TAst.Constant(TScalar.FromInt64(100))), TAst.BinaryExpr(TAst.Identifier('baseValue'), boAdd, TAst.Identifier('offset')) ] ) ) ), TAst.FunctionCall(TAst.Identifier('createStrategyInstance'), [TAst.Constant(TScalar.FromInt64(20))]), TAst.FunctionCall(TAst.Identifier('createStrategyInstance'), [TAst.Constant(TScalar.FromInt64(55))]) ] ); FLastAst := root; result := ExecuteAst(root, FGScope); sw.Stop; Memo1.Lines.Add(Format('Result of the final expression: %s (calculated in %d ms)', [result.ToString, sw.ElapsedMilliseconds])); end; procedure TForm1.WorkspaceMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Single); begin if Button <> TMouseButton.mbMiddle then exit; if FLastAst <> nil then begin var visu := TVisualizationMode.vmDetailed; if FlowOnlyBox.IsChecked then visu := TVisualizationMode.vmControlFlow; var descr := TAstBinder.Bind(FLastAst, FGScope); FWorkspace.BuildTree(FLastAst, descr.CreateScope(FGScope), TPointF.Create(X, Y), visu); end; end; procedure TForm1.OHLCButtonClick(Sender: TObject); const numRecs = 1000; lookback = 50; smaSlowLength = 20; smaFastLength = 10; var scope: IExecutionScope; begin Memo1.Lines.Clear; Memo1.Lines.Add(Format('--- Simulating O(1) SMA Crossover Strategy for %d ticks ---', [numRecs])); var sw := TStopwatch.StartNew; var setupAst := TAst.Block( [ TAst.VarDecl( TAst.Identifier('CreateSMA'), TAst.LambdaExpr( [TAst.Identifier('len')], TAst.Block( [ TAst.VarDecl(TAst.Identifier('sum'), TAst.Constant(TScalar.FromDouble(0.0))), TAst.VarDecl(TAst.Identifier('count'), TAst.Constant(TScalar.FromInt64(0))), TAst.LambdaExpr( [TAst.Identifier('series'), TAst.Identifier('val')], TAst.Block( [ TAst.Assign( TAst.Identifier('sum'), TAst.BinaryExpr(TAst.Identifier('sum'), boAdd, TAst.Identifier('val')) ), TAst.Assign( TAst.Identifier('count'), TAst.BinaryExpr(TAst.Identifier('count'), boAdd, TAst.Constant(TScalar.FromInt64(1))) ), TAst.IfExpr( TAst.BinaryExpr(TAst.Identifier('count'), boGreater, TAst.Identifier('len')), TAst.Assign( TAst.Identifier('sum'), TAst.BinaryExpr( TAst.Identifier('sum'), boSubtract, TAst.Indexer(TAst.Identifier('series'), TAst.Identifier('len')) ) ), nil ), TAst.BinaryExpr( TAst.Identifier('sum'), boDivide, TAst.TernaryExpr( TAst.BinaryExpr(TAst.Identifier('count'), boLess, TAst.Identifier('len')), TAst.Identifier('count'), TAst.Identifier('len') ) ) ] ) ) ] ) ) ), TAst.VarDecl( TAst.Identifier('smaFast'), TAst.FunctionCall(TAst.Identifier('CreateSMA'), [TAst.Constant(TScalar.FromInt64(smaFastLength))]) ), TAst.VarDecl( TAst.Identifier('smaSlow'), TAst.FunctionCall(TAst.Identifier('CreateSMA'), [TAst.Constant(TScalar.FromInt64(smaSlowLength))]) ), TAst.VarDecl( TAst.Identifier('maCrossStrategy'), TAst.LambdaExpr( [TAst.Identifier('ohlcv')], TAst.Block( [ TAst.VarDecl( TAst.Identifier('closeSeries'), TAst.MemberAccess(TAst.Identifier('ohlcv'), TAst.Identifier('Close')) ), TAst.VarDecl( TAst.Identifier('currentClose'), TAst.Indexer(TAst.Identifier('closeSeries'), TAst.Constant(TScalar.FromInt64(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'), boGreater, TAst.Identifier('valSmaSlow')), TAst.Constant(TScalar.FromInt64(1)), TAst.Constant(TScalar.FromInt64(-1)) ) ] ) ) ) ] ); FLastAst := setupAst; // 1. Bind and execute the setup script. scope := TAstBinder.Bind(setupAst, FGScope).CreateScope(FGScope); // This is a temporary visitor just for the setup execution. 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); var currentSeriesIdent := TAst.Identifier('current_series'); var callAst := TAst.FunctionCall(TAst.Identifier('maCrossStrategy'), [currentSeriesIdent]); // 3. Re-bind the scope with the new AST. This creates the FINAL scope for the loop. scope := TAstBinder.Bind(callAst, scope).CreateScope(scope); var seriesAddress := currentSeriesIdent.Address; var visitor := CreateVisitor(scope); // 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 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 scope[seriesAddress] := 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; end; end; sw.Stop; Memo1.Lines.Add('--- Simulation Finished ---'); Memo1.Lines.Add(Format('Total time: %d ms', [sw.ElapsedMilliseconds])); end; var TriggerScope: IExecutionScope; procedure TForm1.CreateTriggerExampleButtonClick(Sender: TObject); var blk: IAstNode; begin Memo1.Lines.Clear; Memo1.Lines.Add('--- Creating Trigger Blueprint ---'); blk := TAst.Block( [ TAst.VarDecl(TAst.Identifier('X'), TAst.Constant(TScalar.FromInt64(0))), TAst.VarDecl( TAst.Identifier('tickHandler'), TAst.LambdaExpr( [TAst.Identifier('summand')], TAst.Assign(TAst.Identifier('X'), TAst.BinaryExpr(TAst.Identifier('X'), boAdd, TAst.Identifier('summand'))) ) ) ] ); TriggerScope := TAstBinder.Bind(blk, FGScope).CreateScope(FGScope); // This case is simple enough to just inline the logic from ExecuteAst var visitor := CreateVisitor(TriggerScope); blk.Accept(visitor); FLastAst := blk; Memo1.Lines.Add('Variable "X" and function "tickHandler" defined in persistent scope.'); 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; begin callAst := TAst.FunctionCall(TAst.Identifier('tickHandler'), [TAst.Constant(TScalar.FromInt64(1))]); FLastAst := callAst; var X := ExecuteAst(callAst, TriggerScope); Memo1.Lines.Add(Format('Tick(1)! New value of X: %s', [X.ToString])); end; procedure TForm1.DoTrigger2ButtonClick(Sender: TObject); var callAst: IFunctionCallNode; begin callAst := TAst.FunctionCall(TAst.Identifier('tickHandler'), [TAst.Constant(TScalar.FromInt64(2))]); FLastAst := callAst; var X := ExecuteAst(callAst, TriggerScope); Memo1.Lines.Add(Format('Tick(2)! New value of X: %s', [X.ToString])); 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( 'delphiAdd', function(const ArgNodes: TArray): TDataValue var val1, val2: Int64; begin if Length(ArgNodes) <> 2 then raise Exception.Create('delphiAdd requires exactly 2 arguments.'); val1 := ArgNodes[0].AsScalar.Value.AsInt64; val2 := ArgNodes[1].AsScalar.Value.AsInt64; Result := TScalar.FromInt64(val1 + val2); end ); callAst := TAst.FunctionCall(TAst.Identifier('delphiAdd'), [TAst.Constant(TScalar.FromInt64(100)), TAst.Constant(TScalar.FromInt64(23))]); FLastAst := callAst; resultValue := ExecuteAst(callAst, scope); Memo1.Lines.Add(Format('Result from delphiAdd(100, 23): %s', [resultValue.ToString])); end; procedure TForm1.FromJSONButtonClick(Sender: TObject); var jsonString: string; begin Memo1.Lines.BeginUpdate; try jsonString := Memo1.Lines.Text; Memo1.Lines.Clear; if jsonString.IsEmpty then begin Memo1.Lines.Add('Memo is empty. Please paste an AST JSON string.'); exit; end; try FLastAst := TAstJson.Deserialize(jsonString); Memo1.Lines.Add('AST deserialized successfully from JSON.'); Memo1.Lines.Add('You can now visualize it (Middle Mouse Click) or pretty-print it.'); except on E: Exception do begin FLastAst := nil; Memo1.Lines.Add('Error deserializing AST from JSON:'); Memo1.Lines.Add(E.Message); Memo1.Lines.Add('--- Original JSON ---'); Memo1.Lines.Text := Memo1.Lines.Text + sLineBreak + jsonString; end; end; finally Memo1.Lines.EndUpdate; end; end; procedure TForm1.ToJSONButtonClick(Sender: TObject); var jsonString: string; begin Memo1.Lines.Clear; if not Assigned(FLastAst) then begin Memo1.Lines.Add('No AST available to serialize. Please generate one first.'); exit; end; try jsonString := TAstJson.Serialize(FLastAst); Memo1.Lines.Text := jsonString; except on E: Exception do begin Memo1.Lines.Add('Error serializing AST to JSON:'); Memo1.Lines.Add(E.Message); end; end; end; end.