diff --git a/ASTPlayground/MainForm.fmx b/ASTPlayground/MainForm.fmx index f5665ba..a04de77 100644 --- a/ASTPlayground/MainForm.fmx +++ b/ASTPlayground/MainForm.fmx @@ -132,6 +132,12 @@ object Form1: TForm1 TextSettings.Trimming = None OnClick = OHLCButtonClick end + object DebugBox: TCheckBox + Position.X = 24.000000000000000000 + Position.Y = 360.000000000000000000 + TabOrder = 17 + Text = 'Debug' + end end object Panel2: TPanel Align = Client diff --git a/ASTPlayground/MainForm.pas b/ASTPlayground/MainForm.pas index 21e1a76..c3ecbdb 100644 --- a/ASTPlayground/MainForm.pas +++ b/ASTPlayground/MainForm.pas @@ -60,6 +60,7 @@ type FlowOnlyBox: TCheckBox; SeriesTestButton: TButton; OHLCButton: TButton; + DebugBox: TCheckBox; procedure ClearButtonClick(Sender: TObject); procedure FormCreate(Sender: TObject); procedure CrerateTriggerExampleButtonClick(Sender: TObject); @@ -78,6 +79,7 @@ type FLastAst: IExpressionNode; FGScope: IExecutionScope; FWorkspace: TAuraWorkspace; + function CreateVisitor(const AScope: IExecutionScope): IAstVisitor; procedure WorkspaceMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Single); public { Public declarations } @@ -102,6 +104,18 @@ begin FWorkspace.Repaint; end; +function TForm1.CreateVisitor(const AScope: IExecutionScope): IAstVisitor; +begin + // Conditionally create a debug visitor if the checkbox is checked. + if DebugBox.IsChecked then + begin + Memo1.Lines.Add('--- Creating DEBUG visitor ---'); + Result := TDebugEvaluatorVisitor.Create(AScope, Memo1.Lines, ShowScopeBox.IsChecked); + end + else + Result := TEvaluatorVisitor.Create(AScope); +end; + procedure TForm1.FormCreate(Sender: TObject); begin FWorkspace := TAuraWorkspace.Create(Panel2); @@ -133,6 +147,7 @@ begin Memo1.Lines.Clear; Memo1.Lines.Add('--- Debug Evaluator Trace ---'); + // This button ALWAYS uses the debug visitor, regardless of the checkbox. visitor := TDebugEvaluatorVisitor.Create(scope, Memo1.Lines, ShowScopeBox.IsChecked, 0); sw := TStopwatch.StartNew; @@ -203,38 +218,7 @@ begin Application.ProcessMessages; // Update UI before blocking sw.Start; - { - root := - TAst.Block( - [ - TAst.VarDecl( - TAst.Identifier('fib'), - TAst.LambdaExpr( - [TAst.Identifier('n')], - TAst.IfExpr( - TAst.BinaryExpr(TAst.Identifier('n'), boLess, TAst.Constant(TScalar.FromInt64(2))), - TAst.Assign(TAst.Identifier('Result'), TAst.Identifier('n')), - TAst.Assign( - TAst.Identifier('Result'), - TAst.BinaryExpr( - TAst.FunctionCall( - TAst.Identifier('fib'), - [TAst.BinaryExpr(TAst.Identifier('n'), boSubtract, TAst.Constant(TScalar.FromInt64(1)))] - ), - boAdd, - TAst.FunctionCall( - TAst.Identifier('fib'), - [TAst.BinaryExpr(TAst.Identifier('n'), boSubtract, TAst.Constant(TScalar.FromInt64(2)))] - ) - ) - ) - ) - ) - ), - TAst.FunctionCall(TAst.Identifier('fib'), [TAst.Constant(TScalar.FromInt64(30))]) - ] - ); -} + root := TAst.Block( [ @@ -269,7 +253,7 @@ begin FLastAst := root; scope := TExecutionScope.Create(nil); - visitor := TEvaluatorVisitor.Create(scope); + visitor := CreateVisitor(scope); result := root.Accept(visitor); sw.Stop; @@ -325,31 +309,6 @@ begin root := TAst.Block( [ - TAst.FunctionCall( - TAst.LambdaExpr( - [TAst.Identifier('n')], - TAst.Block( - [ - TAst.IfExpr( - TAst.BinaryExpr(TAst.Identifier('n'), boLess, TAst.Constant(TScalar.FromInt64(2))), - TAst.Assign(TAst.Identifier('Result'), TAst.Constant(TScalar.FromInt64(1))), - TAst.Assign( - TAst.Identifier('Result'), - TAst.BinaryExpr( - TAst.Identifier('n'), - boMultiply, - TAst.FunctionCall( - TAst.Identifier('Self'), - [TAst.BinaryExpr(TAst.Identifier('n'), boSubtract, TAst.Constant(TScalar.FromInt64(1)))] - ) - ) - ) - ) - ] - ) - ), - [TAst.Constant(TScalar.FromInt64(20))] - ), TAst.VarDecl( TAst.Identifier('factorial'), TAst.LambdaExpr( @@ -363,7 +322,7 @@ begin TAst.Identifier('n'), boMultiply, TAst.FunctionCall( - TAst.Identifier('Self'), + TAst.Identifier('factorial'), [TAst.BinaryExpr(TAst.Identifier('n'), boSubtract, TAst.Constant(TScalar.FromInt64(1)))] ) ) @@ -379,7 +338,7 @@ begin FLastAst := root; scope := TExecutionScope.Create(nil); - visitor := TEvaluatorVisitor.Create(scope); + visitor := CreateVisitor(scope); result := root.Accept(visitor); sw.Stop; @@ -437,10 +396,6 @@ begin // --- 2. Act (in Script) --- Memo1.Lines.Add('2. Executing script...'); - // This script now tests member access and indexing the resulting member series. - // let closeColumn = ohlcvSeries.Close; - // let secondClose = closeColumn[1]; - // secondClose ast := TAst.LambdaExpr( [], @@ -459,7 +414,7 @@ begin ); FLastAst := ast; - visitor := TEvaluatorVisitor.Create(FGScope); + visitor := CreateVisitor(FGScope); resultValue := TAst.FunctionCall(ast, []).Accept(visitor); Memo1.Lines.Add(' - Script finished.'); @@ -530,7 +485,7 @@ begin FLastAst := main; scope := TExecutionScope.Create(FGScope); - visitor := TEvaluatorVisitor.Create(scope); + visitor := CreateVisitor(scope); result := TAst.FunctionCall(main, []).Accept(visitor); sw.Stop; @@ -582,7 +537,7 @@ begin scope := TExecutionScope.Create(FGScope); - visitor := TEvaluatorVisitor.Create(scope); + visitor := CreateVisitor(scope); result := root.Accept(visitor); sw.Stop; @@ -620,15 +575,9 @@ begin ] ); - // lambdaAst := - // TAst.LambdaExpr( - // [TAst.Identifier('summand')], - // TAst.Assign(TAst.Identifier('X'), TAst.BinaryExpr(TAst.Identifier('X'), boAdd, TAst.Identifier('summand'))) - // ); - // Evaluate the lambda to create a closure and store it in the scope. // The closure captures the scope where 'X' is defined. - visitor := TEvaluatorVisitor.Create(FGScope); + visitor := CreateVisitor(FGScope); blk.Accept(visitor); // FLastAst is not used for this parameterized example. @@ -652,7 +601,7 @@ begin callAst := TAst.FunctionCall(TAst.Identifier('tickHandler'), [TAst.Constant(TScalar.FromInt64(1))]); // Execute the call AST. - visitor := TEvaluatorVisitor.Create(FGScope); + visitor := CreateVisitor(FGScope); callAst.Accept(visitor); FLastAst := callAst; @@ -684,7 +633,7 @@ begin FLastAst := callAst; // Execute the call AST. - visitor := TEvaluatorVisitor.Create(FGScope); + visitor := CreateVisitor(FGScope); callAst.Accept(visitor); // Get the updated value of 'X' from the scope and display it. @@ -715,8 +664,8 @@ end; procedure TForm1.OHLCButtonClick(Sender: TObject); const - numRecs = 1000; - lookback = 100; + numRecs = 100; + lookback = 50; smaSlowLength = 20; smaFastLength = 5; var @@ -734,7 +683,7 @@ var begin // 1. Setup Memo1.Lines.Clear; - Memo1.Lines.Add(Format('--- Simulating SMA Crossover Strategy for %d ticks ---', [numRecs])); + Memo1.Lines.Add(Format('--- Simulating O(1) SMA Crossover Strategy for %d ticks ---', [numRecs])); FGScope.Clear; RegisterNativeFunctions(FGScope); sw := TStopwatch.StartNew; @@ -742,61 +691,66 @@ begin recordDef := TRttiAstHelper.JsonToRecordDefinition(TRttiAstHelper.RecordDefinitionToJson(TypeInfo(TOHLCV))); series := TScalarRecordSeries.Create(recordDef); - // 2. Create the setup AST. This block defines all necessary functions. + // 2. Create the setup AST with O(1) SMA implementation setupAst := TAst.Block( [ - // let sumRange = (s, n) => (n <= 0) ? 0.0 : (s[n - 1] + sumRange(s, n - 1)); TAst.VarDecl( - TAst.Identifier('sumRange'), - TAst.LambdaExpr( - [TAst.Identifier('s'), TAst.Identifier('n')], - TAst.TernaryExpr( - TAst.BinaryExpr(TAst.Identifier('n'), boLessOrEqual, TAst.Constant(TScalar.FromInt64(0))), - TAst.Constant(TScalar.FromDouble(0.0)), // Base case for recursion - TAst.BinaryExpr( - TAst.Indexer( - TAst.Identifier('s'), - TAst.BinaryExpr(TAst.Identifier('n'), boSubtract, TAst.Constant(TScalar.FromInt64(1))) - ), - boAdd, - TAst.FunctionCall( - TAst.Identifier('sumRange'), - [ - TAst.Identifier('s'), - TAst.BinaryExpr(TAst.Identifier('n'), boSubtract, TAst.Constant(TScalar.FromInt64(1))) - ] - ) - ) - ) - ) - ), - // let CreateSMA = (len) => (series) => sumRange(series, len) / len; - TAst.VarDecl( - TAst.Identifier('CreateSMA'), + TAst.Identifier('CreateSMA_O1'), TAst.LambdaExpr( [TAst.Identifier('len')], - TAst.LambdaExpr( - [TAst.Identifier('series')], - TAst.BinaryExpr( - TAst.FunctionCall(TAst.Identifier('sumRange'), [TAst.Identifier('series'), TAst.Identifier('len')]), - boDivide, - TAst.Identifier('len') - ) + TAst.Block( + [ + TAst.VarDecl(TAst.Identifier('sum'), TAst.Constant(TScalar.FromDouble(0.0))), + TAst.VarDecl(TAst.Identifier('values'), TAst.CreateSeries('double')), + TAst.LambdaExpr( + [TAst.Identifier('val')], + TAst.Block( + [ + TAst.IfExpr( + TAst.BinaryExpr( + TAst.SeriesLength(TAst.Identifier('values')), + boGreaterOrEqual, + TAst.Identifier('len') + ), + TAst.Assign( + TAst.Identifier('sum'), + TAst.BinaryExpr( + TAst.Identifier('sum'), + boSubtract, + TAst.Indexer( + TAst.Identifier('values'), + TAst.BinaryExpr( + TAst.Identifier('len'), + boSubtract, + TAst.Constant(TScalar.FromInt64(1)) + ) + ) + ) + ), + nil + ), + TAst.Assign( + TAst.Identifier('sum'), + TAst.BinaryExpr(TAst.Identifier('sum'), boAdd, TAst.Identifier('val')) + ), + TAst.AddSeriesItem(TAst.Identifier('values'), TAst.Identifier('val'), TAst.Identifier('len')), + TAst.BinaryExpr(TAst.Identifier('sum'), boDivide, TAst.SeriesLength(TAst.Identifier('values'))) + ] + ) + ) + ] ) ) ), - // let smaFast = CreateSMA(5); TAst.VarDecl( TAst.Identifier('smaFast'), - TAst.FunctionCall(TAst.Identifier('CreateSMA'), [TAst.Constant(TScalar.FromInt64(smaFastLength))]) + TAst.FunctionCall(TAst.Identifier('CreateSMA_O1'), [TAst.Constant(TScalar.FromInt64(smaFastLength))]) ), - // let smaSlow = CreateSMA(20); TAst.VarDecl( TAst.Identifier('smaSlow'), - TAst.FunctionCall(TAst.Identifier('CreateSMA'), [TAst.Constant(TScalar.FromInt64(smaSlowLength))]) + TAst.FunctionCall(TAst.Identifier('CreateSMA_O1'), [TAst.Constant(TScalar.FromInt64(smaSlowLength))]) ), - // Define the main strategy function TAst.VarDecl( TAst.Identifier('maCrossStrategy'), TAst.LambdaExpr( @@ -804,22 +758,25 @@ begin TAst.Block( [ TAst.VarDecl( - TAst.Identifier('close'), - TAst.MemberAccess(TAst.Identifier('ohlcv'), TAst.Identifier('Close')) + TAst.Identifier('currentClose'), + TAst.MemberAccess( + TAst.Indexer(TAst.Identifier('ohlcv'), TAst.Constant(TScalar.FromInt64(0))), + TAst.Identifier('Close') + ) ), TAst.VarDecl( TAst.Identifier('valSmaFast'), - TAst.FunctionCall(TAst.Identifier('smaFast'), [TAst.Identifier('close')]) + TAst.FunctionCall(TAst.Identifier('smaFast'), [TAst.Identifier('currentClose')]) ), TAst.VarDecl( TAst.Identifier('valSmaSlow'), - TAst.FunctionCall(TAst.Identifier('smaSlow'), [TAst.Identifier('close')]) + TAst.FunctionCall(TAst.Identifier('smaSlow'), [TAst.Identifier('currentClose')]) ), TAst.TernaryExpr( TAst.BinaryExpr(TAst.Identifier('valSmaFast'), boGreater, TAst.Identifier('valSmaSlow')), - TAst.Constant(TScalar.FromInt64(1)), // Buy signal + TAst.Constant(TScalar.FromInt64(1)), TAst.Constant(TScalar.FromInt64(-1)) - ) // Sell signal + ) ] ) ) @@ -827,13 +784,9 @@ begin ] ); - FLastAst := setupAst; // Store for visualization - visitor := TEvaluatorVisitor.Create(FGScope); - - // Execute the setup script once to define all functions in the scope. + FLastAst := setupAst; + visitor := CreateVisitor(FGScope); setupAst.Accept(visitor); - - // Create the AST for the function call that will be executed on each tick. callAst := TAst.FunctionCall(TAst.Identifier('maCrossStrategy'), [TAst.Identifier('current_series')]); // 3. Simulation Loop @@ -846,7 +799,6 @@ begin var nw := Now; for i := 1 to numRecs do begin - // Generate a random OHLCV record ohlcvRec.Timestamp := nw + (i / (24 * 60)); ohlcvRec.Open := lastClose + (Random * 0.05); ohlcvRec.Close := lastClose + (Random - 0.49) * 2; @@ -870,20 +822,13 @@ begin series.Add(recordValue, lookback); - // Execute the strategy if we have enough data for the slowest indicator if series.TotalCount >= smaSlowLength then begin - // Place the current data into the scope for the callAst to find FGScope.SetValue('current_series', TAstValue.FromRecordSeries(series)); - - // Correctly call the strategy function via its identifier resultValue := callAst.Accept(visitor); - if (i mod 100 = 0) or (i = numRecs) then - begin - Memo1.Lines.Add(Format('Tick %d/%d: Close = %.2f, Signal = %s', [i, numRecs, ohlcvRec.Close, resultValue.ToString])); - Application.ProcessMessages; - end; + Memo1.Lines.Add(Format('Tick %d/%d: Close = %.2f, Signal = %s', [i, numRecs, ohlcvRec.Close, resultValue.ToString])); + Application.ProcessMessages; end; end; diff --git a/ASTPlayground/Myc.Ast.Visualizer.pas b/ASTPlayground/Myc.Ast.Visualizer.pas index 338a571..f739af6 100644 --- a/ASTPlayground/Myc.Ast.Visualizer.pas +++ b/ASTPlayground/Myc.Ast.Visualizer.pas @@ -130,6 +130,9 @@ type function VisitAssignment(const Node: IAssignmentNode): TAstValue; function VisitIndexer(const Node: IIndexerNode): TAstValue; function VisitMemberAccess(const Node: IMemberAccessNode): TAstValue; + function VisitCreateSeries(const Node: ICreateSeriesNode): TAstValue; + function VisitAddSeriesItem(const Node: IAddSeriesItemNode): TAstValue; + function VisitSeriesLength(const Node: ISeriesLengthNode): TAstValue; end; TAuraWorkspace = class(TStyledControl) @@ -204,6 +207,9 @@ type function VisitAssignment(const Node: IAssignmentNode): TAstValue; function VisitIndexer(const Node: IIndexerNode): TAstValue; function VisitMemberAccess(const Node: IMemberAccessNode): TAstValue; + function VisitCreateSeries(const Node: ICreateSeriesNode): TAstValue; + function VisitAddSeriesItem(const Node: IAddSeriesItemNode): TAstValue; + function VisitSeriesLength(const Node: ISeriesLengthNode): TAstValue; end; constructor TAstToTextVisitor.Create; @@ -213,6 +219,18 @@ end; { TAstToTextVisitor } +function TAstToTextVisitor.VisitAddSeriesItem(const Node: IAddSeriesItemNode): TAstValue; +var + seriesStr, valueStr, lookbackStr: string; +begin + seriesStr := Node.Series.Accept(Self).AsText; + valueStr := Node.Value.Accept(Self).AsText; + lookbackStr := ''; + if Assigned(Node.Lookback) then + lookbackStr := ', ' + Node.Lookback.Accept(Self).AsText; + Result := TAstValue.FromText(Format('%s.add(%s%s)', [seriesStr, valueStr, lookbackStr])); +end; + function TAstToTextVisitor.VisitAssignment(const Node: IAssignmentNode): TAstValue; begin Result := TAstValue.FromText(Node.Identifier.Name + ' := ' + Node.Value.Accept(Self).AsText); @@ -235,6 +253,11 @@ begin Result := TAstValue.FromText(Node.Value.ToString); end; +function TAstToTextVisitor.VisitCreateSeries(const Node: ICreateSeriesNode): TAstValue; +begin + Result := TAstValue.FromText('new series(' + Node.Definition + ')'); +end; + function TAstToTextVisitor.VisitFunctionCall(const Node: IFunctionCallNode): TAstValue; var i: Integer; @@ -312,6 +335,15 @@ begin Result := TAstValue.FromText(Format('%s.%s', [baseStr, Node.Member.Name])); end; +function TAstToTextVisitor.VisitSeriesLength(const Node: ISeriesLengthNode): TAstValue; +var + seriesStr: string; +begin + // Get the string representation of the series identifier by visiting the node. + seriesStr := Node.Series.Accept(Self).AsText; + Result := TAstValue.FromText(Format('length(%s)', [seriesStr])); +end; + function TAstToTextVisitor.VisitTernaryExpression(const Node: ITernaryExpressionNode): TAstValue; begin var condStr := Node.Condition.Accept(Self).AsText; @@ -573,6 +605,62 @@ begin Result := Pos('{', Text) = 0; end; +function TAstToAuraNodeVisitor.VisitAddSeriesItem(const Node: IAddSeriesItemNode): TAstValue; +var + details: string; + inputs: TArray; +begin + if (FMode = vmControlFlow) and TryGetDescr(Node, details) then + begin + var addNode := CreateNodeControl('Add to Series', details); + CreateEntry(addNode); + CreateExit(addNode, ''); + FinalizeNodeLayout(addNode); + end + else + begin + if Assigned(Node.Lookback) then + inputs := [Node.Series, Node.Value, Node.Lookback] + else + inputs := [Node.Series, Node.Value]; + + VisitOperatorNode( + inputs, + function(const InputResults: TAuraNodeResultList): TAuraNodeResult + var + addNode: TAuraNode; + seriesPin, valuePin, lookbackPin: TControl; + begin + addNode := BuildNodeControl('Add to Series', ''); + CreateEntry(addNode); + + seriesPin := CreateInput(addNode, 'Series'); + valuePin := CreateInput(addNode, 'Value'); + + if Assigned(InputResults[0].OutputPin) then + FConnections.Add(TPinConnection.Create(InputResults[0].OutputPin, seriesPin)); + if Assigned(InputResults[1].OutputPin) then + FConnections.Add(TPinConnection.Create(InputResults[1].OutputPin, valuePin)); + + if InputResults.Count > 2 then + begin + lookbackPin := CreateInput(addNode, 'Lookback'); + if Assigned(InputResults[2].OutputPin) then + FConnections.Add(TPinConnection.Create(InputResults[2].OutputPin, lookbackPin)); + end; + + CreateExit(addNode, ''); + + FinalizeNodeLayout(addNode); + Result.LayoutNode := addNode; + Result.OutputPin := nil; // No data output + end, + vaTop + ); + end; + Result := TAstValue.Void; +end; + function TAstToAuraNodeVisitor.VisitAssignment(const Node: IAssignmentNode): TAstValue; begin var details: String; @@ -824,6 +912,18 @@ begin FinalizeNodeLayout(containerNode); end; +function TAstToAuraNodeVisitor.VisitCreateSeries(const Node: ICreateSeriesNode): TAstValue; +var + seriesNode: TAuraNode; +begin + // This node creates a new series. It has no inputs, only a data output. + // The definition is a string literal, so it's part of the node's title. + seriesNode := CreateNodeControl('Create Series', Node.Definition); + FLastResult.OutputPin := CreateOutput(seriesNode); + FinalizeNodeLayout(seriesNode); + Result := TAstValue.Void; +end; + function TAstToAuraNodeVisitor.VisitFunctionCall(const Node: IFunctionCallNode): TAstValue; var inputExpressions: TArray; @@ -951,16 +1051,16 @@ begin // Else branch (if it exists) elseEndY := thenEndY; + FCurrentExec.Clear; + CreateExit(ifNode, 'Else'); if Assigned(Node.ElseBranch) then begin - FCurrentExec.Clear; - CreateExit(ifNode, 'Else'); FCurrentPos.X := branchStartX; FCurrentPos.Y := thenEndY; Node.ElseBranch.Accept(Self); elseEndY := FCurrentPos.Y; - execPathes.AddRange(FCurrentExec); end; + execPathes.AddRange(FCurrentExec); // Finalize layout now that all pins are added. FinalizeNodeLayout(ifNode); @@ -1157,6 +1257,38 @@ begin end; end; +function TAstToAuraNodeVisitor.VisitSeriesLength(const Node: ISeriesLengthNode): TAstValue; +begin + // Use the standard helper for operator-style nodes. + VisitOperatorNode( + [Node.Series], // The single input for this operator is the series identifier. + function(const InputResults: TAuraNodeResultList): TAuraNodeResult + var + lengthNode: TAuraNode; + seriesPin, outPin: TControl; + begin + // Create the visual node for the 'length' operation. + lengthNode := BuildNodeControl('Series Length', ''); + + // Create an input pin for the series and an output pin for the result. + seriesPin := CreateInput(lengthNode, 'Series'); + outPin := CreateOutput(lengthNode); + + // Connect the output of the visited series identifier to our input pin. + if Assigned(InputResults[0].OutputPin) then + FConnections.Add(TPinConnection.Create(InputResults[0].OutputPin, seriesPin)); + + // Finalize the node's layout to correctly position the pins. + FinalizeNodeLayout(lengthNode); + + // The result of this visitation is the new node and its output pin. + Result.LayoutNode := lengthNode; + Result.OutputPin := outPin; + end + ); + Result := TAstValue.Void; +end; + function TAstToAuraNodeVisitor.VisitTernaryExpression(const Node: ITernaryExpressionNode): TAstValue; begin var exprStr: String; diff --git a/Src/AST/Myc.Ast.Evaluator.pas b/Src/AST/Myc.Ast.Evaluator.pas index 4b1f258..e8377e3 100644 --- a/Src/AST/Myc.Ast.Evaluator.pas +++ b/Src/AST/Myc.Ast.Evaluator.pas @@ -33,6 +33,9 @@ type function VisitAssignment(const Node: IAssignmentNode): TAstValue; virtual; function VisitIndexer(const Node: IIndexerNode): TAstValue; virtual; function VisitMemberAccess(const Node: IMemberAccessNode): TAstValue; virtual; + function VisitCreateSeries(const Node: ICreateSeriesNode): TAstValue; virtual; + function VisitAddSeriesItem(const Node: IAddSeriesItemNode): TAstValue; virtual; + function VisitSeriesLength(const Node: ISeriesLengthNode): TAstValue; end; // TDebugEvaluatorVisitor now overrides all visit methods for full tracing @@ -63,6 +66,8 @@ type function VisitAssignment(const Node: IAssignmentNode): TAstValue; override; function VisitIndexer(const Node: IIndexerNode): TAstValue; override; function VisitMemberAccess(const Node: IMemberAccessNode): TAstValue; override; + function VisitCreateSeries(const Node: ICreateSeriesNode): TAstValue; override; + function VisitAddSeriesItem(const Node: IAddSeriesItemNode): TAstValue; override; end; // Registers all native core functions in the given scope. @@ -71,7 +76,9 @@ procedure RegisterNativeFunctions(const AScope: IExecutionScope); implementation uses + System.TypInfo, Myc.Data.Decimal, + Myc.Data.Series, Myc.Ast.Scope, Myc.Ast.Printer, Myc.Data.Scalar.JSON; // Added for JsonToRecordDefinition @@ -105,6 +112,61 @@ type property Method: TNativeFunction read FMethod; end; +// --- Helper Functions --- + +function ScalarKindToString(AKind: TScalarKind): string; +begin + case AKind of + skInteger: Result := 'integer'; + skInt64: Result := 'int64'; + skUInt64: Result := 'uint64'; + skSingle: Result := 'single'; + skDouble: Result := 'double'; + skDateTime: Result := 'datetime'; + skTimestamp: Result := 'timestamp'; + skBoolean: Result := 'boolean'; + skChar: Result := 'char'; + skPChar: Result := 'pchar'; + skString: Result := 'string'; + skBytes: Result := 'bytes'; + skDecimal: Result := 'decimal'; + else + Result := 'unknown'; + end; +end; + +function StringToScalarKind(const AName: string): TScalarKind; +begin + if SameText(AName, 'integer') then + Result := skInteger + else if SameText(AName, 'int64') then + Result := skInt64 + else if SameText(AName, 'uint64') then + Result := skUInt64 + else if SameText(AName, 'single') then + Result := skSingle + else if SameText(AName, 'double') then + Result := skDouble + else if SameText(AName, 'datetime') then + Result := skDateTime + else if SameText(AName, 'timestamp') then + Result := skTimestamp + else if SameText(AName, 'boolean') then + Result := skBoolean + else if SameText(AName, 'char') then + Result := skChar + else if SameText(AName, 'pchar') then + Result := skPChar + else if SameText(AName, 'string') then + Result := skString + else if SameText(AName, 'bytes') then + Result := skBytes + else if SameText(AName, 'decimal') then + Result := skDecimal + else + raise EArgumentException.CreateFmt('Unknown scalar type name: "%s"', [AName]); +end; + // --- Native Functions Implementation --- function NativeCreateRecordSeries(const Args: TArray): TAstValue; @@ -216,6 +278,66 @@ begin end; end; +function TEvaluatorVisitor.VisitAddSeriesItem(const Node: IAddSeriesItemNode): TAstValue; +var + itemValue, lookbackValue: TAstValue; + lookback: Int64; + varName: string; + seriesVar: TAstValue; +begin + // The target series is now guaranteed to be an identifier by the AST node definition. + varName := Node.Series.Name; + if not FScope.FindValue(varName, seriesVar) then + raise EArgumentException.CreateFmt('Identifier not found: "%s"', [varName]); + + itemValue := Node.Value.Accept(Self); + + lookback := -1; // Default: no lookback limit + if Assigned(Node.Lookback) then + begin + lookbackValue := Node.Lookback.Accept(Self); + if (lookbackValue.Kind <> avkScalar) or not (lookbackValue.AsScalar.Kind in [skInteger, skInt64]) then + raise EArgumentException.Create('Lookback parameter must be an integer.'); + + if lookbackValue.AsScalar.Kind = skInteger then + lookback := lookbackValue.AsScalar.Value.AsInteger + else + lookback := lookbackValue.AsScalar.Value.AsInt64; + end; + + // Dispatch based on series type + case seriesVar.Kind of + avkSeries: + begin + if (itemValue.Kind <> avkScalar) then + raise EArgumentException.Create('Can only add scalar values to a TScalarSeries.'); + + with seriesVar.AsSeries.Value do + begin + if (itemValue.AsScalar.Kind <> Kind) then + raise EArgumentException.CreateFmt( + 'Type mismatch: Cannot add %s to a series of %s.', + [ScalarKindToString(itemValue.AsScalar.Kind), ScalarKindToString(Kind)]); + + Items.Add(itemValue.AsScalar.Value, lookback); + end; + end; + avkRecordSeries: + begin + if (itemValue.Kind <> avkRecord) then + raise EArgumentException.Create('Can only add record values to a TScalarRecordSeries.'); + + with seriesVar.AsRecordSeries.Value do + begin + Add(itemValue.AsRecord, lookback); + end; + end; + else + raise EArgumentException.Create('"add" operation is only supported for series types.'); + end; + Result := TAstValue.Void; +end; + function TEvaluatorVisitor.VisitAssignment(const Node: IAssignmentNode): TAstValue; var varName: string; @@ -237,6 +359,33 @@ begin Result := TAstValue.FromScalar(Node.Value); end; +function TEvaluatorVisitor.VisitCreateSeries(const Node: ICreateSeriesNode): TAstValue; +var + def: string; +begin + // CORRECTED: Node.Definition is now a simple string. + def := Node.Definition.Trim; + + // Based on the content, create a scalar or a record series. + if def.StartsWith('[') then + begin + // Assumed to be a record definition array, e.g., '[{"Name": "Close", "Kind": "skDouble"}]' + var recordDef := TRttiAstHelper.JsonToRecordDefinition(def); + if Length(recordDef.Fields) = 0 then + raise EArgumentException.Create('Failed to parse record definition from JSON array.'); + + var recordSeries := TScalarRecordSeries.Create(recordDef); + Result := TAstValue.FromRecordSeries(recordSeries); + end + else + begin + // Assumed to be a single scalar type name, e.g., 'double' + var scalarKind := StringToScalarKind(def); + var scalarSeries := TScalarSeries.Create(scalarKind, Default(TSeries)); + Result := TAstValue.FromSeries(scalarSeries); + end; +end; + function TEvaluatorVisitor.VisitIdentifier(const Node: IIdentifierNode): TAstValue; var val: TAstValue; @@ -270,9 +419,19 @@ begin // Base type dispatching case baseValue.Kind of + avkSeries: + begin + var series := baseValue.AsSeries.Value; + if (index < 0) or (index >= series.Items.TotalCount) then + raise EArgumentException + .CreateFmt('Index %d is out of bounds for series with %d elements.', [index, series.Items.TotalCount]); + + var scalarValue := series.Items[Integer(index)]; + Result := TAstValue.FromScalar(TScalar.Create(series.Kind, scalarValue)); + end; avkRecordSeries: begin - var series := baseValue.AsRecordSeries; + var series := baseValue.AsRecordSeries.Value; if (index < 0) or (index >= series.TotalCount) then raise EArgumentException.CreateFmt('Index %d is out of bounds for series with %d elements.', [index, series.TotalCount]); @@ -281,7 +440,7 @@ begin end; avkMemberSeries: begin - var memberSeries := baseValue.AsMemberSeries; + var memberSeries := baseValue.AsMemberSeries.Value; if (index < 0) or (index >= memberSeries.Count) then raise EArgumentException .CreateFmt('Index %d is out of bounds for member series with %d elements.', [index, memberSeries.Count]); @@ -303,7 +462,19 @@ begin memberName := Node.Member.Name; case baseValue.Kind of - avkRecordSeries: Result := TAstValue.FromMemberSeries(baseValue.AsRecordSeries.CreateMemberSeries(memberName)); + avkSeries: + begin + var series := baseValue.AsSeries.Value; + if SameText(memberName, 'Count') then + Result := TAstValue.FromScalar(TScalar.FromInt64(series.Items.Count)) + else if SameText(memberName, 'TotalCount') then + Result := TAstValue.FromScalar(TScalar.FromInt64(series.Items.TotalCount)) + else if SameText(memberName, 'Kind') then + Result := TAstValue.FromText(ScalarKindToString(series.Kind)) + else + raise EArgumentException.CreateFmt('Member "%s" not found on TScalarSeries.', [memberName]); + end; + avkRecordSeries: Result := TAstValue.FromMemberSeries(baseValue.AsRecordSeries.Value.CreateMemberSeries(memberName)); avkRecord: Result := TAstValue.FromScalar(baseValue.AsRecord.Items[memberName]); else raise EArgumentException.Create('Member access operator `.` is not supported for this value type.'); @@ -499,7 +670,14 @@ begin if IsTruthy(conditionValue) then Result := Node.ThenBranch.Accept(Self) else - Result := Node.ElseBranch.Accept(Self); + begin + // If an else branch exists, evaluate it. + if Assigned(Node.ElseBranch) then + Result := Node.ElseBranch.Accept(Self) + else + // Otherwise, an if-statement without an else branch evaluates to void. + Result := TAstValue.Void; + end; end; function TEvaluatorVisitor.VisitTernaryExpression(const Node: ITernaryExpressionNode): TAstValue; @@ -526,6 +704,28 @@ begin Result := lastValue; end; +function TEvaluatorVisitor.VisitSeriesLength(const Node: ISeriesLengthNode): TAstValue; +var + seriesValue: TAstValue; + len: Int64; +begin + // 1. Evaluate the identifier to get the series object from the scope. + seriesValue := Node.Series.Accept(Self); + + // 2. Get the length based on the actual series type. + case seriesValue.Kind of + avkSeries: len := seriesValue.AsSeries.Value.Items.Count; + avkRecordSeries: len := seriesValue.AsRecordSeries.Value.Count; + avkMemberSeries: len := seriesValue.AsMemberSeries.Value.Count; + else + // It's an error if we try to get the length of something that isn't a series. + raise EArgumentException.CreateFmt('Cannot get length of type %s.', [GetEnumName(TypeInfo(TAstValueKind), Ord(seriesValue.Kind))]); + end; + + // 3. Return the length as a new scalar value. + Result := TAstValue.FromScalar(TScalar.FromInt64(len)); +end; + { TDebugEvaluatorVisitor } constructor TDebugEvaluatorVisitor.Create(const AScope: IExecutionScope; ALog: TStrings; AShowScope: Boolean; AInitialIndent: Integer = 0); @@ -596,6 +796,18 @@ begin end; end; +function TDebugEvaluatorVisitor.VisitAddSeriesItem(const Node: IAddSeriesItemNode): TAstValue; +begin + AppendLine('AddSeriesItem {'); + Indent; + try + Result := inherited VisitAddSeriesItem(Node); + finally + Unindent; + end; + AppendLine('} -> (void)'); +end; + function TDebugEvaluatorVisitor.VisitAssignment(const Node: IAssignmentNode): TAstValue; begin AppendLine(Format('Assignment %s := {', [Node.Identifier.Name])); @@ -614,6 +826,18 @@ begin Result := inherited VisitConstant(Node); end; +function TDebugEvaluatorVisitor.VisitCreateSeries(const Node: ICreateSeriesNode): TAstValue; +begin + AppendLine('CreateSeries {'); + Indent; + try + Result := inherited VisitCreateSeries(Node); + finally + Unindent; + end; + AppendLine(Format('} -> %s', [Result.ToString])); +end; + function TDebugEvaluatorVisitor.VisitIdentifier(const Node: IIdentifierNode): TAstValue; begin Result := inherited VisitIdentifier(Node); diff --git a/Src/AST/Myc.Ast.Nodes.pas b/Src/AST/Myc.Ast.Nodes.pas index 6f5d7a9..2f6795b 100644 --- a/Src/AST/Myc.Ast.Nodes.pas +++ b/Src/AST/Myc.Ast.Nodes.pas @@ -21,7 +21,8 @@ type end; // Added avkMemberSeries to represent a TScalarMemberSeries value. - TAstValueKind = (avkUndefined, avkScalar, avkClosure, avkText, avkRecordSeries, avkRecord, avkMemberSeries); + // Added avkSeries to represent a TScalarSeries value. + TAstValueKind = (avkUndefined, avkScalar, avkClosure, avkText, avkSeries, avkRecordSeries, avkRecord, avkMemberSeries); // --- Forward Declarations to break cycles --- IExecutionScope = interface; @@ -40,8 +41,10 @@ type IVariableDeclarationNode = interface; IAssignmentNode = interface; IIndexerNode = interface; - // Added forward declaration for the new member access node. IMemberAccessNode = interface; + ICreateSeriesNode = interface; + IAddSeriesItemNode = interface; + ISeriesLengthNode = interface; IEvaluatorClosure = interface; // --- Concrete Type Definitions --- @@ -60,17 +63,8 @@ type TAstValue = record private type - IVal = interface - ['{7D5AD675-A008-4007-B1A4-CF7A05749510}'] // needed, do not remove - function GetValue: T; - property Value: T read GetValue; - end; - - TVal = class(TInterfacedObject, IVal) - private - FValue: T; - function GetValue: T; - public + TVal = class(TInterfacedObject) + Value: T; constructor Create(const AValue: T); end; @@ -86,15 +80,17 @@ type class function FromScalar(const AValue: TScalar): TAstValue; inline; static; class function FromClosure(const AValue: IEvaluatorClosure): TAstValue; inline; static; class function FromText(const AValue: String): TAstValue; inline; static; - class function FromRecordSeries(const AValue: TScalarRecordSeries): TAstValue; inline; static; - class function FromRecord(const AValue: TScalarRecord): TAstValue; inline; static; - class function FromMemberSeries(const AValue: TScalarMemberSeries): TAstValue; inline; static; + class function FromSeries(const [ref] AValue: TScalarSeries): TAstValue; static; inline; + class function FromRecordSeries(const [ref] AValue: TScalarRecordSeries): TAstValue; static; inline; + class function FromRecord(const [ref] AValue: TScalarRecord): TAstValue; static; inline; + class function FromMemberSeries(const [ref] AValue: TScalarMemberSeries): TAstValue; static; inline; function AsScalar: TScalar; inline; function AsClosure: IEvaluatorClosure; inline; function AsText: String; inline; - function AsRecordSeries: TScalarRecordSeries; inline; + function AsRecordSeries: TVal; inline; function AsRecord: TScalarRecord; inline; - function AsMemberSeries: TScalarMemberSeries; inline; + function AsMemberSeries: TVal; inline; + function AsSeries: TVal; inline; function ToString: String; property IsVoid: Boolean read GetIsVoid; property Kind: TAstValueKind read GetKind; @@ -126,6 +122,9 @@ type function VisitAssignment(const Node: IAssignmentNode): TAstValue; function VisitIndexer(const Node: IIndexerNode): TAstValue; function VisitMemberAccess(const Node: IMemberAccessNode): TAstValue; + function VisitCreateSeries(const Node: ICreateSeriesNode): TAstValue; + function VisitAddSeriesItem(const Node: IAddSeriesItemNode): TAstValue; + function VisitSeriesLength(const Node: ISeriesLengthNode): TAstValue; end; IAstNode = interface(IInterface) @@ -256,6 +255,33 @@ type property Member: IIdentifierNode read GetMember; end; + // Represents creating a new, empty series (e.g., new series(int)). + ICreateSeriesNode = interface(IExpressionNode) + {$region 'private'} + function GetDefinition: String; + {$endregion} + property Definition: String read GetDefinition; + end; + + // Represents adding a value to a series (e.g., my_series.add(value, 100)). + IAddSeriesItemNode = interface(IExpressionNode) + {$region 'private'} + function GetSeries: IIdentifierNode; + function GetValue: IExpressionNode; + function GetLookback: IExpressionNode; + {$endregion} + property Series: IIdentifierNode read GetSeries; + property Value: IExpressionNode read GetValue; + property Lookback: IExpressionNode read GetLookback; + end; + + ISeriesLengthNode = interface(IExpressionNode) + {$region 'private'} + function GetSeries: IIdentifierNode; + {$endregion} + property Series: IIdentifierNode read GetSeries; + end; + implementation uses @@ -266,12 +292,7 @@ uses constructor TAstValue.TVal.Create(const AValue: T); begin inherited Create; - FValue := AValue; -end; - -function TAstValue.TVal.GetValue: T; -begin - Result := FValue; + Value := AValue; end; { TAstValue } @@ -289,25 +310,25 @@ begin Result := IEvaluatorClosure(FInterface); end; -function TAstValue.AsMemberSeries: TScalarMemberSeries; +function TAstValue.AsMemberSeries: TVal; begin if (FKind <> avkMemberSeries) then raise EInvalidCast.Create('Cannot read value as MemberSeries.'); - Result := (FInterface as IVal).Value; + Result := TVal(FInterface); end; function TAstValue.AsRecord: TScalarRecord; begin if (FKind <> avkRecord) then raise EInvalidCast.Create('Cannot read value as Record.'); - Result := (FInterface as IVal).Value; + Result := (FInterface as TVal).Value; end; -function TAstValue.AsRecordSeries: TScalarRecordSeries; +function TAstValue.AsRecordSeries: TVal; begin if (FKind <> avkRecordSeries) then raise EInvalidCast.Create('Cannot read value as RecordSeries.'); - Result := (FInterface as IVal).Value; + Result := TVal(FInterface); end; function TAstValue.AsScalar: TScalar; @@ -317,12 +338,20 @@ begin Result := FScalar; end; +// Added support for TScalarSeries +function TAstValue.AsSeries: TVal; +begin + if (FKind <> avkSeries) then + raise EInvalidCast.Create('Cannot read value as Series.'); + Result := TVal(FInterface); +end; + function TAstValue.AsText: String; begin if (FKind <> avkText) then raise EInvalidCast.Create('Cannot read value as Text.'); - Result := (FInterface as IVal).Value; + Result := (FInterface as TVal).Value; end; class function TAstValue.FromClosure(const AValue: IEvaluatorClosure): TAstValue; @@ -332,21 +361,21 @@ begin Result.FScalar := Default(TScalar); end; -class function TAstValue.FromMemberSeries(const AValue: TScalarMemberSeries): TAstValue; +class function TAstValue.FromMemberSeries(const [ref] AValue: TScalarMemberSeries): TAstValue; begin Result.FKind := avkMemberSeries; Result.FInterface := TVal.Create(AValue); Result.FScalar := Default(TScalar); end; -class function TAstValue.FromRecord(const AValue: TScalarRecord): TAstValue; +class function TAstValue.FromRecord(const [ref] AValue: TScalarRecord): TAstValue; begin Result.FKind := avkRecord; Result.FInterface := TVal.Create(AValue); Result.FScalar := Default(TScalar); end; -class function TAstValue.FromRecordSeries(const AValue: TScalarRecordSeries): TAstValue; +class function TAstValue.FromRecordSeries(const [ref] AValue: TScalarRecordSeries): TAstValue; begin Result.FKind := avkRecordSeries; Result.FInterface := TVal.Create(AValue); @@ -360,6 +389,13 @@ begin Result.FInterface := nil; end; +class function TAstValue.FromSeries(const [ref] AValue: TScalarSeries): TAstValue; +begin + Result.FKind := avkSeries; + Result.FInterface := TVal.Create(AValue); + Result.FScalar := Default(TScalar); +end; + class function TAstValue.FromText(const AValue: String): TAstValue; begin Result.FKind := avkText; @@ -383,6 +419,7 @@ begin avkScalar: Result := FScalar.ToString; avkClosure: Result := ''; avkText: Result := AsText; + avkSeries: Result := ''; avkRecordSeries: Result := ''; avkRecord: Result := ''; avkMemberSeries: Result := ''; diff --git a/Src/AST/Myc.Ast.Printer.pas b/Src/AST/Myc.Ast.Printer.pas index af5c112..9d52a3e 100644 --- a/Src/AST/Myc.Ast.Printer.pas +++ b/Src/AST/Myc.Ast.Printer.pas @@ -35,6 +35,9 @@ type function VisitAssignment(const Node: IAssignmentNode): TAstValue; function VisitIndexer(const Node: IIndexerNode): TAstValue; function VisitMemberAccess(const Node: IMemberAccessNode): TAstValue; + function VisitCreateSeries(const Node: ICreateSeriesNode): TAstValue; + function VisitAddSeriesItem(const Node: IAddSeriesItemNode): TAstValue; + function VisitSeriesLength(const Node: ISeriesLengthNode): TAstValue; end; implementation @@ -123,10 +126,13 @@ begin Indent; Node.ThenBranch.Accept(Self); Unindent; - AppendLine('Else:'); - Indent; - Node.ElseBranch.Accept(Self); - Unindent; + if Node.ElseBranch <> nil then + begin + AppendLine('Else:'); + Indent; + Node.ElseBranch.Accept(Self); + Unindent; + end; Unindent; Result := TAstValue.Void; end; @@ -255,4 +261,45 @@ begin Result := TAstValue.Void; end; +function TPrettyPrintVisitor.VisitCreateSeries(const Node: ICreateSeriesNode): TAstValue; +begin + AppendLine('CreateSeries'); + Indent; + AppendLine('Definition: ' + Node.Definition); + Unindent; + Result := TAstValue.Void; +end; + +function TPrettyPrintVisitor.VisitAddSeriesItem(const Node: IAddSeriesItemNode): TAstValue; +begin + AppendLine('AddSeriesItem'); + Indent; + AppendLine('Series:'); + Indent; + Node.Series.Accept(Self); + Unindent; + AppendLine('Value:'); + Indent; + Node.Value.Accept(Self); + Unindent; + if Assigned(Node.Lookback) then + begin + AppendLine('Lookback:'); + Indent; + Node.Lookback.Accept(Self); + Unindent; + end; + Unindent; + Result := TAstValue.Void; +end; + +function TPrettyPrintVisitor.VisitSeriesLength(const Node: ISeriesLengthNode): TAstValue; +var + seriesStr: string; +begin + // Get the string representation of the series identifier by visiting the node. + seriesStr := Node.Series.Accept(Self).ToString; + Result := TAstValue.FromText(Format('length(%s)', [seriesStr])); +end; + end. diff --git a/Src/AST/Myc.Ast.pas b/Src/AST/Myc.Ast.pas index 4ad7901..1e042ad 100644 --- a/Src/AST/Myc.Ast.pas +++ b/Src/AST/Myc.Ast.pas @@ -34,6 +34,14 @@ type class function AssignResult(const AValue: IExpressionNode): IAssignmentNode; static; class function Indexer(const ABase: IExpressionNode; const AIndex: IExpressionNode): IIndexerNode; static; class function MemberAccess(const ABase: IExpressionNode; const AMember: IIdentifierNode): IMemberAccessNode; static; + class function CreateSeries(const ADefinition: String): ICreateSeriesNode; static; + class function AddSeriesItem( + const ASeries: IIdentifierNode; + const AValue: IExpressionNode; + const ALookback: IExpressionNode = nil + ): IAddSeriesItemNode; static; + // Added the new factory function for SeriesLength + class function SeriesLength(const ASeries: IIdentifierNode): ISeriesLengthNode; static; end; implementation @@ -209,6 +217,41 @@ type function Accept(const Visitor: IAstVisitor): TAstValue; override; end; + { TCreateSeriesNode } + TCreateSeriesNode = class(TAstNode, ICreateSeriesNode) + private + FDefinition: String; + function GetDefinition: String; + public + constructor Create(const ADefinition: String); + function Accept(const Visitor: IAstVisitor): TAstValue; override; + end; + + { TAddSeriesItemNode } + TAddSeriesItemNode = class(TAstNode, IAddSeriesItemNode) + private + FSeries: IIdentifierNode; + FValue: IExpressionNode; + FLookback: IExpressionNode; + function GetSeries: IIdentifierNode; + function GetValue: IExpressionNode; + function GetLookback: IExpressionNode; + public + constructor Create(const ASeries: IIdentifierNode; const AValue: IExpressionNode; const ALookback: IExpressionNode); + function Accept(const Visitor: IAstVisitor): TAstValue; override; + end; + + // Added concrete class for the series length node. + { TSeriesLengthNode } + TSeriesLengthNode = class(TAstNode, ISeriesLengthNode) + private + FSeries: IIdentifierNode; + function GetSeries: IIdentifierNode; + public + constructor Create(const ASeries: IIdentifierNode); + function Accept(const Visitor: IAstVisitor): TAstValue; override; + end; + { TConstantNode } constructor TConstantNode.Create(AValue: TScalar); @@ -541,8 +584,83 @@ begin Result := FMember; end; +{ TCreateSeriesNode } + +constructor TCreateSeriesNode.Create(const ADefinition: String); +begin + inherited Create; + FDefinition := ADefinition; +end; + +function TCreateSeriesNode.Accept(const Visitor: IAstVisitor): TAstValue; +begin + Result := Visitor.VisitCreateSeries(Self); +end; + +function TCreateSeriesNode.GetDefinition: String; +begin + Result := FDefinition; +end; + +{ TAddSeriesItemNode } + +constructor TAddSeriesItemNode.Create(const ASeries: IIdentifierNode; const AValue, ALookback: IExpressionNode); +begin + inherited Create; + FSeries := ASeries; + FValue := AValue; + FLookback := ALookback; +end; + +function TAddSeriesItemNode.Accept(const Visitor: IAstVisitor): TAstValue; +begin + Result := Visitor.VisitAddSeriesItem(Self); +end; + +function TAddSeriesItemNode.GetLookback: IExpressionNode; +begin + Result := FLookback; +end; + +function TAddSeriesItemNode.GetSeries: IIdentifierNode; +begin + Result := FSeries; +end; + +function TAddSeriesItemNode.GetValue: IExpressionNode; +begin + Result := FValue; +end; + +{ TSeriesLengthNode } + +constructor TSeriesLengthNode.Create(const ASeries: IIdentifierNode); +begin + inherited Create; + FSeries := ASeries; +end; + +function TSeriesLengthNode.Accept(const Visitor: IAstVisitor): TAstValue; +begin + Result := Visitor.VisitSeriesLength(Self); +end; + +function TSeriesLengthNode.GetSeries: IIdentifierNode; +begin + Result := FSeries; +end; + { TAst } +class function TAst.AddSeriesItem( + const ASeries: IIdentifierNode; + const AValue: IExpressionNode; + const ALookback: IExpressionNode +): IAddSeriesItemNode; +begin + Result := TAddSeriesItemNode.Create(ASeries, AValue, ALookback); +end; + class function TAst.Assign(const AIdentifier: IIdentifierNode; const AValue: IExpressionNode): IAssignmentNode; begin Result := TAssignmentNode.Create(AIdentifier, AValue); @@ -563,6 +681,11 @@ begin Result := TConstantNode.Create(AValue); end; +class function TAst.CreateSeries(const ADefinition: String): ICreateSeriesNode; +begin + Result := TCreateSeriesNode.Create(ADefinition); +end; + class function TAst.BinaryExpr(ALeft: IExpressionNode; AOperator: TBinaryOperator; ARight: IExpressionNode): IBinaryExpressionNode; begin Result := TBinaryExpressionNode.Create(ALeft, AOperator, ARight); @@ -593,6 +716,11 @@ begin Result := TMemberAccessNode.Create(ABase, AMember); end; +class function TAst.SeriesLength(const ASeries: IIdentifierNode): ISeriesLengthNode; +begin + Result := TSeriesLengthNode.Create(ASeries); +end; + class function TAst.TernaryExpr(const ACondition: IExpressionNode; const AThenBranch, AElseBranch: IExpressionNode): ITernaryExpressionNode; begin Result := TTernaryExpressionNode.Create(ACondition, AThenBranch, AElseBranch); diff --git a/Src/Data/Myc.Data.Scalar.pas b/Src/Data/Myc.Data.Scalar.pas index 4c5b5a5..e5b6dcb 100644 --- a/Src/Data/Myc.Data.Scalar.pas +++ b/Src/Data/Myc.Data.Scalar.pas @@ -144,19 +144,15 @@ type // A time series of scalar values of the same kind. TScalarSeries = record + private + FKind: TScalarKind; + FItems: TSeries; public // Creates a new scalar series. constructor Create(AKind: TScalarKind; const AItems: TSeries); - function GetKind: TScalarKind; inline; - function GetItems: TSeries; inline; - - property Kind: TScalarKind read GetKind; - property Items: TSeries read GetItems; - - strict private - FKind: TScalarKind; - FItems: TSeries; + property Kind: TScalarKind read FKind; + property Items: TSeries read FItems; end; TScalarMemberSeries = record @@ -462,16 +458,6 @@ begin FItems := AItems; end; -function TScalarSeries.GetKind: TScalarKind; -begin - Result := FKind; -end; - -function TScalarSeries.GetItems: TSeries; -begin - Result := FItems; -end; - { TScalarTupleSeries } constructor TScalarTupleSeries.Create(const ADef: TArray);