Global data value refactoring + 1st scripting version

This commit is contained in:
Michael Schimmel
2025-09-20 18:30:32 +02:00
parent 09bd25b318
commit 00f5861148
17 changed files with 1430 additions and 2742 deletions
+2 -2
View File
@@ -9,7 +9,6 @@ uses
Myc.Ast.Nodes in '..\Src\AST\Myc.Ast.Nodes.pas', Myc.Ast.Nodes in '..\Src\AST\Myc.Ast.Nodes.pas',
Myc.Ast.Scope in '..\Src\AST\Myc.Ast.Scope.pas', Myc.Ast.Scope in '..\Src\AST\Myc.Ast.Scope.pas',
Myc.Fmx.AstEditor in 'Myc.Fmx.AstEditor.pas', Myc.Fmx.AstEditor in 'Myc.Fmx.AstEditor.pas',
Myc.Ast.ViewModel in '..\Src\AST\Myc.Ast.ViewModel.pas',
Myc.Data.Value in 'Myc.Data.Value.pas', Myc.Data.Value in 'Myc.Data.Value.pas',
Myc.Ast.Debugger in '..\Src\AST\Myc.Ast.Debugger.pas', Myc.Ast.Debugger in '..\Src\AST\Myc.Ast.Debugger.pas',
Myc.Ast.Traverser in '..\Src\AST\Myc.Ast.Traverser.pas', Myc.Ast.Traverser in '..\Src\AST\Myc.Ast.Traverser.pas',
@@ -20,7 +19,8 @@ uses
Myc.Fmx.AstEditor.Node in 'Myc.Fmx.AstEditor.Node.pas', Myc.Fmx.AstEditor.Node in 'Myc.Fmx.AstEditor.Node.pas',
Myc.Fmx.AstEditor.Workspace in 'Myc.Fmx.AstEditor.Workspace.pas', Myc.Fmx.AstEditor.Workspace in 'Myc.Fmx.AstEditor.Workspace.pas',
Myc.Fmx.AstEditor.Text in 'Myc.Fmx.AstEditor.Text.pas', Myc.Fmx.AstEditor.Text in 'Myc.Fmx.AstEditor.Text.pas',
Myc.Utils in '..\Src\Myc.Utils.pas'; Myc.Utils in '..\Src\Myc.Utils.pas',
Myc.Ast.Script in '..\Src\AST\Myc.Ast.Script.pas';
{$R *.res} {$R *.res}
+1 -1
View File
@@ -140,7 +140,6 @@
<DCCReference Include="..\Src\AST\Myc.Ast.Nodes.pas"/> <DCCReference Include="..\Src\AST\Myc.Ast.Nodes.pas"/>
<DCCReference Include="..\Src\AST\Myc.Ast.Scope.pas"/> <DCCReference Include="..\Src\AST\Myc.Ast.Scope.pas"/>
<DCCReference Include="Myc.Fmx.AstEditor.pas"/> <DCCReference Include="Myc.Fmx.AstEditor.pas"/>
<DCCReference Include="..\Src\AST\Myc.Ast.ViewModel.pas"/>
<DCCReference Include="Myc.Data.Value.pas"/> <DCCReference Include="Myc.Data.Value.pas"/>
<DCCReference Include="..\Src\AST\Myc.Ast.Debugger.pas"/> <DCCReference Include="..\Src\AST\Myc.Ast.Debugger.pas"/>
<DCCReference Include="..\Src\AST\Myc.Ast.Traverser.pas"/> <DCCReference Include="..\Src\AST\Myc.Ast.Traverser.pas"/>
@@ -152,6 +151,7 @@
<DCCReference Include="Myc.Fmx.AstEditor.Workspace.pas"/> <DCCReference Include="Myc.Fmx.AstEditor.Workspace.pas"/>
<DCCReference Include="Myc.Fmx.AstEditor.Text.pas"/> <DCCReference Include="Myc.Fmx.AstEditor.Text.pas"/>
<DCCReference Include="..\Src\Myc.Utils.pas"/> <DCCReference Include="..\Src\Myc.Utils.pas"/>
<DCCReference Include="..\Src\AST\Myc.Ast.Script.pas"/>
<BuildConfiguration Include="Base"> <BuildConfiguration Include="Base">
<Key>Base</Key> <Key>Base</Key>
</BuildConfiguration> </BuildConfiguration>
+40 -6
View File
@@ -3,7 +3,7 @@ object Form1: TForm1
Top = 0 Top = 0
Caption = 'Form1' Caption = 'Form1'
ClientHeight = 883 ClientHeight = 883
ClientWidth = 925 ClientWidth = 1394
FormFactor.Width = 320 FormFactor.Width = 320
FormFactor.Height = 480 FormFactor.Height = 480
FormFactor.Devices = [Desktop] FormFactor.Devices = [Desktop]
@@ -103,7 +103,7 @@ object Form1: TForm1
Position.Y = 8.000000000000000000 Position.Y = 8.000000000000000000
TabOrder = 11 TabOrder = 11
Text = 'Flow only' Text = 'Flow only'
OnChange = ClearButtonClick OnChange = FlowOnlyBoxChange
end end
object SeriesTestButton: TButton object SeriesTestButton: TButton
Position.X = 24.000000000000000000 Position.X = 24.000000000000000000
@@ -189,8 +189,8 @@ object Form1: TForm1
end end
object Panel2: TPanel object Panel2: TPanel
Align = Client Align = Client
Size.Width = 796.000000000000000000 Size.Width = 784.000000000000000000
Size.Height = 616.000000000000000000 Size.Height = 609.000000000000000000
Size.PlatformDefault = False Size.PlatformDefault = False
TabOrder = 3 TabOrder = 3
end end
@@ -202,11 +202,45 @@ object Form1: TForm1
Align = Bottom Align = Bottom
Position.X = 129.000000000000000000 Position.X = 129.000000000000000000
Position.Y = 616.000000000000000000 Position.Y = 616.000000000000000000
Size.Width = 796.000000000000000000 Size.Width = 1265.000000000000000000
Size.Height = 267.000000000000000000 Size.Height = 267.000000000000000000
Size.PlatformDefault = False Size.PlatformDefault = False
TabOrder = 2 TabOrder = 2
Viewport.Width = 792.000000000000000000 Viewport.Width = 1261.000000000000000000
Viewport.Height = 263.000000000000000000 Viewport.Height = 263.000000000000000000
end end
object Splitter1: TSplitter
Align = Bottom
Cursor = crVSplit
MinSize = 20.000000000000000000
Position.X = 129.000000000000000000
Position.Y = 609.000000000000000000
Size.Width = 1265.000000000000000000
Size.Height = 7.000000000000000000
Size.PlatformDefault = False
end
object Splitter2: TSplitter
Align = Right
Cursor = crHSplit
MinSize = 20.000000000000000000
Position.X = 913.000000000000000000
Size.Width = 7.000000000000000000
Size.Height = 609.000000000000000000
Size.PlatformDefault = False
end
object ScriptMemo: TMemo
Touch.InteractiveGestures = [Pan, LongTap, DoubleTap]
DataDetectorTypes = []
StyledSettings = [Size, Style, FontColor]
TextSettings.Font.Family = 'Consolas'
OnChangeTracking = ScriptMemoChange
Align = Right
Position.X = 920.000000000000000000
Size.Width = 474.000000000000000000
Size.Height = 609.000000000000000000
Size.PlatformDefault = False
TabOrder = 6
Viewport.Width = 470.000000000000000000
Viewport.Height = 605.000000000000000000
end
end end
+199 -141
View File
@@ -31,6 +31,7 @@ uses
Myc.Data.Decimal, Myc.Data.Decimal,
Myc.Ast.Binding, Myc.Ast.Binding,
Myc.Ast.RTL, Myc.Ast.RTL,
Myc.Ast.Script,
FMX.Layouts, FMX.Layouts,
FMX.Objects, FMX.Objects,
Myc.Ast.Debugger, Myc.Ast.Debugger,
@@ -74,6 +75,9 @@ type
DumpButton: TButton; DumpButton: TButton;
FailingUpvalueButton: TButton; FailingUpvalueButton: TButton;
TailCallButten: TButton; TailCallButten: TButton;
Splitter1: TSplitter;
Splitter2: TSplitter;
ScriptMemo: TMemo;
procedure InnerLambdaButtonClick(Sender: TObject); procedure InnerLambdaButtonClick(Sender: TObject);
procedure ClearButtonClick(Sender: TObject); procedure ClearButtonClick(Sender: TObject);
procedure FormCreate(Sender: TObject); procedure FormCreate(Sender: TObject);
@@ -84,6 +88,7 @@ type
procedure ExternalFuncButtonClick(Sender: TObject); procedure ExternalFuncButtonClick(Sender: TObject);
procedure FailingUpvalueButtonClick(Sender: TObject); procedure FailingUpvalueButtonClick(Sender: TObject);
procedure FibonacciButtonClick(Sender: TObject); procedure FibonacciButtonClick(Sender: TObject);
procedure FlowOnlyBoxChange(Sender: TObject);
procedure OHLCButtonClick(Sender: TObject); procedure OHLCButtonClick(Sender: TObject);
procedure PrettyPrintButtonClick(Sender: TObject); procedure PrettyPrintButtonClick(Sender: TObject);
procedure RecursionButtonClick(Sender: TObject); procedure RecursionButtonClick(Sender: TObject);
@@ -91,6 +96,7 @@ type
procedure Test1ButtonClick(Sender: TObject); procedure Test1ButtonClick(Sender: TObject);
procedure Test2ButtonClick(Sender: TObject); procedure Test2ButtonClick(Sender: TObject);
procedure FromJSONButtonClick(Sender: TObject); procedure FromJSONButtonClick(Sender: TObject);
procedure ScriptMemoChange(Sender: TObject);
procedure TailCallButtenClick(Sender: TObject); procedure TailCallButtenClick(Sender: TObject);
procedure ToJSONButtonClick(Sender: TObject); procedure ToJSONButtonClick(Sender: TObject);
private private
@@ -98,10 +104,14 @@ type
FLastAst: IAstNode; FLastAst: IAstNode;
FGScope: IExecutionScope; FGScope: IExecutionScope;
FWorkspace: TAuraWorkspace; FWorkspace: TAuraWorkspace;
FTriggerScope: IExecutionScope;
FScriptUpdate: Boolean;
procedure WorkspaceMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Single); procedure WorkspaceMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Single);
function CreateVisitor(Scope: IExecutionScope): IAstVisitor; function CreateVisitor(Scope: IExecutionScope): IAstVisitor;
// Helper function to encapsulate the Bind -> Evaluate pattern // Helper function to encapsulate the Bind -> Evaluate pattern
function ExecuteAst(const ANode: IAstNode; const AParentScope: IExecutionScope): TDataValue; function ExecuteAst(const ANode: IAstNode; const AParentScope: IExecutionScope): TDataValue;
procedure UpdateScript;
procedure ShowVizualization(X, Y: Single);
public public
{ Public declarations } { Public declarations }
end; end;
@@ -138,7 +148,7 @@ begin
TAst.Block( TAst.Block(
[ [
// var x = 10; // var x = 10;
TAst.VarDecl(TAst.Identifier('x'), TAst.Constant(TScalar.FromInteger(10))), TAst.VarDecl(TAst.Identifier('x'), TAst.Constant(10)),
// var inner = lambda() { ... }; // var inner = lambda() { ... };
TAst.VarDecl( TAst.VarDecl(
TAst.Identifier('inner'), TAst.Identifier('inner'),
@@ -154,11 +164,7 @@ begin
// x = x + 5; // x = x + 5;
TAst.Assign( TAst.Assign(
TAst.Identifier('x'), TAst.Identifier('x'),
TAst.BinaryExpr( TAst.BinaryExpr(TAst.Identifier('x'), TScalar.TBinaryOp.Add, TAst.Constant(5))
TAst.Identifier('x'),
boAdd,
TAst.Constant(TScalar.FromInteger(5))
)
) )
) )
), ),
@@ -185,7 +191,8 @@ begin
resultValue := ExecuteAst(mainBlock, FGScope); resultValue := ExecuteAst(mainBlock, FGScope);
Assert(TScalar.FromInteger(15) = resultValue.AsScalar, 'The final result should be 15, but is ' + resultValue.AsScalar.ToString); Assert(TScalar.FromInt64(15) = resultValue.AsScalar, 'The final result should be 15, but is ' + resultValue.AsScalar.ToString);
UpdateScript;
end; end;
procedure TForm1.ClearButtonClick(Sender: TObject); procedure TForm1.ClearButtonClick(Sender: TObject);
@@ -224,27 +231,31 @@ begin
[TAst.Identifier('len')], [TAst.Identifier('len')],
TAst.Block( TAst.Block(
[ [
TAst.VarDecl(TAst.Identifier('sum'), TAst.Constant(TScalar.FromDouble(0.0))), TAst.VarDecl(TAst.Identifier('sum'), TAst.Constant(0.0)),
TAst.VarDecl(TAst.Identifier('count'), TAst.Constant(TScalar.FromInt64(0))), TAst.VarDecl(TAst.Identifier('count'), TAst.Constant(0)),
TAst.LambdaExpr( TAst.LambdaExpr(
[TAst.Identifier('series'), TAst.Identifier('val')], [TAst.Identifier('series'), TAst.Identifier('val')],
TAst.Block( TAst.Block(
[ [
TAst.Assign( TAst.Assign(
TAst.Identifier('sum'), TAst.Identifier('sum'),
TAst.BinaryExpr(TAst.Identifier('sum'), boAdd, TAst.Identifier('val')) TAst.BinaryExpr(TAst.Identifier('sum'), TScalar.TBinaryOp.Add, TAst.Identifier('val'))
), ),
TAst.Assign( TAst.Assign(
TAst.Identifier('count'), TAst.Identifier('count'),
TAst.BinaryExpr(TAst.Identifier('count'), boAdd, TAst.Constant(TScalar.FromInt64(1))) TAst.BinaryExpr(TAst.Identifier('count'), TScalar.TBinaryOp.Add, TAst.Constant(1))
), ),
TAst.Assign( TAst.Assign(
TAst.Identifier('sum'), TAst.Identifier('sum'),
TAst.TernaryExpr( TAst.TernaryExpr(
TAst.BinaryExpr(TAst.Identifier('count'), boGreater, TAst.Identifier('len')), TAst.BinaryExpr(
TAst.Identifier('count'),
TScalar.TBinaryOp.Greater,
TAst.Identifier('len')
),
TAst.BinaryExpr( TAst.BinaryExpr(
TAst.Identifier('sum'), TAst.Identifier('sum'),
boSubtract, TScalar.TBinaryOp.Subtract,
TAst.Indexer(TAst.Identifier('series'), TAst.Identifier('len')) TAst.Indexer(TAst.Identifier('series'), TAst.Identifier('len'))
), ),
TAst.Identifier('sum') TAst.Identifier('sum')
@@ -252,9 +263,9 @@ begin
), ),
TAst.BinaryExpr( TAst.BinaryExpr(
TAst.Identifier('sum'), TAst.Identifier('sum'),
boDivide, TScalar.TBinaryOp.Divide,
TAst.TernaryExpr( TAst.TernaryExpr(
TAst.BinaryExpr(TAst.Identifier('count'), boLess, TAst.Identifier('len')), TAst.BinaryExpr(TAst.Identifier('count'), TScalar.TBinaryOp.Less, TAst.Identifier('len')),
TAst.Identifier('count'), TAst.Identifier('count'),
TAst.Identifier('len') TAst.Identifier('len')
) )
@@ -280,7 +291,7 @@ var
result: TDataValue; result: TDataValue;
sw: TStopwatch; sw: TStopwatch;
begin begin
// This script defines a naive, slow recursive 'fib' function. // This script defines a naive, slow recursive 'fib' function (on purpose!).
// The lambda captures its own name ('fib') from the parent scope to perform recursion. // The lambda captures its own name ('fib') from the parent scope to perform recursion.
var fibAst := var fibAst :=
TAst.Block( TAst.Block(
@@ -293,19 +304,19 @@ begin
TAst.LambdaExpr( TAst.LambdaExpr(
[TAst.Identifier('n')], [TAst.Identifier('n')],
TAst.TernaryExpr( TAst.TernaryExpr(
TAst.BinaryExpr(TAst.Identifier('n'), boLess, TAst.Constant(TScalar.FromInt64(2))), TAst.BinaryExpr(TAst.Identifier('n'), TScalar.TBinaryOp.Less, TAst.Constant(2)),
TAst.Identifier('n'), TAst.Identifier('n'),
TAst.BinaryExpr( TAst.BinaryExpr(
// Naive recursive call using the variable name 'fib' // Naive recursive call using the variable name 'fib'
TAst.FunctionCall( TAst.FunctionCall(
TAst.Identifier('fib'), TAst.Identifier('fib'),
[TAst.BinaryExpr(TAst.Identifier('n'), boSubtract, TAst.Constant(TScalar.FromInt64(1)))] [TAst.BinaryExpr(TAst.Identifier('n'), TScalar.TBinaryOp.Subtract, TAst.Constant(1))]
), ),
boAdd, TScalar.TBinaryOp.Add,
// Second naive recursive call // Second naive recursive call
TAst.FunctionCall( TAst.FunctionCall(
TAst.Identifier('fib'), TAst.Identifier('fib'),
[TAst.BinaryExpr(TAst.Identifier('n'), boSubtract, TAst.Constant(TScalar.FromInt64(2)))] [TAst.BinaryExpr(TAst.Identifier('n'), TScalar.TBinaryOp.Subtract, TAst.Constant(2))]
) )
) )
) )
@@ -324,9 +335,10 @@ begin
sw := TStopwatch.StartNew; sw := TStopwatch.StartNew;
// The script to execute is just the call to the function. // The script to execute is just the call to the function.
root := TAst.FunctionCall(TAst.Identifier('fib'), [TAst.Constant(TScalar.FromInt64(25))]); root := TAst.FunctionCall(TAst.Identifier('fib'), [TAst.Constant(25)]);
FLastAst := root; FLastAst := root;
// Execute within the scope where 'fib' is defined. // Execute within the scope where 'fib' is defined.
result := ExecuteAst(root, fibScope); result := ExecuteAst(root, fibScope);
@@ -343,7 +355,7 @@ begin
// Create a memoized version by calling the RTL function on 'fib' // Create a memoized version by calling the RTL function on 'fib'
TAst.Assign(TAst.Identifier('fib'), TAst.FunctionCall(TAst.Identifier('Memoize'), [TAst.Identifier('fib')])), TAst.Assign(TAst.Identifier('fib'), TAst.FunctionCall(TAst.Identifier('Memoize'), [TAst.Identifier('fib')])),
// Call the new, memoized function // Call the new, memoized function
TAst.FunctionCall(TAst.Identifier('fib'), [TAst.Constant(TScalar.FromInt64(25))]) TAst.FunctionCall(TAst.Identifier('fib'), [TAst.Constant(25)])
] ]
); );
@@ -352,6 +364,7 @@ begin
sw.Stop; sw.Stop;
Memo1.Lines.Add(Format('Result: fib(30) %s (calculated in %d ms)', [result.ToString, sw.ElapsedMilliseconds])); Memo1.Lines.Add(Format('Result: fib(30) %s (calculated in %d ms)', [result.ToString, sw.ElapsedMilliseconds]));
UpdateScript;
end; end;
procedure TForm1.PrettyPrintButtonClick(Sender: TObject); procedure TForm1.PrettyPrintButtonClick(Sender: TObject);
@@ -392,12 +405,12 @@ begin
TAst.LambdaExpr( TAst.LambdaExpr(
[TAst.Identifier('n'), TAst.Identifier('acc')], [TAst.Identifier('n'), TAst.Identifier('acc')],
TAst.TernaryExpr( TAst.TernaryExpr(
TAst.BinaryExpr(TAst.Identifier('n'), boLessOrEqual, TAst.Constant(TScalar.FromInt64(1))), TAst.BinaryExpr(TAst.Identifier('n'), TScalar.TBinaryOp.LessOrEqual, TAst.Constant(1)),
TAst.Identifier('acc'), // Base case: return the accumulator TAst.Identifier('acc'), // Base case: return the accumulator
TAst.Recur( // Tail-recursive step TAst.Recur( // Tail-recursive step
[ [
TAst.BinaryExpr(TAst.Identifier('n'), boSubtract, TAst.Constant(TScalar.FromInt64(1))), TAst.BinaryExpr(TAst.Identifier('n'), TScalar.TBinaryOp.Subtract, TAst.Constant(1)),
TAst.BinaryExpr(TAst.Identifier('acc'), boMultiply, TAst.Identifier('n')) TAst.BinaryExpr(TAst.Identifier('acc'), TScalar.TBinaryOp.Multiply, TAst.Identifier('n'))
] ]
) )
) )
@@ -408,11 +421,11 @@ begin
TAst.Identifier('factorial'), TAst.Identifier('factorial'),
TAst.LambdaExpr( TAst.LambdaExpr(
[TAst.Identifier('n')], [TAst.Identifier('n')],
TAst.FunctionCall(TAst.Identifier('fact_iter'), [TAst.Identifier('n'), TAst.Constant(TScalar.FromInt64(1))]) TAst.FunctionCall(TAst.Identifier('fact_iter'), [TAst.Identifier('n'), TAst.Constant(1)])
) )
), ),
// Call the main function // Call the main function
TAst.FunctionCall(TAst.Identifier('factorial'), [TAst.Constant(TScalar.FromInt64(20))]) TAst.FunctionCall(TAst.Identifier('factorial'), [TAst.Constant(20)])
] ]
); );
@@ -422,6 +435,7 @@ begin
sw.Stop; sw.Stop;
Memo1.Lines.Add(Format('Result: %s (calculated in %d ms)', [result.ToString, sw.ElapsedMilliseconds])); Memo1.Lines.Add(Format('Result: %s (calculated in %d ms)', [result.ToString, sw.ElapsedMilliseconds]));
UpdateScript;
end; end;
procedure TForm1.SeriesTestButtonClick(Sender: TObject); procedure TForm1.SeriesTestButtonClick(Sender: TObject);
@@ -432,6 +446,7 @@ var
recordDef: TScalarRecordDefinition; recordDef: TScalarRecordDefinition;
i: Integer; i: Integer;
scope: IExecutionScope; scope: IExecutionScope;
values: TArray<TScalar.TValue>;
begin begin
Memo1.Lines.Clear; Memo1.Lines.Clear;
Memo1.Lines.Add('--- Series Test ---'); Memo1.Lines.Add('--- Series Test ---');
@@ -439,21 +454,19 @@ begin
scope := TAst.CreateScope(FGScope); scope := TAst.CreateScope(FGScope);
recordDef := TRttiAstHelper.JsonToRecordDefinition(TRttiAstHelper.RecordDefinitionToJson<TOHLCV>); recordDef := TRttiAstHelper.JsonToRecordDefinition(TRttiAstHelper.RecordDefinitionToJson<TOHLCV>);
series := TScalarRecordSeries.Create(recordDef); series := TScalarRecordSeries.Create(recordDef);
SetLength(values, 6);
for i := 0 to 4 do for i := 0 to 4 do
begin begin
series.Add( // TScalar can no longer hold TDateTime, convert to Int64 for the test.
TScalarRecord.Create( values[0].AsInt64 := Round((Now + i) * 24 * 60 * 60 * 1000);
recordDef, values[1].AsDouble := 100.0 + i;
[ values[2].AsDouble := 105.0 + i;
TScalarValue.FromDateTime(Now + i), values[3].AsDouble := 98.0 + i;
TScalarValue.FromDouble(100.0 + i), values[4].AsDouble := 102.0 + i;
TScalarValue.FromDouble(105.0 + i), values[5].AsInt64 := 10000 * (i + 1);
TScalarValue.FromDouble(98.0 + i),
TScalarValue.FromDouble(102.0 + i), series.Add(TScalarRecord.Create(recordDef, values));
TScalarValue.FromInt64(10000 * (i + 1))
]
)
);
end; end;
scope.Define('ohlcvSeries', TDataValue.FromRecordSeries(series)); scope.Define('ohlcvSeries', TDataValue.FromRecordSeries(series));
@@ -466,7 +479,7 @@ begin
TAst.Identifier('closeColumn'), TAst.Identifier('closeColumn'),
TAst.MemberAccess(TAst.Identifier('ohlcvSeries'), TAst.Identifier('Close')) TAst.MemberAccess(TAst.Identifier('ohlcvSeries'), TAst.Identifier('Close'))
), ),
TAst.Indexer(TAst.Identifier('closeColumn'), TAst.Constant(TScalar.FromInt64(1))) TAst.Indexer(TAst.Identifier('closeColumn'), TAst.Constant(1))
] ]
) )
); );
@@ -476,6 +489,7 @@ begin
resultValue := ExecuteAst(callAst, scope); resultValue := ExecuteAst(callAst, scope);
Memo1.Lines.Add(Format('Result of script: %s', [resultValue.ToString])); Memo1.Lines.Add(Format('Result of script: %s', [resultValue.ToString]));
UpdateScript;
end; end;
procedure TForm1.Test1ButtonClick(Sender: TObject); procedure TForm1.Test1ButtonClick(Sender: TObject);
@@ -493,12 +507,9 @@ begin
[], [],
TAst.Block( TAst.Block(
[ [
TAst.VarDecl(TAst.Identifier('a'), TAst.Constant(TScalar.FromInt64(10))), TAst.VarDecl(TAst.Identifier('a'), TAst.Constant(10)),
TAst.VarDecl( TAst.VarDecl(TAst.Identifier('b'), TAst.BinaryExpr(TAst.Identifier('a'), TScalar.TBinaryOp.Multiply, TAst.Constant(2))),
TAst.Identifier('b'), TAst.BinaryExpr(TAst.Identifier('a'), TScalar.TBinaryOp.Add, TAst.Identifier('b'))
TAst.BinaryExpr(TAst.Identifier('a'), boMultiply, TAst.Constant(TScalar.FromInt64(2)))
),
TAst.BinaryExpr(TAst.Identifier('a'), boAdd, TAst.Identifier('b'))
] ]
) )
); );
@@ -509,6 +520,7 @@ begin
sw.Stop; sw.Stop;
Memo1.Lines.Add(Format('Result: %s (calculated in %d ms)', [result.ToString, sw.ElapsedMilliseconds])); Memo1.Lines.Add(Format('Result: %s (calculated in %d ms)', [result.ToString, sw.ElapsedMilliseconds]));
UpdateScript;
end; end;
procedure TForm1.Test2ButtonClick(Sender: TObject); procedure TForm1.Test2ButtonClick(Sender: TObject);
@@ -530,14 +542,14 @@ begin
[TAst.Identifier('offset')], [TAst.Identifier('offset')],
TAst.Block( TAst.Block(
[ [
TAst.VarDecl(TAst.Identifier('baseValue'), TAst.Constant(TScalar.FromInt64(100))), TAst.VarDecl(TAst.Identifier('baseValue'), TAst.Constant(100)),
TAst.BinaryExpr(TAst.Identifier('baseValue'), boAdd, TAst.Identifier('offset')) TAst.BinaryExpr(TAst.Identifier('baseValue'), TScalar.TBinaryOp.Add, TAst.Identifier('offset'))
] ]
) )
) )
), ),
TAst.FunctionCall(TAst.Identifier('createStrategyInstance'), [TAst.Constant(TScalar.FromInt64(20))]), TAst.FunctionCall(TAst.Identifier('createStrategyInstance'), [TAst.Constant(20)]),
TAst.FunctionCall(TAst.Identifier('createStrategyInstance'), [TAst.Constant(TScalar.FromInt64(55))]) TAst.FunctionCall(TAst.Identifier('createStrategyInstance'), [TAst.Constant(55)])
] ]
); );
@@ -546,6 +558,7 @@ begin
sw.Stop; sw.Stop;
Memo1.Lines.Add(Format('Result of the final expression: %s (calculated in %d ms)', [result.ToString, sw.ElapsedMilliseconds])); Memo1.Lines.Add(Format('Result of the final expression: %s (calculated in %d ms)', [result.ToString, sw.ElapsedMilliseconds]));
UpdateScript;
end; end;
procedure TForm1.WorkspaceMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Single); procedure TForm1.WorkspaceMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Single);
@@ -553,15 +566,7 @@ begin
if Button <> TMouseButton.mbMiddle then if Button <> TMouseButton.mbMiddle then
exit; exit;
if FLastAst <> nil then ShowVizualization(X, Y);
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; end;
procedure TForm1.OHLCButtonClick(Sender: TObject); procedure TForm1.OHLCButtonClick(Sender: TObject);
@@ -572,6 +577,8 @@ const
smaFastLength = 10; smaFastLength = 10;
var var
scope: IExecutionScope; scope: IExecutionScope;
values: TArray<TScalar.TValue>;
recordValue: TScalarRecord;
begin begin
Memo1.Lines.Clear; Memo1.Lines.Clear;
Memo1.Lines.Add(Format('--- Simulating O(1) SMA Crossover Strategy for %d ticks ---', [numRecs])); Memo1.Lines.Add(Format('--- Simulating O(1) SMA Crossover Strategy for %d ticks ---', [numRecs]));
@@ -579,14 +586,8 @@ begin
var setupAst := var setupAst :=
TAst.Block( TAst.Block(
[ [
TAst.VarDecl( TAst.VarDecl(TAst.Identifier('smaFast'), TAst.FunctionCall(TAst.Identifier('CreateSMA'), [TAst.Constant(smaFastLength)])),
TAst.Identifier('smaFast'), TAst.VarDecl(TAst.Identifier('smaSlow'), TAst.FunctionCall(TAst.Identifier('CreateSMA'), [TAst.Constant(smaSlowLength)])),
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.VarDecl(
TAst.Identifier('maCrossStrategy'), TAst.Identifier('maCrossStrategy'),
TAst.LambdaExpr( TAst.LambdaExpr(
@@ -599,7 +600,7 @@ begin
), ),
TAst.VarDecl( TAst.VarDecl(
TAst.Identifier('currentClose'), TAst.Identifier('currentClose'),
TAst.Indexer(TAst.Identifier('closeSeries'), TAst.Constant(TScalar.FromInt64(0))) TAst.Indexer(TAst.Identifier('closeSeries'), TAst.Constant(0))
), ),
TAst.VarDecl( TAst.VarDecl(
TAst.Identifier('valSmaFast'), TAst.Identifier('valSmaFast'),
@@ -616,9 +617,13 @@ begin
) )
), ),
TAst.TernaryExpr( TAst.TernaryExpr(
TAst.BinaryExpr(TAst.Identifier('valSmaFast'), boGreater, TAst.Identifier('valSmaSlow')), TAst.BinaryExpr(
TAst.Constant(TScalar.FromInt64(1)), TAst.Identifier('valSmaFast'),
TAst.Constant(TScalar.FromInt64(-1)) TScalar.TBinaryOp.Greater,
TAst.Identifier('valSmaSlow')
),
TAst.Constant(1),
TAst.Constant(-1)
) )
] ]
) )
@@ -658,7 +663,6 @@ begin
var ohlcvRec: TOHLCV; var ohlcvRec: TOHLCV;
for var i := 1 to numRecs do for var i := 1 to numRecs do
begin begin
// ... (simulation logic is unchanged) ...
ohlcvRec.Timestamp := nw + (i / (24 * 60)); ohlcvRec.Timestamp := nw + (i / (24 * 60));
ohlcvRec.Open := lastClose + (Random * 0.05); ohlcvRec.Open := lastClose + (Random * 0.05);
ohlcvRec.Close := lastClose + (Random - 0.49) * 2; ohlcvRec.Close := lastClose + (Random - 0.49) * 2;
@@ -666,18 +670,16 @@ begin
ohlcvRec.Low := Min(ohlcvRec.Open, ohlcvRec.Close) - Random; ohlcvRec.Low := Min(ohlcvRec.Open, ohlcvRec.Close) - Random;
ohlcvRec.Volume := RandomRange(1000, 50000); ohlcvRec.Volume := RandomRange(1000, 50000);
lastClose := ohlcvRec.Close; lastClose := ohlcvRec.Close;
var recordValue :=
TScalarRecord.Create( SetLength(values, 6);
recDef, values[0].AsDouble := ohlcvRec.Timestamp;
[ values[1].AsDouble := ohlcvRec.Open;
TScalarValue.FromDateTime(ohlcvRec.Timestamp), values[2].AsDouble := ohlcvRec.High;
TScalarValue.FromDouble(ohlcvRec.Open), values[3].AsDouble := ohlcvRec.Low;
TScalarValue.FromDouble(ohlcvRec.High), values[4].AsDouble := ohlcvRec.Close;
TScalarValue.FromDouble(ohlcvRec.Low), values[5].AsInt64 := ohlcvRec.Volume;
TScalarValue.FromDouble(ohlcvRec.Close), recordValue := TScalarRecord.Create(recDef, values);
TScalarValue.FromInt64(ohlcvRec.Volume)
]
);
series.AsRecordSeries.Add(recordValue, lookback); series.AsRecordSeries.Add(recordValue, lookback);
if series.AsRecordSeries.TotalCount >= smaSlowLength then if series.AsRecordSeries.TotalCount >= smaSlowLength then
@@ -695,10 +697,54 @@ begin
sw.Stop; sw.Stop;
Memo1.Lines.Add('--- Simulation Finished ---'); Memo1.Lines.Add('--- Simulation Finished ---');
Memo1.Lines.Add(Format('Total time: %d ms', [sw.ElapsedMilliseconds])); Memo1.Lines.Add(Format('Total time: %d ms', [sw.ElapsedMilliseconds]));
UpdateScript;
end; end;
procedure TForm1.TailCallButtenClick(Sender: TObject);
const
// A large number to prove TCO prevents stack overflow
RecursionDepth = 1000000;
var var
TriggerScope: IExecutionScope; root: IAstNode;
result: TDataValue;
sw: TStopwatch;
begin
Memo1.Lines.Clear;
Memo1.Lines.Add(Format('--- Testing TCO with recursion depth of %d ---', [RecursionDepth]));
Application.ProcessMessages;
sw := TStopwatch.StartNew;
root :=
TAst.Block(
[
// var countDown = lambda(n) { ... };
TAst.VarDecl(
TAst.Identifier('countDown'),
TAst.LambdaExpr(
[TAst.Identifier('n')],
// if (n > 0) then recur(n-1) else 0
TAst.IfExpr(
TAst.BinaryExpr(TAst.Identifier('n'), TScalar.TBinaryOp.Greater, TAst.Constant(0)),
// This is the tail call position, now using recur.
TAst.Recur([TAst.BinaryExpr(TAst.Identifier('n'), TScalar.TBinaryOp.Subtract, TAst.Constant(1))]),
// Base case of the recursion returns 0
TAst.Constant(0)
)
)
),
// Initial call to start the recursion
TAst.FunctionCall(TAst.Identifier('countDown'), [TAst.Constant(RecursionDepth)])
]
);
FLastAst := root;
result := ExecuteAst(root, FGScope);
sw.Stop;
Memo1.Lines.Add(Format('Result: %s', [result.ToString]));
Memo1.Lines.Add(Format('Execution finished in %d ms without stack overflow.', [sw.ElapsedMilliseconds]));
UpdateScript;
end;
procedure TForm1.CreateTriggerExampleButtonClick(Sender: TObject); procedure TForm1.CreateTriggerExampleButtonClick(Sender: TObject);
var var
@@ -709,27 +755,31 @@ begin
blk := blk :=
TAst.Block( TAst.Block(
[ [
TAst.VarDecl(TAst.Identifier('X'), TAst.Constant(TScalar.FromInt64(0))), TAst.VarDecl(TAst.Identifier('X'), TAst.Constant(0)),
TAst.VarDecl( TAst.VarDecl(
TAst.Identifier('tickHandler'), TAst.Identifier('tickHandler'),
TAst.LambdaExpr( TAst.LambdaExpr(
[TAst.Identifier('summand')], [TAst.Identifier('summand')],
TAst.Assign(TAst.Identifier('X'), TAst.BinaryExpr(TAst.Identifier('X'), boAdd, TAst.Identifier('summand'))) TAst.Assign(
TAst.Identifier('X'),
TAst.BinaryExpr(TAst.Identifier('X'), TScalar.TBinaryOp.Add, TAst.Identifier('summand'))
)
) )
) )
] ]
); );
TriggerScope := TAstBinder.Bind(blk, FGScope).CreateScope(FGScope); FTriggerScope := TAstBinder.Bind(blk, FGScope).CreateScope(FGScope);
// This case is simple enough to just inline the logic from ExecuteAst // This case is simple enough to just inline the logic from ExecuteAst
var visitor := CreateVisitor(TriggerScope); var visitor := CreateVisitor(FTriggerScope);
visitor.Execute(blk); visitor.Execute(blk);
FLastAst := blk; FLastAst := blk;
Memo1.Lines.Add('Variable "X" and function "tickHandler" defined in persistent scope.'); Memo1.Lines.Add('Variable "X" and function "tickHandler" defined in persistent scope.');
Memo1.Lines.Add('Click "Do Trigger" to execute.'); Memo1.Lines.Add('Click "Do Trigger" to execute.');
UpdateScript;
end; end;
function TForm1.CreateVisitor(Scope: IExecutionScope): IAstVisitor; function TForm1.CreateVisitor(Scope: IExecutionScope): IAstVisitor;
@@ -744,24 +794,26 @@ procedure TForm1.DoTriggerButtonClick(Sender: TObject);
var var
callAst: IFunctionCallNode; callAst: IFunctionCallNode;
begin begin
callAst := TAst.FunctionCall(TAst.Identifier('tickHandler'), [TAst.Constant(TScalar.FromInt64(1))]); callAst := TAst.FunctionCall(TAst.Identifier('tickHandler'), [TAst.Constant(1)]);
FLastAst := callAst; FLastAst := callAst;
var X := ExecuteAst(callAst, TriggerScope); var X := ExecuteAst(callAst, FTriggerScope);
Memo1.Lines.Add(Format('Tick(1)! New value of X: %s', [X.ToString])); Memo1.Lines.Add(Format('Tick(1)! New value of X: %s', [X.ToString]));
UpdateScript;
end; end;
procedure TForm1.DoTrigger2ButtonClick(Sender: TObject); procedure TForm1.DoTrigger2ButtonClick(Sender: TObject);
var var
callAst: IFunctionCallNode; callAst: IFunctionCallNode;
begin begin
callAst := TAst.FunctionCall(TAst.Identifier('tickHandler'), [TAst.Constant(TScalar.FromInt64(2))]); callAst := TAst.FunctionCall(TAst.Identifier('tickHandler'), [TAst.Constant(2)]);
FLastAst := callAst; FLastAst := callAst;
var X := ExecuteAst(callAst, TriggerScope); var X := ExecuteAst(callAst, FTriggerScope);
Memo1.Lines.Add(Format('Tick(2)! New value of X: %s', [X.ToString])); Memo1.Lines.Add(Format('Tick(2)! New value of X: %s', [X.ToString]));
UpdateScript;
end; end;
procedure TForm1.DumpButtonClick(Sender: TObject); procedure TForm1.DumpButtonClick(Sender: TObject);
@@ -804,12 +856,12 @@ begin
end end
); );
callAst := callAst := TAst.FunctionCall(TAst.Identifier('delphiAdd'), [TAst.Constant(100), TAst.Constant(123)]);
TAst.FunctionCall(TAst.Identifier('delphiAdd'), [TAst.Constant(TScalar.FromInt64(100)), TAst.Constant(TScalar.FromInt64(23))]);
FLastAst := callAst; FLastAst := callAst;
resultValue := ExecuteAst(callAst, scope); resultValue := ExecuteAst(callAst, scope);
Memo1.Lines.Add(Format('Result from delphiAdd(100, 23): %s', [resultValue.ToString])); Memo1.Lines.Add(Format('Result from delphiAdd(100, 123): %s', [resultValue.ToString]));
UpdateScript;
end; end;
procedure TForm1.FailingUpvalueButtonClick(Sender: TObject); procedure TForm1.FailingUpvalueButtonClick(Sender: TObject);
@@ -821,7 +873,7 @@ begin
TAst.Block( TAst.Block(
[ [
// 1. Define the shared variable. // 1. Define the shared variable.
TAst.VarDecl(TAst.Identifier('a'), TAst.Constant(TScalar.FromInt64(10))), TAst.VarDecl(TAst.Identifier('a'), TAst.Constant(10)),
// 2. Define a function that returns a closure that MODIFIES 'a'. // 2. Define a function that returns a closure that MODIFIES 'a'.
// This creates the multi-level capture scenario. // This creates the multi-level capture scenario.
TAst.VarDecl( TAst.VarDecl(
@@ -832,7 +884,7 @@ begin
[], // Inner closure that is returned [], // Inner closure that is returned
TAst.Assign( TAst.Assign(
TAst.Identifier('a'), TAst.Identifier('a'),
TAst.BinaryExpr(TAst.Identifier('a'), boAdd, TAst.Constant(TScalar.FromInt64(5))) TAst.BinaryExpr(TAst.Identifier('a'), TScalar.TBinaryOp.Add, TAst.Constant(5))
) )
) )
) )
@@ -866,8 +918,14 @@ begin
Memo1.Lines.Add(Format('FAILURE: Expected 15, but got %s.', [resultValue.ToString])); Memo1.Lines.Add(Format('FAILURE: Expected 15, but got %s.', [resultValue.ToString]));
end; end;
Assert(TScalar.FromInteger(15) = resultValue.AsScalar, 'The final result should be 15.'); Assert(TScalar.FromInt64(15) = resultValue.AsScalar, 'The final result should be 15.');
Memo1.Lines.Add('Please check the new dump.'); Memo1.Lines.Add('Please check the new dump.');
UpdateScript;
end;
procedure TForm1.FlowOnlyBoxChange(Sender: TObject);
begin
UpdateScript;
end; end;
procedure TForm1.FromJSONButtonClick(Sender: TObject); procedure TForm1.FromJSONButtonClick(Sender: TObject);
@@ -902,51 +960,20 @@ begin
finally finally
Memo1.Lines.EndUpdate; Memo1.Lines.EndUpdate;
end; end;
UpdateScript;
end; end;
procedure TForm1.TailCallButtenClick(Sender: TObject); procedure TForm1.ScriptMemoChange(Sender: TObject);
const
// A large number to prove TCO prevents stack overflow
RecursionDepth = 1000000;
var
root: IAstNode;
result: TDataValue;
sw: TStopwatch;
begin begin
Memo1.Lines.Clear; if FScriptUpdate then
Memo1.Lines.Add(Format('--- Testing TCO with recursion depth of %d ---', [RecursionDepth])); exit;
Application.ProcessMessages;
sw := TStopwatch.StartNew;
root := try
TAst.Block( FLastAst := TAstScript.Parse(ScriptMemo.Lines.Text)
[ except
// var countDown = lambda(n) { ... }; on E: Exception do
TAst.VarDecl( Memo1.Lines.Add(E.Message);
TAst.Identifier('countDown'), end;
TAst.LambdaExpr(
[TAst.Identifier('n')],
// if (n > 0) then recur(n-1) else 'done'
TAst.IfExpr(
TAst.BinaryExpr(TAst.Identifier('n'), boGreater, TAst.Constant(TScalar.FromInt64(0))),
// This is the tail call position, now using recur.
TAst.Recur([TAst.BinaryExpr(TAst.Identifier('n'), boSubtract, TAst.Constant(TScalar.FromInt64(1)))]),
// Base case of the recursion
TAst.Constant(TScalar.FromString('done'))
)
)
),
// Initial call to start the recursion
TAst.FunctionCall(TAst.Identifier('countDown'), [TAst.Constant(TScalar.FromInt64(RecursionDepth))])
]
);
FLastAst := root;
result := ExecuteAst(root, FGScope);
sw.Stop;
Memo1.Lines.Add(Format('Result: %s', [result.ToString]));
Memo1.Lines.Add(Format('Execution finished in %d ms without stack overflow.', [sw.ElapsedMilliseconds]));
end; end;
procedure TForm1.ToJSONButtonClick(Sender: TObject); procedure TForm1.ToJSONButtonClick(Sender: TObject);
@@ -973,4 +1000,35 @@ begin
end; end;
end; end;
procedure TForm1.UpdateScript;
begin
try
FScriptUpdate := true;
try
ScriptMemo.Lines.Text := TAstScript.Print(FLastAst);
finally
FScriptUpdate := false;
end;
except
on E: Exception do
ScriptMemo.Lines.Add(E.Message);
end;
FWorkspace.DeleteChildren;
ShowVizualization(14, 14);
end;
procedure TForm1.ShowVizualization(X, Y: Single);
begin
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;
end. end.
+7 -19
View File
@@ -152,10 +152,8 @@ begin
end; end;
case AValue.AsScalar.Kind of case AValue.AsScalar.Kind of
skInteger: Result := (AValue.AsScalar.Value.AsInteger <> 0); TScalar.TKind.Ordinal: Result := (AValue.AsScalar.Value.AsInt64 <> 0);
skInt64: Result := (AValue.AsScalar.Value.AsInt64 <> 0); TScalar.TKind.Float: Result := (AValue.AsScalar.Value.AsDouble <> 0.0);
skUInt64: Result := (AValue.AsScalar.Value.AsUInt64 <> 0);
skBoolean: Result := (AValue.AsScalar.Value.AsBoolean);
else else
Result := False; Result := False;
end; end;
@@ -297,13 +295,10 @@ begin
if Assigned(Node.Lookback) then if Assigned(Node.Lookback) then
begin begin
lookbackValue := Node.Lookback.Accept(Self); lookbackValue := Node.Lookback.Accept(Self);
if (lookbackValue.Kind <> vkScalar) or not (lookbackValue.AsScalar.Kind in [skInteger, skInt64]) then if (lookbackValue.Kind <> vkScalar) or (lookbackValue.AsScalar.Kind <> TScalar.TKind.Ordinal) then
raise EArgumentException.Create('Lookback parameter must be an integer.'); raise EArgumentException.Create('Lookback parameter must be an integer.');
if lookbackValue.AsScalar.Kind = skInteger then lookback := lookbackValue.AsScalar.Value.AsInt64;
lookback := lookbackValue.AsScalar.Value.AsInteger
else
lookback := lookbackValue.AsScalar.Value.AsInt64;
end; end;
case seriesVar.Kind of case seriesVar.Kind of
@@ -365,21 +360,14 @@ function TEvaluatorVisitor.VisitIndexer(const Node: IIndexerNode): TDataValue;
var var
baseValue, indexValue: TDataValue; baseValue, indexValue: TDataValue;
index: Int64; index: Int64;
indexScalar: TScalar;
begin begin
baseValue := Node.Base.Accept(Self); baseValue := Node.Base.Accept(Self);
indexValue := Node.Index.Accept(Self); indexValue := Node.Index.Accept(Self);
if (indexValue.Kind <> vkScalar) then if (indexValue.Kind <> vkScalar) or (indexValue.AsScalar.Kind <> TScalar.TKind.Ordinal) then
raise EArgumentException.Create('Indexer `[]` requires a scalar integer argument.'); raise EArgumentException.Create('Indexer `[]` requires an integer argument.');
indexScalar := indexValue.AsScalar; index := indexValue.AsScalar.Value.AsInt64;
case indexScalar.Kind of
skInteger: index := indexScalar.Value.AsInteger;
skInt64: index := indexScalar.Value.AsInt64;
else
raise EArgumentException.Create('Indexer `[]` requires an integer type argument.');
end;
case baseValue.Kind of case baseValue.Kind of
vkSeries: vkSeries:
+53 -37
View File
@@ -21,7 +21,7 @@ uses
System.Generics.Collections, System.Generics.Collections,
Myc.Ast, Myc.Ast,
Myc.Data.Scalar, Myc.Data.Scalar,
Myc.Data.Value; // Added for TDataValue Myc.Data.Value;
type type
// TJsonAstConverter implements the visitor pattern for serialization // TJsonAstConverter implements the visitor pattern for serialization
@@ -29,10 +29,10 @@ type
TJsonAstConverter = class(TInterfacedObject, IAstVisitor) TJsonAstConverter = class(TInterfacedObject, IAstVisitor)
private private
FJsonObjectStack: TStack<TJSONObject>; FJsonObjectStack: TStack<TJSONObject>;
procedure ScalarToJson(const AScalar: TScalar; const AParent: TJSONObject; const AName: string); procedure DataValueToJson(const AValue: TDataValue; const AParent: TJSONObject; const AName: string);
function JsonToNode(const AJson: TJSONValue): IAstNode; function JsonToNode(const AJson: TJSONValue): IAstNode;
function JsonToScalar(const AObj: TJSONObject; const AName: string): TScalar; function JsonToDataValue(const AObj: TJSONObject; const AName: string): TDataValue;
// Updated signatures to return specific node interface types
function JsonToConstantNode(const AObj: TJSONObject): IConstantNode; function JsonToConstantNode(const AObj: TJSONObject): IConstantNode;
function JsonToIdentifierNode(const AObj: TJSONObject): IIdentifierNode; function JsonToIdentifierNode(const AObj: TJSONObject): IIdentifierNode;
function JsonToBinaryExprNode(const AObj: TJSONObject): IBinaryExpressionNode; function JsonToBinaryExprNode(const AObj: TJSONObject): IBinaryExpressionNode;
@@ -137,23 +137,31 @@ end;
{ TJsonAstConverter - IAstVisitor for Serialization } { TJsonAstConverter - IAstVisitor for Serialization }
procedure TJsonAstConverter.ScalarToJson(const AScalar: TScalar; const AParent: TJSONObject; const AName: string); procedure TJsonAstConverter.DataValueToJson(const AValue: TDataValue; const AParent: TJSONObject; const AName: string);
var var
scalarObj: TJSONObject; valObj, scalarObj: TJSONObject;
begin begin
scalarObj := TJSONObject.Create; valObj := TJSONObject.Create;
scalarObj.AddPair('Kind', TJSONString.Create(AScalar.Kind.ToString)); valObj.AddPair('Kind', TJSONString.Create(AValue.Kind.ToString));
case AScalar.Kind of case AValue.Kind of
skInt64: scalarObj.AddPair('Value', TJSONNumber.Create(AScalar.Value.AsInt64)); vkScalar:
skDouble: scalarObj.AddPair('Value', TJSONNumber.Create(AScalar.Value.AsDouble)); begin
skBoolean: scalarObj.AddPair('Value', TJSONBool.Create(AScalar.Value.AsBoolean)); scalarObj := TJSONObject.Create;
skString: scalarObj.AddPair('Value', TJSONString.Create(AScalar.Value.AsString)); scalarObj.AddPair('Kind', TJSONString.Create(AValue.AsScalar.Kind.ToString));
case AValue.AsScalar.Kind of
TScalar.TKind.Ordinal: scalarObj.AddPair('Value', TJSONNumber.Create(AValue.AsScalar.Value.AsInt64));
TScalar.TKind.Float: scalarObj.AddPair('Value', TJSONNumber.Create(AValue.AsScalar.Value.AsDouble));
end;
valObj.AddPair('Value', scalarObj);
end;
vkText: valObj.AddPair('Value', TJSONString.Create(AValue.AsText));
vkVoid:; // No value to add
else else
raise ENotSupportedException.CreateFmt('Scalar kind %s not supported for JSON serialization.', [AScalar.Kind.ToString]); raise ENotSupportedException.Create('Unsupported TDataValue kind for constant serialization.');
end; end;
AParent.AddPair(AName, scalarObj); AParent.AddPair(AName, valObj);
end; end;
function TJsonAstConverter.VisitConstant(const Node: IConstantNode): TDataValue; function TJsonAstConverter.VisitConstant(const Node: IConstantNode): TDataValue;
@@ -162,7 +170,7 @@ var
begin begin
obj := TJSONObject.Create; obj := TJSONObject.Create;
obj.AddPair('NodeType', TJSONString.Create('Constant')); obj.AddPair('NodeType', TJSONString.Create('Constant'));
ScalarToJson(Node.Value, obj, 'Value'); DataValueToJson(Node.Value, obj, 'Value');
FJsonObjectStack.Push(obj); FJsonObjectStack.Push(obj);
Result := TDataValue.Void; Result := TDataValue.Void;
end; end;
@@ -509,32 +517,40 @@ end;
{ TJsonAstConverter - Deserialization } { TJsonAstConverter - Deserialization }
function TJsonAstConverter.JsonToScalar(const AObj: TJSONObject; const AName: string): TScalar; function TJsonAstConverter.JsonToDataValue(const AObj: TJSONObject; const AName: string): TDataValue;
var var
scalarObj: TJSONObject; valObj, scalarObj: TJSONObject;
kindStr: string; kindStr: string;
scalarKind: TScalarKind; scalarKind: TScalar.TKind;
jsonValue: TJSONValue;
begin begin
scalarObj := AObj.GetValue<TJSONObject>(AName); valObj := AObj.GetValue<TJSONObject>(AName);
kindStr := scalarObj.GetValue<string>('Kind'); kindStr := valObj.GetValue<string>('Kind');
scalarKind := TScalar.StringToKind(kindStr);
jsonValue := scalarObj.GetValue('Value'); if SameText(kindStr, 'Scalar') then
begin
case scalarKind of scalarObj := valObj.GetValue<TJSONObject>('Value');
skInt64: Result := TScalar.FromInt64((jsonValue as TJSONNumber).AsInt64); kindStr := scalarObj.GetValue<string>('Kind');
skDouble: Result := TScalar.FromDouble((jsonValue as TJSONNumber).AsDouble); scalarKind := TScalar.StringToKind(kindStr);
skBoolean: Result := TScalar.FromBoolean((jsonValue as TJSONBool).AsBoolean); case scalarKind of
skString: Result := TScalar.FromString((jsonValue as TJSONString).Value); TScalar.TKind.Ordinal: Result := TScalar.FromInt64(scalarObj.GetValue<TJSONNumber>('Value').AsInt64);
TScalar.TKind.Float: Result := TScalar.FromDouble(scalarObj.GetValue<TJSONNumber>('Value').AsDouble);
end;
end
else if SameText(kindStr, 'Text') then
begin
Result := valObj.GetValue<string>('Value');
end
else if SameText(kindStr, 'Void') then
begin
Result := TDataValue.Void;
end
else else
raise ENotSupportedException.CreateFmt('Scalar kind %s not supported for JSON deserialization.', [kindStr]); raise ENotSupportedException.Create('Unsupported TDataValue kind for constant deserialization.');
end;
end; end;
function TJsonAstConverter.JsonToConstantNode(const AObj: TJSONObject): IConstantNode; function TJsonAstConverter.JsonToConstantNode(const AObj: TJSONObject): IConstantNode;
begin begin
Result := TAst.Constant(JsonToScalar(AObj, 'Value')); Result := TAst.Constant(JsonToDataValue(AObj, 'Value'));
end; end;
function TJsonAstConverter.JsonToIdentifierNode(const AObj: TJSONObject): IIdentifierNode; function TJsonAstConverter.JsonToIdentifierNode(const AObj: TJSONObject): IIdentifierNode;
@@ -545,11 +561,11 @@ end;
function TJsonAstConverter.JsonToBinaryExprNode(const AObj: TJSONObject): IBinaryExpressionNode; function TJsonAstConverter.JsonToBinaryExprNode(const AObj: TJSONObject): IBinaryExpressionNode;
var var
opStr: string; opStr: string;
op: TBinaryOperator; op: TScalar.TBinaryOp;
leftNode, rightNode: IAstNode; leftNode, rightNode: IAstNode;
begin begin
opStr := AObj.GetValue<string>('Operator'); opStr := AObj.GetValue<string>('Operator');
for op := Low(TBinaryOperator) to High(TBinaryOperator) do for op := Low(TScalar.TBinaryOp) to High(TScalar.TBinaryOp) do
if SameText(op.ToString, opStr) then if SameText(op.ToString, opStr) then
begin begin
leftNode := JsonToNode(AObj.GetValue('Left')); leftNode := JsonToNode(AObj.GetValue('Left'));
@@ -563,11 +579,11 @@ end;
function TJsonAstConverter.JsonToUnaryExprNode(const AObj: TJSONObject): IUnaryExpressionNode; function TJsonAstConverter.JsonToUnaryExprNode(const AObj: TJSONObject): IUnaryExpressionNode;
var var
opStr: string; opStr: string;
op: TUnaryOperator; op: TScalar.TUnaryOp;
rightNode: IAstNode; rightNode: IAstNode;
begin begin
opStr := AObj.GetValue<string>('Operator'); opStr := AObj.GetValue<string>('Operator');
for op := Low(TUnaryOperator) to High(TUnaryOperator) do for op := Low(TScalar.TUnaryOp) to High(TScalar.TUnaryOp) do
if SameText(op.ToString, opStr) then if SameText(op.ToString, opStr) then
begin begin
rightNode := JsonToNode(AObj.GetValue('Right')); rightNode := JsonToNode(AObj.GetValue('Right'));
+7 -6
View File
@@ -112,10 +112,11 @@ type
end; end;
IConstantNode = interface(IAstNode) IConstantNode = interface(IAstNode)
// Represents a constant value in the AST (Scalar, Text, or Void).
{$region 'private'} {$region 'private'}
function GetValue: TScalar; function GetValue: TDataValue;
{$endregion} {$endregion}
property Value: TScalar read GetValue; property Value: TDataValue read GetValue;
end; end;
IIdentifierNode = interface(IAstNode) IIdentifierNode = interface(IAstNode)
@@ -130,20 +131,20 @@ type
IBinaryExpressionNode = interface(IAstNode) IBinaryExpressionNode = interface(IAstNode)
{$region 'private'} {$region 'private'}
function GetLeft: IAstNode; function GetLeft: IAstNode;
function GetOperator: TBinaryOperator; function GetOperator: TScalar.TBinaryOp;
function GetRight: IAstNode; function GetRight: IAstNode;
{$endregion} {$endregion}
property Left: IAstNode read GetLeft; property Left: IAstNode read GetLeft;
property Operator: TBinaryOperator read GetOperator; property Operator: TScalar.TBinaryOp read GetOperator;
property Right: IAstNode read GetRight; property Right: IAstNode read GetRight;
end; end;
IUnaryExpressionNode = interface(IAstNode) IUnaryExpressionNode = interface(IAstNode)
{$region 'private'} {$region 'private'}
function GetOperator: TUnaryOperator; function GetOperator: TScalar.TUnaryOp;
function GetRight: IAstNode; function GetRight: IAstNode;
{$endregion} {$endregion}
property Operator: TUnaryOperator read GetOperator; property Operator: TScalar.TUnaryOp read GetOperator;
property Right: IAstNode read GetRight; property Right: IAstNode read GetRight;
end; end;
File diff suppressed because it is too large Load Diff
+100 -101
View File
@@ -11,15 +11,17 @@ uses
Myc.Ast; Myc.Ast;
type type
// A visitor that converts an AST into a LISP-like (Clojure-style) string representation.
TPrettyPrintVisitor = class(TInterfacedObject, IAstVisitor) TPrettyPrintVisitor = class(TInterfacedObject, IAstVisitor)
private private
FBuilder: TStringBuilder; FBuilder: TStringBuilder;
FIndentLevel: Integer; FIndentLevel: Integer;
procedure Indent; procedure Indent;
procedure Unindent; procedure Unindent;
procedure AppendLine(const S: string); procedure Append(const S: string);
procedure NewLine;
public public
constructor Create(AIndentLevel: Integer = 0); constructor Create;
destructor Destroy; override; destructor Destroy; override;
function GetResult: string; function GetResult: string;
// IAstVisitor // IAstVisitor
@@ -46,17 +48,15 @@ type
implementation implementation
uses uses
Myc.Data.Scalar, Myc.Data.Scalar;
Myc.Data.Decimal;
{ TPrettyPrintVisitor } { TPrettyPrintVisitor }
constructor TPrettyPrintVisitor.Create(AIndentLevel: Integer); constructor TPrettyPrintVisitor.Create;
begin begin
inherited Create; inherited Create;
FBuilder := TStringBuilder.Create; FBuilder := TStringBuilder.Create;
FIndentLevel := 0; FIndentLevel := 0;
FIndentLevel := AIndentLevel;
end; end;
destructor TPrettyPrintVisitor.Destroy; destructor TPrettyPrintVisitor.Destroy;
@@ -72,119 +72,126 @@ end;
procedure TPrettyPrintVisitor.Indent; procedure TPrettyPrintVisitor.Indent;
begin begin
inc(FIndentLevel, 4); inc(FIndentLevel, 2);
end; end;
procedure TPrettyPrintVisitor.Unindent; procedure TPrettyPrintVisitor.Unindent;
begin begin
dec(FIndentLevel, 4); dec(FIndentLevel, 2);
end; end;
procedure TPrettyPrintVisitor.AppendLine(const S: string); procedure TPrettyPrintVisitor.Append(const S: string);
begin begin
FBuilder.Append(S);
end;
procedure TPrettyPrintVisitor.NewLine;
begin
FBuilder.AppendLine;
FBuilder.Append(''.PadLeft(FIndentLevel)); FBuilder.Append(''.PadLeft(FIndentLevel));
FBuilder.AppendLine(S);
end; end;
function TPrettyPrintVisitor.Execute(const RootNode: IAstNode): TDataValue; function TPrettyPrintVisitor.Execute(const RootNode: IAstNode): TDataValue;
begin begin
Result := RootNode.Accept(Self); if Assigned(RootNode) then
RootNode.Accept(Self);
Result := TDataValue.Void;
end; end;
function TPrettyPrintVisitor.VisitConstant(const Node: IConstantNode): TDataValue; function TPrettyPrintVisitor.VisitConstant(const Node: IConstantNode): TDataValue;
var
val: TDataValue;
begin begin
AppendLine(Format('Constant (%s)', [Node.Value.ToString])); val := Node.Value;
if val.Kind = vkText then
Append('"' + val.AsText + '"')
else
Append(val.ToString);
Result := TDataValue.Void; Result := TDataValue.Void;
end; end;
function TPrettyPrintVisitor.VisitIdentifier(const Node: IIdentifierNode): TDataValue; function TPrettyPrintVisitor.VisitIdentifier(const Node: IIdentifierNode): TDataValue;
begin begin
AppendLine(Format('Identifier (%s)', [Node.Name])); Append(Node.Name);
Result := TDataValue.Void; Result := TDataValue.Void;
end; end;
function TPrettyPrintVisitor.VisitBinaryExpression(const Node: IBinaryExpressionNode): TDataValue; function TPrettyPrintVisitor.VisitBinaryExpression(const Node: IBinaryExpressionNode): TDataValue;
begin begin
AppendLine(Format('BinaryExpr "%s"', [Node.Operator.ToString])); Append('(' + Node.Operator.ToString);
Indent; Append(' ');
Node.Left.Accept(Self); Node.Left.Accept(Self);
Append(' ');
Node.Right.Accept(Self); Node.Right.Accept(Self);
Unindent; Append(')');
Result := TDataValue.Void; Result := TDataValue.Void;
end; end;
function TPrettyPrintVisitor.VisitUnaryExpression(const Node: IUnaryExpressionNode): TDataValue; function TPrettyPrintVisitor.VisitUnaryExpression(const Node: IUnaryExpressionNode): TDataValue;
begin begin
AppendLine(Format('UnaryExpr "%s"', [Node.Operator.ToString])); Append('(' + Node.Operator.ToString);
Indent; Append(' ');
Node.Right.Accept(Self); Node.Right.Accept(Self);
Unindent; Append(')');
Result := TDataValue.Void; Result := TDataValue.Void;
end; end;
function TPrettyPrintVisitor.VisitIfExpression(const Node: IIfExpressionNode): TDataValue; function TPrettyPrintVisitor.VisitIfExpression(const Node: IIfExpressionNode): TDataValue;
begin begin
AppendLine('IfExpr'); Append('(if ');
Indent;
AppendLine('Condition:');
Indent;
Node.Condition.Accept(Self); Node.Condition.Accept(Self);
Unindent;
AppendLine('Then:');
Indent; Indent;
NewLine;
Node.ThenBranch.Accept(Self); Node.ThenBranch.Accept(Self);
Unindent; if Assigned(Node.ElseBranch) then
if Node.ElseBranch <> nil then
begin begin
AppendLine('Else:'); NewLine;
Indent;
Node.ElseBranch.Accept(Self); Node.ElseBranch.Accept(Self);
Unindent;
end; end;
Unindent; Unindent;
NewLine;
Append(')');
Result := TDataValue.Void; Result := TDataValue.Void;
end; end;
function TPrettyPrintVisitor.VisitTernaryExpression(const Node: ITernaryExpressionNode): TDataValue; function TPrettyPrintVisitor.VisitTernaryExpression(const Node: ITernaryExpressionNode): TDataValue;
begin begin
AppendLine('TernaryExpr'); Append('(if ');
Indent;
AppendLine('Condition:');
Indent;
Node.Condition.Accept(Self); Node.Condition.Accept(Self);
Unindent;
AppendLine('Then:');
Indent; Indent;
NewLine;
Node.ThenBranch.Accept(Self); Node.ThenBranch.Accept(Self);
Unindent; NewLine;
AppendLine('Else:');
Indent;
Node.ElseBranch.Accept(Self); Node.ElseBranch.Accept(Self);
Unindent; Unindent;
Unindent; NewLine;
Append(')');
Result := TDataValue.Void; Result := TDataValue.Void;
end; end;
function TPrettyPrintVisitor.VisitLambdaExpression(const Node: ILambdaExpressionNode): TDataValue; function TPrettyPrintVisitor.VisitLambdaExpression(const Node: ILambdaExpressionNode): TDataValue;
var var
param: IIdentifierNode; param: IIdentifierNode;
paramNames: TStringList; sb: TStringBuilder;
begin begin
paramNames := TStringList.Create; sb := TStringBuilder.Create;
try try
for param in Node.Parameters do for param in Node.Parameters do
paramNames.Add(param.Name); sb.Append(param.Name + ' ');
AppendLine(Format('Lambda (params: %s)', [paramNames.CommaText])); if sb.Length > 0 then
sb.Remove(sb.Length - 1, 1);
Append('(fn [' + sb.ToString + ']');
finally finally
paramNames.Free; sb.Free;
end; end;
Indent; Indent;
AppendLine('Body:'); NewLine;
Indent;
Node.Body.Accept(Self); Node.Body.Accept(Self);
Unindent; Unindent;
Unindent; NewLine;
Append(')');
Result := TDataValue.Void; Result := TDataValue.Void;
end; end;
@@ -192,18 +199,21 @@ function TPrettyPrintVisitor.VisitFunctionCall(const Node: IFunctionCallNode): T
var var
arg: IAstNode; arg: IAstNode;
begin begin
AppendLine('FunctionCall'); Append('(');
Indent;
AppendLine('Callee:');
Indent;
Node.Callee.Accept(Self); Node.Callee.Accept(Self);
Unindent;
AppendLine('Arguments:');
Indent; Indent;
for arg in Node.Arguments do for arg in Node.Arguments do
begin
NewLine;
arg.Accept(Self); arg.Accept(Self);
end;
Unindent; Unindent;
Unindent;
if Length(Node.Arguments) > 0 then
NewLine;
Append(')');
Result := TDataValue.Void; Result := TDataValue.Void;
end; end;
@@ -211,14 +221,17 @@ function TPrettyPrintVisitor.VisitRecurNode(const Node: IRecurNode): TDataValue;
var var
arg: IAstNode; arg: IAstNode;
begin begin
AppendLine('Recur'); Append('(recur');
Indent;
AppendLine('Arguments:');
Indent; Indent;
for arg in Node.Arguments do for arg in Node.Arguments do
begin
NewLine;
arg.Accept(Self); arg.Accept(Self);
end;
Unindent; Unindent;
Unindent; if Length(Node.Arguments) > 0 then
NewLine;
Append(')');
Result := TDataValue.Void; Result := TDataValue.Void;
end; end;
@@ -226,102 +239,88 @@ function TPrettyPrintVisitor.VisitBlockExpression(const Node: IBlockExpressionNo
var var
expr: IAstNode; expr: IAstNode;
begin begin
AppendLine('Block'); Append('(do');
Indent; Indent;
for expr in Node.Expressions do for expr in Node.Expressions do
begin
NewLine;
expr.Accept(Self); expr.Accept(Self);
end;
Unindent; Unindent;
NewLine;
Append(')');
Result := TDataValue.Void; Result := TDataValue.Void;
end; end;
function TPrettyPrintVisitor.VisitVariableDeclaration(const Node: IVariableDeclarationNode): TDataValue; function TPrettyPrintVisitor.VisitVariableDeclaration(const Node: IVariableDeclarationNode): TDataValue;
begin begin
AppendLine(Format('VarDecl (%s)', [Node.Identifier.Name])); Append('(def ');
Node.Identifier.Accept(Self);
if Assigned(Node.Initializer) then if Assigned(Node.Initializer) then
begin begin
Indent; Append(' ');
Node.Initializer.Accept(Self); Node.Initializer.Accept(Self);
Unindent;
end; end;
Append(')');
Result := TDataValue.Void; Result := TDataValue.Void;
end; end;
function TPrettyPrintVisitor.VisitAssignment(const Node: IAssignmentNode): TDataValue; function TPrettyPrintVisitor.VisitAssignment(const Node: IAssignmentNode): TDataValue;
begin begin
// Print the assignment node. Append('(assign ');
AppendLine(Format('Assignment (%s)', [Node.Identifier.Name])); Node.Identifier.Accept(Self);
Indent; Append(' ');
Node.Value.Accept(Self); Node.Value.Accept(Self);
Unindent; Append(')');
Result := TDataValue.Void; Result := TDataValue.Void;
end; end;
function TPrettyPrintVisitor.VisitIndexer(const Node: IIndexerNode): TDataValue; function TPrettyPrintVisitor.VisitIndexer(const Node: IIndexerNode): TDataValue;
begin begin
AppendLine('Indexer'); Append('(get ');
Indent;
AppendLine('Base:');
Indent;
Node.Base.Accept(Self); Node.Base.Accept(Self);
Unindent; Append(' ');
AppendLine('Index:');
Indent;
Node.Index.Accept(Self); Node.Index.Accept(Self);
Unindent; Append(')');
Unindent;
Result := TDataValue.Void; Result := TDataValue.Void;
end; end;
function TPrettyPrintVisitor.VisitMemberAccess(const Node: IMemberAccessNode): TDataValue; function TPrettyPrintVisitor.VisitMemberAccess(const Node: IMemberAccessNode): TDataValue;
begin begin
AppendLine(Format('MemberAccess (Member: %s)', [Node.Member.Name])); Append('(.');
Indent; Node.Member.Accept(Self);
AppendLine('Base:'); Append(' ');
Indent;
Node.Base.Accept(Self); Node.Base.Accept(Self);
Unindent; Append(')');
Unindent;
Result := TDataValue.Void; Result := TDataValue.Void;
end; end;
function TPrettyPrintVisitor.VisitCreateSeries(const Node: ICreateSeriesNode): TDataValue; function TPrettyPrintVisitor.VisitCreateSeries(const Node: ICreateSeriesNode): TDataValue;
begin begin
AppendLine('CreateSeries'); Append(Format('(new-series "%s")', [Node.Definition]));
Indent;
AppendLine('Definition: ' + Node.Definition);
Unindent;
Result := TDataValue.Void; Result := TDataValue.Void;
end; end;
function TPrettyPrintVisitor.VisitAddSeriesItem(const Node: IAddSeriesItemNode): TDataValue; function TPrettyPrintVisitor.VisitAddSeriesItem(const Node: IAddSeriesItemNode): TDataValue;
begin begin
AppendLine('AddSeriesItem'); Append('(add-item ');
Indent;
AppendLine('Series:');
Indent;
Node.Series.Accept(Self); Node.Series.Accept(Self);
Unindent; Append(' ');
AppendLine('Value:');
Indent;
Node.Value.Accept(Self); Node.Value.Accept(Self);
Unindent;
if Assigned(Node.Lookback) then if Assigned(Node.Lookback) then
begin begin
AppendLine('Lookback:'); Append(' ');
Indent;
Node.Lookback.Accept(Self); Node.Lookback.Accept(Self);
Unindent;
end; end;
Unindent; Append(')');
Result := TDataValue.Void; Result := TDataValue.Void;
end; end;
function TPrettyPrintVisitor.VisitSeriesLength(const Node: ISeriesLengthNode): TDataValue; function TPrettyPrintVisitor.VisitSeriesLength(const Node: ISeriesLengthNode): TDataValue;
begin begin
AppendLine('SeriesLength'); Append('(count ');
Indent;
Node.Series.Accept(Self); Node.Series.Accept(Self);
Unindent; Append(')');
Result := TDataValue.Void; Result := TDataValue.Void;
end; end;
+22 -34
View File
@@ -60,13 +60,9 @@ uses
class function TRtlFunctions.Abs(const Arg: TScalar): TScalar; class function TRtlFunctions.Abs(const Arg: TScalar): TScalar;
begin begin
case Arg.Kind of case Arg.Kind of
skInteger: Result := TScalar.FromInteger(System.Abs(Arg.Value.AsInteger)); TScalar.TKind.Ordinal: Result := TScalar.FromInt64(System.Abs(Arg.Value.AsInt64));
skInt64: Result := TScalar.FromInt64(System.Abs(Arg.Value.AsInt64)); TScalar.TKind.Float: Result := TScalar.FromDouble(System.Abs(Arg.Value.AsDouble));
skSingle: Result := TScalar.FromSingle(System.Abs(Arg.Value.AsSingle));
skDouble: Result := TScalar.FromDouble(System.Abs(Arg.Value.AsDouble));
skDecimal: Result := TScalar.FromDecimal(Arg.Value.AsDecimal.Abs);
else else
// This case should not be reached if the wrapper validation is correct.
raise EArgumentException.Create('Abs requires a numeric argument.'); raise EArgumentException.Create('Abs requires a numeric argument.');
end; end;
end; end;
@@ -74,10 +70,8 @@ end;
class function TRtlFunctions.Trunc(const Arg: TScalar): TScalar; class function TRtlFunctions.Trunc(const Arg: TScalar): TScalar;
begin begin
case Arg.Kind of case Arg.Kind of
skInteger, skInt64: Result := Arg; // Trunc on an integer is a no-op TScalar.TKind.Ordinal: Result := Arg; // Trunc on an integer is a no-op
skSingle: Result := TScalar.FromInt64(System.Trunc(Arg.Value.AsSingle)); TScalar.TKind.Float: Result := TScalar.FromInt64(System.Trunc(Arg.Value.AsDouble));
skDouble: Result := TScalar.FromInt64(System.Trunc(Arg.Value.AsDouble));
skDecimal: Result := TScalar.FromInt64(System.Trunc(Arg.Value.AsDecimal));
else else
raise EArgumentException.Create('Trunc requires a numeric argument.'); raise EArgumentException.Create('Trunc requires a numeric argument.');
end; end;
@@ -86,10 +80,8 @@ end;
class function TRtlFunctions.Ceil(const Arg: TScalar): TScalar; class function TRtlFunctions.Ceil(const Arg: TScalar): TScalar;
begin begin
case Arg.Kind of case Arg.Kind of
skInteger, skInt64: Result := Arg; // Ceil on an integer is a no-op TScalar.TKind.Ordinal: Result := Arg; // Ceil on an integer is a no-op
skSingle: Result := TScalar.FromInt64(System.Math.Ceil(Arg.Value.AsSingle)); TScalar.TKind.Float: Result := TScalar.FromInt64(System.Math.Ceil(Arg.Value.AsDouble));
skDouble: Result := TScalar.FromInt64(System.Math.Ceil(Arg.Value.AsDouble));
skDecimal: Result := TScalar.FromInt64(System.Math.Ceil(Double(Arg.Value.AsDecimal)));
else else
raise EArgumentException.Create('Ceil requires a numeric argument.'); raise EArgumentException.Create('Ceil requires a numeric argument.');
end; end;
@@ -98,10 +90,8 @@ end;
class function TRtlFunctions.Floor(const Arg: TScalar): TScalar; class function TRtlFunctions.Floor(const Arg: TScalar): TScalar;
begin begin
case Arg.Kind of case Arg.Kind of
skInteger, skInt64: Result := Arg; // Floor on an integer is a no-op TScalar.TKind.Ordinal: Result := Arg; // Floor on an integer is a no-op
skSingle: Result := TScalar.FromInt64(System.Math.Floor(Arg.Value.AsSingle)); TScalar.TKind.Float: Result := TScalar.FromInt64(System.Math.Floor(Arg.Value.AsDouble));
skDouble: Result := TScalar.FromInt64(System.Math.Floor(Arg.Value.AsDouble));
skDecimal: Result := TScalar.FromInt64(System.Math.Floor(Double(Arg.Value.AsDecimal)));
else else
raise EArgumentException.Create('Floor requires a numeric argument.'); raise EArgumentException.Create('Floor requires a numeric argument.');
end; end;
@@ -110,11 +100,8 @@ end;
class function TRtlFunctions.Sign(const Arg: TScalar): TScalar; class function TRtlFunctions.Sign(const Arg: TScalar): TScalar;
begin begin
case Arg.Kind of case Arg.Kind of
skInteger: Result := TScalar.FromInteger(System.Math.Sign(Arg.Value.AsInteger)); TScalar.TKind.Ordinal: Result := TScalar.FromInt64(System.Math.Sign(Arg.Value.AsInt64));
skInt64: Result := TScalar.FromInteger(System.Math.Sign(Arg.Value.AsInt64)); TScalar.TKind.Float: Result := TScalar.FromInt64(System.Math.Sign(Arg.Value.AsDouble));
skSingle: Result := TScalar.FromInteger(System.Math.Sign(Arg.Value.AsSingle));
skDouble: Result := TScalar.FromInteger(System.Math.Sign(Arg.Value.AsDouble));
skDecimal: Result := TScalar.FromInteger(Arg.Value.AsDecimal.Sign);
else else
raise EArgumentException.Create('Sign requires a numeric argument.'); raise EArgumentException.Create('Sign requires a numeric argument.');
end; end;
@@ -145,13 +132,10 @@ begin
raise EArgumentException.Create('This memoized function can only be called with a single scalar argument.'); raise EArgumentException.Create('This memoized function can only be called with a single scalar argument.');
argScalar := AArgs[0].AsScalar; argScalar := AArgs[0].AsScalar;
if not (argScalar.Kind in [skInteger, skInt64]) then if argScalar.Kind <> TScalar.TKind.Ordinal then
raise EArgumentException.Create('This memoized function expects an integer argument for caching.'); raise EArgumentException.Create('This memoized function expects an ordinal argument for caching.');
if argScalar.Kind = skInteger then key := argScalar.Value.AsInt64;
key := argScalar.Value.AsInteger
else
key := argScalar.Value.AsInt64;
var cache := cCache.AsObject as TDictionary<Int64, TDataValue>; var cache := cCache.AsObject as TDictionary<Int64, TDataValue>;
@@ -248,7 +232,9 @@ begin
begin begin
item := sourceSeries.Items[i]; item := sourceSeries.Items[i];
predicateResult := predicateFunc([TDataValue(item)]); predicateResult := predicateFunc([TDataValue(item)]);
if (predicateResult.Kind = vkScalar) and (predicateResult.AsScalar.Value.AsBoolean) then if (predicateResult.Kind = vkScalar)
and (predicateResult.AsScalar.Kind = TScalar.TKind.Ordinal)
and (predicateResult.AsScalar.Value.AsInt64 <> 0) then
matchingIndices.Add(i); matchingIndices.Add(i);
end; end;
@@ -262,7 +248,7 @@ begin
var var
idx: Integer; idx: Integer;
begin begin
idx := AArgs[0].AsScalar.Value.AsInteger; idx := AArgs[0].AsScalar.Value.AsInt64;
Result := TDataValue(sourceSeries.Items[idx]); Result := TDataValue(sourceSeries.Items[idx]);
end; end;
@@ -297,14 +283,16 @@ begin
item := sourceSeries.Items[i]; item := sourceSeries.Items[i];
predicateResult := predicateFunc([TDataValue(item)]); predicateResult := predicateFunc([TDataValue(item)]);
if (predicateResult.Kind = vkScalar) and (predicateResult.AsScalar.Value.AsBoolean) then if (predicateResult.Kind = vkScalar)
and (predicateResult.AsScalar.Kind = TScalar.TKind.Ordinal)
and (predicateResult.AsScalar.Value.AsInt64 <> 0) then
begin begin
Result := TScalar.FromBoolean(True); Result := TScalar.FromInt64(1);
exit; exit;
end; end;
end; end;
Result := TScalar.FromBoolean(False); Result := TScalar.FromInt64(0);
end; end;
end. end.
+2 -3
View File
@@ -69,9 +69,8 @@ begin
raise EArgumentException.CreateFmt('%s requires a scalar argument.', [AName]); raise EArgumentException.CreateFmt('%s requires a scalar argument.', [AName]);
AArg := AArgs[0].AsScalar; AArg := AArgs[0].AsScalar;
if not (AArg.Kind in [skInteger, skInt64, skSingle, skDouble, skDecimal]) then // The check for specific numeric kinds is no longer necessary,
raise EArgumentException.CreateFmt('%s requires a numeric argument, but got %s.', [AName, AArg.Kind.ToString]); // as TScalar can only be Ordinal or Float.
Result := True; Result := True;
end; end;
+699
View File
@@ -0,0 +1,699 @@
unit Myc.Ast.Script;
interface
uses
System.SysUtils,
Myc.Ast.Nodes;
type
// Provides a high-level facade for parsing and printing the AST.
TAstScript = record
public
class function Parse(const ASource: string): IAstNode; static;
class function Print(const ANode: IAstNode): string; static;
end;
implementation
uses
System.Classes,
System.Generics.Collections,
System.Character,
System.Math,
Myc.Data.Scalar,
Myc.Data.Value,
Myc.Ast;
type
// --- Internal Parser Implementation ---
TTokenKind = (
tkLeftParen, // (
tkRightParen, // )
tkLeftBracket, // [
tkRightBracket, // ]
tkIdentifier,
tkNumber,
tkString,
tkEOF,
tkError
);
TToken = record
Kind: TTokenKind;
Text: string;
end;
TLexer = class
private
FSource: string;
FCurrentPos: Integer;
function Peek: Char;
procedure Advance;
function ReadNumber: string;
function ReadString: string;
function ReadIdentifier: string;
public
constructor Create(const ASource: string);
function GetNextToken: TToken;
end;
TExpr = record
Token: TToken;
Node: IAstNode;
end;
TParser = class
private
FLexer: TLexer;
FCurrentToken: TToken;
procedure Consume(AExpectedKind: TTokenKind);
procedure NextToken;
function ParseList: IAstNode;
function ParseParameterList: TArray<IIdentifierNode>;
function ParseExpression: TExpr;
public
constructor Create(const ASource: string);
destructor Destroy; override;
function Parse: IAstNode;
end;
// --- Internal Printer Implementation ---
TPrettyPrintVisitor = class(TInterfacedObject, IAstVisitor)
private
FBuilder: TStringBuilder;
FIndentLevel: Integer;
procedure Indent;
procedure Unindent;
procedure Append(const S: string);
procedure NewLine;
public
constructor Create;
destructor Destroy; override;
function GetResult: string;
// IAstVisitor
function Execute(const RootNode: IAstNode): TDataValue;
function VisitConstant(const Node: IConstantNode): TDataValue;
function VisitIdentifier(const Node: IIdentifierNode): TDataValue;
function VisitBinaryExpression(const Node: IBinaryExpressionNode): TDataValue;
function VisitUnaryExpression(const Node: IUnaryExpressionNode): TDataValue;
function VisitIfExpression(const Node: IIfExpressionNode): TDataValue;
function VisitTernaryExpression(const Node: ITernaryExpressionNode): TDataValue;
function VisitLambdaExpression(const Node: ILambdaExpressionNode): TDataValue;
function VisitFunctionCall(const Node: IFunctionCallNode): TDataValue;
function VisitBlockExpression(const Node: IBlockExpressionNode): TDataValue;
function VisitVariableDeclaration(const Node: IVariableDeclarationNode): TDataValue;
function VisitAssignment(const Node: IAssignmentNode): TDataValue;
function VisitIndexer(const Node: IIndexerNode): TDataValue;
function VisitMemberAccess(const Node: IMemberAccessNode): TDataValue;
function VisitCreateSeries(const Node: ICreateSeriesNode): TDataValue;
function VisitAddSeriesItem(const Node: IAddSeriesItemNode): TDataValue;
function VisitSeriesLength(const Node: ISeriesLengthNode): TDataValue;
function VisitRecurNode(const Node: IRecurNode): TDataValue;
end;
{ TLexer }
constructor TLexer.Create(const ASource: string);
begin
inherited Create;
FSource := ASource;
FCurrentPos := 1;
end;
function TLexer.Peek: Char;
begin
if FCurrentPos > Length(FSource) then
Result := #0
else
Result := FSource[FCurrentPos];
end;
procedure TLexer.Advance;
begin
inc(FCurrentPos);
end;
function TLexer.ReadIdentifier: string;
var
startPos: Integer;
begin
startPos := FCurrentPos;
// Corrected W1050: Use CharInSet for robust unicode support.
while (Peek <> #0) and (not (Peek.IsWhiteSpace or CharInSet(Peek, ['(', ')', '[', ']']))) do
Advance;
Result := Copy(FSource, startPos, FCurrentPos - startPos);
end;
function TLexer.ReadNumber: string;
var
startPos: Integer;
begin
startPos := FCurrentPos;
while (Peek <> #0) and (Peek.IsDigit or (Peek = '.')) do
Advance;
Result := Copy(FSource, startPos, FCurrentPos - startPos);
end;
function TLexer.ReadString: string;
var
startPos: Integer;
begin
Advance; // Skip opening "
startPos := FCurrentPos;
while (Peek <> #0) and (Peek <> '"') do
Advance;
Result := Copy(FSource, startPos, FCurrentPos - startPos);
if Peek = '"' then
Advance; // Skip closing "
end;
function TLexer.GetNextToken: TToken;
begin
while (FCurrentPos <= Length(FSource)) and FSource[FCurrentPos].IsWhiteSpace do
Advance;
if FCurrentPos > Length(FSource) then
begin
Result.Kind := tkEOF;
exit;
end;
var c := Peek;
case c of
'(':
begin
Result.Kind := tkLeftParen;
Advance;
end;
')':
begin
Result.Kind := tkRightParen;
Advance;
end;
'[':
begin
Result.Kind := tkLeftBracket;
Advance;
end;
']':
begin
Result.Kind := tkRightBracket;
Advance;
end;
'"':
begin
Result.Kind := tkString;
Result.Text := ReadString;
end;
else
if c.IsDigit or ((c = '-') and (FCurrentPos < Length(FSource)) and FSource[FCurrentPos + 1].IsDigit) then
begin
Result.Kind := tkNumber;
Result.Text := ReadNumber;
end
else
begin
Result.Kind := tkIdentifier;
Result.Text := ReadIdentifier;
end;
end;
end;
{ TParser }
constructor TParser.Create(const ASource: string);
begin
inherited Create;
FLexer := TLexer.Create(ASource);
NextToken; // Load the first token
end;
destructor TParser.Destroy;
begin
FLexer.Free;
inherited;
end;
procedure TParser.NextToken;
begin
FCurrentToken := FLexer.GetNextToken;
end;
procedure TParser.Consume(AExpectedKind: TTokenKind);
begin
if FCurrentToken.Kind <> AExpectedKind then
raise Exception.CreateFmt('Syntax Error: Expected token %d, but found %d', [Ord(AExpectedKind), Ord(FCurrentToken.Kind)]);
NextToken;
end;
function TParser.ParseParameterList: TArray<IIdentifierNode>;
var
params: TList<IIdentifierNode>;
begin
Consume(tkLeftBracket);
params := TList<IIdentifierNode>.Create;
try
while FCurrentToken.Kind <> tkRightBracket do
begin
if FCurrentToken.Kind <> tkIdentifier then
raise Exception.Create('Syntax Error: Expected identifier in parameter list.');
params.Add(TAst.Identifier(FCurrentToken.Text));
NextToken;
end;
Result := params.ToArray;
finally
params.Free;
end;
Consume(tkRightBracket);
end;
function TParser.ParseList: IAstNode;
function IfThen(cond: Boolean; const TrueBranch, FalseBranch: IAstNode): IAstNode;
begin
if cond then
exit(TrueBranch)
else
exit(FalseBranch);
end;
var
elements: TList<TExpr>;
head: TExpr;
headIdent: IIdentifierNode;
tailTokens: TArray<TToken>;
tailNodes: TArray<IAstNode>;
begin
Consume(tkLeftParen);
if FCurrentToken.Kind = tkRightParen then
raise Exception.Create('Syntax Error: Empty list () is not a valid expression.');
elements := TList<TExpr>.Create;
try
while FCurrentToken.Kind <> tkRightParen do
elements.Add(ParseExpression);
head := elements[0];
// Convert the rest of the list to an array for the factory functions
SetLength(tailTokens, elements.Count - 1);
SetLength(tailNodes, elements.Count - 1);
for var i := 0 to High(tailNodes) do
begin
tailTokens[i] := elements[i + 1].Token;
tailNodes[i] := elements[i + 1].Node;
end;
if head.Token.Kind = tkIdentifier then
begin
// Handle special forms
if SameText(headIdent.Name, 'if') then
Result := TAst.IfExpr(tailNodes[0], tailNodes[1], IfThen(Length(tailNodes) > 2, tailNodes[2], nil))
else if SameText(headIdent.Name, 'def') then
begin
var identNode: IIdentifierNode;
if tailTokens[0].Kind <> tkIdentifier then
raise Exception.Create('Syntax Error: Expected an identifier for def statement.');
Result := TAst.VarDecl(identNode, IfThen(Length(tailNodes) > 1, tailNodes[1], nil));
end
else if SameText(headIdent.Name, 'assign') then
begin
var identNode: IIdentifierNode;
if tailTokens[0].Kind <> tkIdentifier then
raise Exception.Create('Syntax Error: Expected an identifier for assignment.');
Result := TAst.Assign(identNode, tailNodes[1]);
end
else if SameText(headIdent.Name, 'fn') then
Result := TAst.LambdaExpr(ParseParameterList, tailNodes[0]) // Special parsing for params
else if SameText(headIdent.Name, 'do') then
Result := TAst.Block(tailNodes)
else if SameText(headIdent.Name, 'recur') then
Result := TAst.Recur(tailNodes)
else if SameText(headIdent.Name, 'get') then
Result := TAst.Indexer(tailNodes[0], tailNodes[1])
else if (Length(headIdent.Name) > 1) and (headIdent.Name.StartsWith('.')) then
Result := TAst.MemberAccess(tailNodes[0], TAst.Identifier(headIdent.Name.Substring(1)))
else
Result := TAst.FunctionCall(head.Node, tailNodes); // Default is a function call
end
else
Result := TAst.FunctionCall(head.Node, tailNodes); // Callee is a complex expression, e.g. ((fn [x] x) 1)
finally
elements.Free;
end;
Consume(tkRightParen);
end;
function TParser.ParseExpression: TExpr;
var
i64: Int64;
dbl: Double;
begin
Result.Token := FCurrentToken;
case FCurrentToken.Kind of
tkNumber:
begin
if TryStrToInt64(FCurrentToken.Text, i64) then
Result.Node := TAst.Constant(i64)
else if TryStrToFloat(FCurrentToken.Text, dbl) then
Result.Node := TAst.Constant(dbl)
else
raise Exception.CreateFmt('Syntax Error: Invalid number format "%s"', [FCurrentToken.Text]);
NextToken;
end;
tkString:
begin
Result.Node := TAst.Constant(FCurrentToken.Text);
NextToken;
end;
tkIdentifier:
begin
Result.Node := TAst.Identifier(FCurrentToken.Text);
NextToken;
end;
tkLeftParen: Result.Node := ParseList;
else
raise Exception.CreateFmt('Syntax Error: Unexpected token %d', [Ord(FCurrentToken.Kind)]);
end;
end;
function TParser.Parse: IAstNode;
begin
var expr := ParseExpression;
if expr.Token.Kind <> tkEOF then
raise Exception.Create('Syntax Error: Unexpected characters after end of expression.');
Result := expr.Node;
end;
{ TPrettyPrintVisitor }
// ... (Implementation of TPrettyPrintVisitor is unchanged and correct) ...
constructor TPrettyPrintVisitor.Create;
begin
inherited Create;
FBuilder := TStringBuilder.Create;
FIndentLevel := 0;
end;
destructor TPrettyPrintVisitor.Destroy;
begin
FBuilder.Free;
inherited Destroy;
end;
function TPrettyPrintVisitor.GetResult: string;
begin
Result := FBuilder.ToString;
end;
procedure TPrettyPrintVisitor.Indent;
begin
inc(FIndentLevel, 2);
end;
procedure TPrettyPrintVisitor.Unindent;
begin
dec(FIndentLevel, 2);
end;
procedure TPrettyPrintVisitor.Append(const S: string);
begin
FBuilder.Append(S);
end;
procedure TPrettyPrintVisitor.NewLine;
begin
FBuilder.AppendLine;
FBuilder.Append(''.PadLeft(FIndentLevel));
end;
function TPrettyPrintVisitor.Execute(const RootNode: IAstNode): TDataValue;
begin
if Assigned(RootNode) then
RootNode.Accept(Self);
Result := TDataValue.Void;
end;
function TPrettyPrintVisitor.VisitConstant(const Node: IConstantNode): TDataValue;
var
val: TDataValue;
begin
val := Node.Value;
if val.Kind = vkText then
Append('"' + val.AsText + '"')
else
Append(val.ToString);
Result := TDataValue.Void;
end;
function TPrettyPrintVisitor.VisitIdentifier(const Node: IIdentifierNode): TDataValue;
begin
Append(Node.Name);
Result := TDataValue.Void;
end;
function TPrettyPrintVisitor.VisitBinaryExpression(const Node: IBinaryExpressionNode): TDataValue;
begin
Append('(' + Node.Operator.ToString);
Append(' ');
Node.Left.Accept(Self);
Append(' ');
Node.Right.Accept(Self);
Append(')');
Result := TDataValue.Void;
end;
function TPrettyPrintVisitor.VisitUnaryExpression(const Node: IUnaryExpressionNode): TDataValue;
begin
Append('(' + Node.Operator.ToString);
Append(' ');
Node.Right.Accept(Self);
Append(')');
Result := TDataValue.Void;
end;
function TPrettyPrintVisitor.VisitIfExpression(const Node: IIfExpressionNode): TDataValue;
begin
Append('(if ');
Node.Condition.Accept(Self);
Indent;
NewLine;
Node.ThenBranch.Accept(Self);
if Assigned(Node.ElseBranch) then
begin
NewLine;
Node.ElseBranch.Accept(Self);
end;
Unindent;
NewLine;
Append(')');
Result := TDataValue.Void;
end;
function TPrettyPrintVisitor.VisitTernaryExpression(const Node: ITernaryExpressionNode): TDataValue;
begin
Append('(if ');
Node.Condition.Accept(Self);
Indent;
NewLine;
Node.ThenBranch.Accept(Self);
NewLine;
Node.ElseBranch.Accept(Self);
Unindent;
NewLine;
Append(')');
Result := TDataValue.Void;
end;
function TPrettyPrintVisitor.VisitLambdaExpression(const Node: ILambdaExpressionNode): TDataValue;
var
param: IIdentifierNode;
sb: TStringBuilder;
begin
sb := TStringBuilder.Create;
try
for param in Node.Parameters do
sb.Append(param.Name + ' ');
if sb.Length > 0 then
sb.Remove(sb.Length - 1, 1); // remove trailing space
Append('(fn [' + sb.ToString + ']');
finally
sb.Free;
end;
Indent;
NewLine;
Node.Body.Accept(Self);
Unindent;
NewLine;
Append(')');
Result := TDataValue.Void;
end;
function TPrettyPrintVisitor.VisitFunctionCall(const Node: IFunctionCallNode): TDataValue;
var
arg: IAstNode;
begin
Append('(');
Node.Callee.Accept(Self);
Indent;
for arg in Node.Arguments do
begin
NewLine;
arg.Accept(Self);
end;
Unindent;
if Length(Node.Arguments) > 0 then
NewLine;
Append(')');
Result := TDataValue.Void;
end;
function TPrettyPrintVisitor.VisitRecurNode(const Node: IRecurNode): TDataValue;
var
arg: IAstNode;
begin
Append('(recur');
Indent;
for arg in Node.Arguments do
begin
NewLine;
arg.Accept(Self);
end;
Unindent;
if Length(Node.Arguments) > 0 then
NewLine;
Append(')');
Result := TDataValue.Void;
end;
function TPrettyPrintVisitor.VisitBlockExpression(const Node: IBlockExpressionNode): TDataValue;
var
expr: IAstNode;
begin
Append('(do');
Indent;
for expr in Node.Expressions do
begin
NewLine;
expr.Accept(Self);
end;
Unindent;
NewLine;
Append(')');
Result := TDataValue.Void;
end;
function TPrettyPrintVisitor.VisitVariableDeclaration(const Node: IVariableDeclarationNode): TDataValue;
begin
Append('(def ');
Node.Identifier.Accept(Self);
if Assigned(Node.Initializer) then
begin
Append(' ');
Node.Initializer.Accept(Self);
end;
Append(')');
Result := TDataValue.Void;
end;
function TPrettyPrintVisitor.VisitAssignment(const Node: IAssignmentNode): TDataValue;
begin
Append('(assign ');
Node.Identifier.Accept(Self);
Append(' ');
Node.Value.Accept(Self);
Append(')');
Result := TDataValue.Void;
end;
function TPrettyPrintVisitor.VisitIndexer(const Node: IIndexerNode): TDataValue;
begin
Append('(get ');
Node.Base.Accept(Self);
Append(' ');
Node.Index.Accept(Self);
Append(')');
Result := TDataValue.Void;
end;
function TPrettyPrintVisitor.VisitMemberAccess(const Node: IMemberAccessNode): TDataValue;
begin
Append('(.');
Node.Member.Accept(Self);
Append(' ');
Node.Base.Accept(Self);
Append(')');
Result := TDataValue.Void;
end;
function TPrettyPrintVisitor.VisitCreateSeries(const Node: ICreateSeriesNode): TDataValue;
begin
Append(Format('(new-series "%s")', [Node.Definition]));
Result := TDataValue.Void;
end;
function TPrettyPrintVisitor.VisitAddSeriesItem(const Node: IAddSeriesItemNode): TDataValue;
begin
Append('(add-item ');
Node.Series.Accept(Self);
Append(' ');
Node.Value.Accept(Self);
if Assigned(Node.Lookback) then
begin
Append(' ');
Node.Lookback.Accept(Self);
end;
Append(')');
Result := TDataValue.Void;
end;
function TPrettyPrintVisitor.VisitSeriesLength(const Node: ISeriesLengthNode): TDataValue;
begin
Append('(count ');
Node.Series.Accept(Self);
Append(')');
Result := TDataValue.Void;
end;
{ TAstScript }
class function TAstScript.Parse(const ASource: string): IAstNode;
var
p: TParser;
begin
if ASource.Trim.IsEmpty then
exit(nil);
p := TParser.Create(ASource);
try
Result := p.Parse;
finally
p.Free;
end;
end;
class function TAstScript.Print(const ANode: IAstNode): string;
var
visitor: TPrettyPrintVisitor;
begin
if not Assigned(ANode) then
exit('');
visitor := TPrettyPrintVisitor.Create;
try
visitor.Execute(ANode);
Result := visitor.GetResult;
finally
visitor.Free;
end;
end;
end.
-102
View File
@@ -1,102 +0,0 @@
unit Myc.Ast.ViewModel;
interface
uses
System.SysUtils,
System.Types,
System.Generics.Collections,
Myc.Ast.Nodes;
type
// An enum to identify the specific type of an AST node without relying on RTTI or GUIDs.
// This allows the renderer to depend only on the ViewModel layer.
TAstNodeType = (
antUndefined,
antConstant,
antIdentifier,
antBinaryExpression,
antUnaryExpression,
antIfExpression,
antTernaryExpression,
antLambdaExpression,
antFunctionCall,
antBlockExpression,
antVariableDeclaration,
antAssignment,
antIndexer,
antMemberAccess,
antCreateSeries,
antAddSeriesItem,
antSeriesLength
);
// A unique, stable identifier for a visual node instance.
TViewModelID = NativeInt;
// Defines how a view model displays its children.
TVisualizationMode = (vmSyntactic, vmSemantic);
// Metadata tied to a logical IAstNode. All visual instances of this node will share this data.
TLogicalMetadata = record
// The concrete type of the IAstNode, used for type-safe operations in the renderer.
NodeType: TAstNodeType;
Description: String;
end;
// Metadata tied to a specific visual instance of a node, identified by its TViewModelID.
TVisualInstanceMetadata = record
PositionOverride: TPointF;
IsCollapsed: Boolean;
VisualizationMode: TVisualizationMode;
end;
// The ViewModel for a node in the visual graph.
TVisualNodeViewModel = class
private
FId: TViewModelID;
FNode: IAstNode;
FLogicalMetadata: TLogicalMetadata;
FParents: TList<TVisualNodeViewModel>;
FChildren: TList<TVisualNodeViewModel>;
public
constructor Create(const ANode: IAstNode; const AId: TViewModelID; const ALogicalMetadata: TLogicalMetadata);
destructor Destroy; override;
procedure AddChild(const AChild: TVisualNodeViewModel);
property Id: TViewModelID read FId;
property Node: IAstNode read FNode;
property LogicalMetadata: TLogicalMetadata read FLogicalMetadata;
property Children: TList<TVisualNodeViewModel> read FChildren;
property Parents: TList<TVisualNodeViewModel> read FParents;
end;
implementation
{ TVisualNodeViewModel }
constructor TVisualNodeViewModel.Create(const ANode: IAstNode; const AId: TViewModelID; const ALogicalMetadata: TLogicalMetadata);
begin
inherited Create;
FNode := ANode;
FId := AId;
FLogicalMetadata := ALogicalMetadata;
FParents := TList<TVisualNodeViewModel>.Create;
FChildren := TList<TVisualNodeViewModel>.Create;
end;
destructor TVisualNodeViewModel.Destroy;
begin
FParents.Free;
FChildren.Free;
inherited;
end;
procedure TVisualNodeViewModel.AddChild(const AChild: TVisualNodeViewModel);
begin
FChildren.Add(AChild);
AChild.FParents.Add(Self);
end;
end.
+36 -21
View File
@@ -28,10 +28,12 @@ type
class function CreateScope(Parent: IExecutionScope; const Descriptor: IScopeDescriptor = nil): IExecutionScope; static; class function CreateScope(Parent: IExecutionScope; const Descriptor: IScopeDescriptor = nil): IExecutionScope; static;
// --- Existing factory functions --- // --- Existing factory functions ---
class function Constant(AValue: TScalar): IConstantNode; static; class function Constant(const AValue: TDataValue): IConstantNode; overload; static;
class function Constant(const AValue: TScalar): IConstantNode; overload; static;
class function Constant(const AValue: String): IConstantNode; overload; static;
class function Identifier(AName: string): IIdentifierNode; static; class function Identifier(AName: string): IIdentifierNode; static;
class function BinaryExpr(ALeft: IAstNode; AOperator: TBinaryOperator; ARight: IAstNode): IBinaryExpressionNode; static; class function BinaryExpr(ALeft: IAstNode; AOperator: TScalar.TBinaryOp; ARight: IAstNode): IBinaryExpressionNode; static;
class function UnaryExpr(const AOperator: TUnaryOperator; const ARight: IAstNode): IUnaryExpressionNode; static; class function UnaryExpr(const AOperator: TScalar.TUnaryOp; const ARight: IAstNode): IUnaryExpressionNode; static;
class function IfExpr(const ACondition: IAstNode; const AThenBranch, AElseBranch: IAstNode): IIfExpressionNode; static; class function IfExpr(const ACondition: IAstNode; const AThenBranch, AElseBranch: IAstNode): IIfExpressionNode; static;
class function TernaryExpr(const ACondition: IAstNode; const AThenBranch, AElseBranch: IAstNode): ITernaryExpressionNode; static; class function TernaryExpr(const ACondition: IAstNode; const AThenBranch, AElseBranch: IAstNode): ITernaryExpressionNode; static;
class function LambdaExpr(const AParameters: TArray<IIdentifierNode>; const ABody: IAstNode): ILambdaExpressionNode; static; class function LambdaExpr(const AParameters: TArray<IIdentifierNode>; const ABody: IAstNode): ILambdaExpressionNode; static;
@@ -60,10 +62,10 @@ type
TConstantNode = class(TAstNode, IConstantNode) TConstantNode = class(TAstNode, IConstantNode)
private private
FValue: TScalar; FValue: TDataValue;
function GetValue: TScalar; function GetValue: TDataValue;
public public
constructor Create(AValue: TScalar); constructor Create(const AValue: TDataValue);
function Accept(const Visitor: IAstVisitor): TDataValue; override; function Accept(const Visitor: IAstVisitor): TDataValue; override;
end; end;
@@ -84,24 +86,24 @@ type
TBinaryExpressionNode = class(TAstNode, IBinaryExpressionNode) TBinaryExpressionNode = class(TAstNode, IBinaryExpressionNode)
private private
FLeft: IAstNode; FLeft: IAstNode;
FOperator: TBinaryOperator; FOperator: TScalar.TBinaryOp;
FRight: IAstNode; FRight: IAstNode;
function GetLeft: IAstNode; function GetLeft: IAstNode;
function GetOperator: TBinaryOperator; function GetOperator: TScalar.TBinaryOp;
function GetRight: IAstNode; function GetRight: IAstNode;
public public
constructor Create(ALeft: IAstNode; AOperator: TBinaryOperator; ARight: IAstNode); constructor Create(ALeft: IAstNode; AOperator: TScalar.TBinaryOp; ARight: IAstNode);
function Accept(const Visitor: IAstVisitor): TDataValue; override; function Accept(const Visitor: IAstVisitor): TDataValue; override;
end; end;
TUnaryExpressionNode = class(TAstNode, IUnaryExpressionNode) TUnaryExpressionNode = class(TAstNode, IUnaryExpressionNode)
private private
FOperator: TUnaryOperator; FOperator: TScalar.TUnaryOp;
FRight: IAstNode; FRight: IAstNode;
function GetOperator: TUnaryOperator; function GetOperator: TScalar.TUnaryOp;
function GetRight: IAstNode; function GetRight: IAstNode;
public public
constructor Create(const AOperator: TUnaryOperator; const ARight: IAstNode); constructor Create(const AOperator: TScalar.TUnaryOp; const ARight: IAstNode);
function Accept(const Visitor: IAstVisitor): TDataValue; override; function Accept(const Visitor: IAstVisitor): TDataValue; override;
end; end;
@@ -269,9 +271,12 @@ uses
{ TConstantNode } { TConstantNode }
constructor TConstantNode.Create(AValue: TScalar); constructor TConstantNode.Create(const AValue: TDataValue);
begin begin
inherited Create; inherited Create;
// Validate that only allowed constant kinds are used.
if not (AValue.Kind in [vkScalar, vkText, vkVoid]) then
raise EArgumentException.Create('IConstantNode only supports Scalar, Text, and Void values.');
FValue := AValue; FValue := AValue;
end; end;
@@ -280,7 +285,7 @@ begin
Result := Visitor.VisitConstant(Self); Result := Visitor.VisitConstant(Self);
end; end;
function TConstantNode.GetValue: TScalar; function TConstantNode.GetValue: TDataValue;
begin begin
Result := FValue; Result := FValue;
end; end;
@@ -310,7 +315,7 @@ end;
{ TBinaryExpressionNode } { TBinaryExpressionNode }
constructor TBinaryExpressionNode.Create(ALeft: IAstNode; AOperator: TBinaryOperator; ARight: IAstNode); constructor TBinaryExpressionNode.Create(ALeft: IAstNode; AOperator: TScalar.TBinaryOp; ARight: IAstNode);
begin begin
inherited Create; inherited Create;
FLeft := ALeft; FLeft := ALeft;
@@ -328,7 +333,7 @@ begin
Result := FLeft; Result := FLeft;
end; end;
function TBinaryExpressionNode.GetOperator: TBinaryOperator; function TBinaryExpressionNode.GetOperator: TScalar.TBinaryOp;
begin begin
Result := FOperator; Result := FOperator;
end; end;
@@ -340,7 +345,7 @@ end;
{ TUnaryExpressionNode } { TUnaryExpressionNode }
constructor TUnaryExpressionNode.Create(const AOperator: TUnaryOperator; const ARight: IAstNode); constructor TUnaryExpressionNode.Create(const AOperator: TScalar.TUnaryOp; const ARight: IAstNode);
begin begin
inherited Create; inherited Create;
FOperator := AOperator; FOperator := AOperator;
@@ -352,7 +357,7 @@ begin
Result := Visitor.VisitUnaryExpression(Self); Result := Visitor.VisitUnaryExpression(Self);
end; end;
function TUnaryExpressionNode.GetOperator: TUnaryOperator; function TUnaryExpressionNode.GetOperator: TScalar.TUnaryOp;
begin begin
Result := FOperator; Result := FOperator;
end; end;
@@ -743,17 +748,27 @@ begin
Result := TBlockExpressionNode.Create(AExpressions); Result := TBlockExpressionNode.Create(AExpressions);
end; end;
class function TAst.Constant(AValue: TScalar): IConstantNode; class function TAst.Constant(const AValue: TDataValue): IConstantNode;
begin begin
Result := TConstantNode.Create(AValue); Result := TConstantNode.Create(AValue);
end; end;
class function TAst.Constant(const AValue: TScalar): IConstantNode;
begin
Result := TConstantNode.Create(TDataValue(AValue));
end;
class function TAst.Constant(const AValue: String): IConstantNode;
begin
Result := TConstantNode.Create(TDataValue(AValue));
end;
class function TAst.CreateSeries(const ADefinition: String): ICreateSeriesNode; class function TAst.CreateSeries(const ADefinition: String): ICreateSeriesNode;
begin begin
Result := TCreateSeriesNode.Create(ADefinition); Result := TCreateSeriesNode.Create(ADefinition);
end; end;
class function TAst.BinaryExpr(ALeft: IAstNode; AOperator: TBinaryOperator; ARight: IAstNode): IBinaryExpressionNode; class function TAst.BinaryExpr(ALeft: IAstNode; AOperator: TScalar.TBinaryOp; ARight: IAstNode): IBinaryExpressionNode;
begin begin
Result := TBinaryExpressionNode.Create(ALeft, AOperator, ARight); Result := TBinaryExpressionNode.Create(ALeft, AOperator, ARight);
end; end;
@@ -823,7 +838,7 @@ begin
Result := TLambdaExpressionNode.Create(AParameters, ABody); Result := TLambdaExpressionNode.Create(AParameters, ABody);
end; end;
class function TAst.UnaryExpr(const AOperator: TUnaryOperator; const ARight: IAstNode): IUnaryExpressionNode; class function TAst.UnaryExpr(const AOperator: TScalar.TUnaryOp; const ARight: IAstNode): IUnaryExpressionNode;
begin begin
Result := TUnaryExpressionNode.Create(AOperator, ARight); Result := TUnaryExpressionNode.Create(AOperator, ARight);
end; end;
+18 -31
View File
@@ -12,8 +12,8 @@ uses
type type
TRttiAstHelper = class TRttiAstHelper = class
private private
// Maps a Delphi RTTI type to its TScalarKind. // Maps a Delphi RTTI type to its TScalar.TKind.
class function TypeToScalarKind(AType: TRttiType): TScalarKind; static; class function TypeToScalarKind(AType: TRttiType): TScalar.TKind; static;
public public
// Creates a JSON definition string from a record type info. // Creates a JSON definition string from a record type info.
class function RecordDefinitionToJson<T: record>: string; static; class function RecordDefinitionToJson<T: record>: string; static;
@@ -43,7 +43,6 @@ begin
exit; exit;
try try
// The root element is expected to be a JSON array directly.
if not (jsonValue is TJSONArray) then if not (jsonValue is TJSONArray) then
exit; exit;
@@ -54,12 +53,11 @@ begin
begin begin
fieldObj := fieldsArray.Items[i] as TJSONObject; fieldObj := fieldsArray.Items[i] as TJSONObject;
if not Assigned(fieldObj) then if not Assigned(fieldObj) then
continue; // or raise error continue;
fields[i].Name := fieldObj.GetValue<string>('name'); fields[i].Name := fieldObj.GetValue<string>('name');
kindStr := fieldObj.GetValue<string>('kind'); kindStr := fieldObj.GetValue<string>('kind');
fields[i].Kind := TScalarKind(GetEnumValue(TypeInfo(TScalarKind), kindStr)); fields[i].Kind := TScalar.TKind(GetEnumValue(TypeInfo(TScalar.TKind), kindStr));
end; end;
Result := TScalarRecordDefinition.Create(fields); Result := TScalarRecordDefinition.Create(fields);
finally finally
@@ -84,54 +82,43 @@ begin
rttiRecordType := rttiType as TRttiRecordType; rttiRecordType := rttiType as TRttiRecordType;
// The root element is now the array itself.
fieldsArray := TJSONArray.Create; fieldsArray := TJSONArray.Create;
try try
for field in rttiRecordType.GetFields do for field in rttiRecordType.GetFields do
begin begin
fieldObj := TJSONObject.Create; fieldObj := TJSONObject.Create;
fieldObj.AddPair('name', TJSONString.Create(field.Name)); fieldObj.AddPair('name', TJSONString.Create(field.Name));
fieldObj.AddPair('kind', TJSONString.Create(GetEnumName(TypeInfo(TScalarKind), Ord(TypeToScalarKind(field.FieldType))))); fieldObj.AddPair('kind', TJSONString.Create(GetEnumName(TypeInfo(TScalar.TKind), Ord(TypeToScalarKind(field.FieldType)))));
fieldsArray.Add(fieldObj); fieldsArray.Add(fieldObj);
end; end;
// Convert the array directly to a JSON string.
Result := fieldsArray.ToJSON; Result := fieldsArray.ToJSON;
finally finally
fieldsArray.Free; fieldsArray.Free;
end; end;
end; end;
class function TRttiAstHelper.TypeToScalarKind(AType: TRttiType): TScalarKind; class function TRttiAstHelper.TypeToScalarKind(AType: TRttiType): TScalar.TKind;
var var
typeHandle: PTypeInfo; typeHandle: PTypeInfo;
begin begin
// 1. Use a case statement or if-checks on AType.Handle.
typeHandle := AType.Handle; typeHandle := AType.Handle;
// 2. Compare with TypeInfo(Integer), TypeInfo(Double), TypeInfo(TDecimal) etc. if typeHandle = TypeInfo(Int64) then
if typeHandle = TypeInfo(Integer) then Result := TScalar.TKind.Ordinal
Result := skInteger else if typeHandle = TypeInfo(Integer) then
else if typeHandle = TypeInfo(Int64) then Result := TScalar.TKind.Ordinal
Result := skInt64
else if typeHandle = TypeInfo(UInt64) then else if typeHandle = TypeInfo(UInt64) then
Result := skUInt64 Result := TScalar.TKind.Ordinal
else if typeHandle = TypeInfo(Single) then
Result := skSingle
else if typeHandle = TypeInfo(Double) then
Result := skDouble
else if typeHandle = TypeInfo(TDateTime) then
Result := skDateTime
else if typeHandle = TypeInfo(Boolean) then else if typeHandle = TypeInfo(Boolean) then
Result := skBoolean Result := TScalar.TKind.Ordinal
else if typeHandle = TypeInfo(Char) then else if typeHandle = TypeInfo(Double) then
Result := skChar Result := TScalar.TKind.Float
else if typeHandle = TypeInfo(TDecimal) then else if typeHandle = TypeInfo(Single) then
Result := skDecimal Result := TScalar.TKind.Float
else if typeHandle = TypeInfo(TTimestamp) then else if typeHandle = TypeInfo(TDateTime) then
Result := skTimestamp Result := TScalar.TKind.Float // Correctly handle TDateTime as a Float
else else
// 4. Raise an exception for unsupported types.
raise EArgumentException.CreateFmt('Unsupported record field type: %s', [AType.Name]); raise EArgumentException.CreateFmt('Unsupported record field type: %s', [AType.Name]);
end; end;
+238 -1128
View File
File diff suppressed because it is too large Load Diff
+6
View File
@@ -53,6 +53,7 @@ type
class function Void: TDataValue; inline; static; class function Void: TDataValue; inline; static;
class operator Implicit(const AValue: TScalar): TDataValue; overload; inline; class operator Implicit(const AValue: TScalar): TDataValue; overload; inline;
class operator Implicit(const AValue: TDataValue): TScalar; overload; inline;
class operator Implicit(const AValue: String): TDataValue; overload; inline; class operator Implicit(const AValue: String): TDataValue; overload; inline;
class operator Implicit(const AValue: TDataValue.TFunc): TDataValue; overload; inline; class operator Implicit(const AValue: TDataValue.TFunc): TDataValue; overload; inline;
class operator Implicit(const AValue: TObject): TDataValue; overload; inline; class operator Implicit(const AValue: TObject): TDataValue; overload; inline;
@@ -435,6 +436,11 @@ begin
Result.FInterface := TObjVal.Create(AValue); Result.FInterface := TObjVal.Create(AValue);
end; end;
class operator TDataValue.Implicit(const AValue: TDataValue): TScalar;
begin
Result := AValue.AsScalar;
end;
{ TDataValueKindHelper } { TDataValueKindHelper }
function TDataValueKindHelper.ToString: string; function TDataValueKindHelper.ToString: string;