From eb7902d9e849a919dbb75c88cc2f8fff1227b451 Mon Sep 17 00:00:00 2001 From: Michael Schimmel Date: Tue, 2 Sep 2025 17:49:30 +0200 Subject: [PATCH] Ast development --- ASTPlayground/MainForm.pas | 73 ++++++++++++++-------------- ASTPlayground/Myc.Ast.Visualizer.pas | 47 ++++++++++++++++++ Src/AST/Myc.Ast.Evaluator.pas | 73 +++++++++++++++++++++++----- Src/AST/Myc.Ast.Nodes.pas | 36 ++++++++++++-- Src/AST/Myc.Ast.Printer.pas | 13 +++++ Src/AST/Myc.Ast.pas | 49 +++++++++++++++++-- Src/Data/Myc.Data.POD.pas | 49 +++++++++---------- 7 files changed, 257 insertions(+), 83 deletions(-) diff --git a/ASTPlayground/MainForm.pas b/ASTPlayground/MainForm.pas index 6033b0a..a37ebdf 100644 --- a/ASTPlayground/MainForm.pas +++ b/ASTPlayground/MainForm.pas @@ -399,9 +399,8 @@ var series: TScalarRecordSeries; recordValue: TScalarRecord; visitor: IAstVisitor; - ast: IAstNode; + ast: IExpressionNode; resultValue: TAstValue; - resultRecord: TScalarRecord; i: Integer; begin Memo1.Lines.Clear; @@ -409,17 +408,13 @@ begin // --- 1. Arrange (in Delphi) --- Memo1.Lines.Add('1. Arranging test data in Delphi...'); - - // Clear and prepare the global scope for the script. FGScope.Clear; RegisterNativeFunctions(FGScope); - // Generate JSON definition from our test record type. jsonDef := TRttiAstHelper.RecordDefinitionToJson(TypeInfo(TOHLCV)); FGScope.SetValue('ohlcvDef', TAstValue.FromText(jsonDef)); Memo1.Lines.Add(' - Generated and injected JSON definition for TOHLCV.'); - // Create a TScalarRecordSeries and populate it with some data. recordDef := TRttiAstHelper.JsonToRecordDefinition(jsonDef); series := TScalarRecordSeries.Create(recordDef); for i := 0 to 4 do @@ -439,7 +434,6 @@ begin series.Add(recordValue); end; - // Inject the pre-populated series into the script's scope. FGScope.SetValue('ohlcvSeries', TAstValue.FromRecordSeries(series)); Memo1.Lines.Add(Format(' - Created and injected a series with %d records.', [series.TotalCount])); Memo1.Lines.Add(''); @@ -447,24 +441,32 @@ begin // --- 2. Act (in Script) --- Memo1.Lines.Add('2. Executing script...'); - // Create an AST that: - // a) Creates a new, empty series to test the native function. - // b) Accesses the 3rd element (index 2) of the pre-populated series. - // c) Returns the accessed record as the final result. + // This script now tests member access and indexing the resulting member series. + // let closeColumn = ohlcvSeries.Close; + // let secondClose = closeColumn[1]; + // secondClose ast := - TAst.Block( - [ - TAst.VarDecl( - TAst.Identifier('newSeries'), - TAst.FunctionCall(TAst.Identifier('CreateRecordSeries'), [TAst.Identifier('ohlcvDef')]) - ), - TAst.Indexer(TAst.Identifier('ohlcvSeries'), TAst.Constant(TScalar.FromInt64(2))) - ] + TAst.LambdaExpr( + [], + TAst.Block( + [ + TAst.VarDecl( + TAst.Identifier('closeColumn'), + TAst.MemberAccess(TAst.Identifier('ohlcvSeries'), TAst.Identifier('Close')) + ), + TAst.VarDecl( + TAst.Identifier('secondClose'), + TAst.Indexer(TAst.Identifier('closeColumn'), TAst.Constant(TScalar.FromInt64(1))) + ), + TAst.AssignResult(TAst.Identifier('secondClose')) + ] + ) ); FLastAst := ast; visitor := TEvaluatorVisitor.Create(FGScope); - resultValue := ast.Accept(visitor); + resultValue := TAst.FunctionCall(ast, []).Accept(visitor); + Memo1.Lines.Add(' - Script finished.'); Memo1.Lines.Add(Format(' - Script returned value of type: %s', [GetEnumName(TypeInfo(TAstValueKind), Ord(resultValue.Kind))])); Memo1.Lines.Add(''); @@ -472,34 +474,31 @@ begin // --- 3. Assert (in Delphi) --- Memo1.Lines.Add('3. Asserting results in Delphi...'); - if (resultValue.Kind <> avkRecord) then + if (resultValue.Kind <> avkScalar) then begin - Memo1.Lines.Add('TEST FAILED: Script did not return a record.'); + Memo1.Lines.Add('TEST FAILED: Script did not return a scalar value.'); exit; end; + Memo1.Lines.Add(' - Result is a scalar, as expected.'); - resultRecord := resultValue.AsRecord; - Memo1.Lines.Add(' - Result is a record, as expected.'); + var resultScalar := resultValue.AsScalar; + if (resultScalar.Kind <> skDouble) then + begin + Memo1.Lines.Add('TEST FAILED: Returned scalar is not a Double.'); + exit; + end; + Memo1.Lines.Add(' - Returned scalar is a Double, as expected.'); - // Verify the 'Close' price of the 3rd record (index 2) - var closeValue := resultRecord.Items['Close'].Value.AsDouble; - var expectedClose := 102.0 + 2; // from the data generation loop for i=2 + var closeValue := resultScalar.Value.AsDouble; + // The script asks for index [1], which is the second-to-last element. + // The data was generated for i in 0..4, so the second-to-last is i=3. + var expectedClose := 102.0 + 3; if (abs(closeValue - expectedClose) > 0.001) then begin Memo1.Lines.Add(Format('TEST FAILED: Expected Close price %f, but got %f.', [expectedClose, closeValue])); exit; end; Memo1.Lines.Add(Format(' - Verified Close price: %f.', [closeValue])); - - // Verify the 'Volume' of the 3rd record (index 2) - var volumeValue := resultRecord.Items['Volume'].Value.AsInt64; - var expectedVolume := 10000 * (2 + 1); // for i=2 - if (volumeValue <> expectedVolume) then - begin - Memo1.Lines.Add(Format('TEST FAILED: Expected Volume %d, but got %d.', [expectedVolume, volumeValue])); - exit; - end; - Memo1.Lines.Add(Format(' - Verified Volume: %d.', [volumeValue])); Memo1.Lines.Add(''); Memo1.Lines.Add('--- TEST PASSED ---'); end; diff --git a/ASTPlayground/Myc.Ast.Visualizer.pas b/ASTPlayground/Myc.Ast.Visualizer.pas index 3b0e423..6520d9e 100644 --- a/ASTPlayground/Myc.Ast.Visualizer.pas +++ b/ASTPlayground/Myc.Ast.Visualizer.pas @@ -128,6 +128,7 @@ type function VisitVariableDeclaration(const Node: IVariableDeclarationNode): TAstValue; function VisitAssignment(const Node: IAssignmentNode): TAstValue; function VisitIndexer(const Node: IIndexerNode): TAstValue; + function VisitMemberAccess(const Node: IMemberAccessNode): TAstValue; end; TAuraWorkspace = class(TStyledControl) @@ -200,6 +201,7 @@ type function VisitVariableDeclaration(const Node: IVariableDeclarationNode): TAstValue; function VisitAssignment(const Node: IAssignmentNode): TAstValue; function VisitIndexer(const Node: IIndexerNode): TAstValue; + function VisitMemberAccess(const Node: IMemberAccessNode): TAstValue; end; constructor TAstToTextVisitor.Create; @@ -300,6 +302,14 @@ begin end; end; +function TAstToTextVisitor.VisitMemberAccess(const Node: IMemberAccessNode): TAstValue; +var + baseStr: string; +begin + baseStr := Node.Base.Accept(Self).AsText; + Result := TAstValue.FromText(Format('%s.%s', [baseStr, Node.Member.Name])); +end; + function TAstToTextVisitor.VisitTernaryExpression(const Node: ITernaryExpressionNode): TAstValue; begin var condStr := Node.Condition.Accept(Self).AsText; @@ -1032,6 +1042,43 @@ begin Result := TAstValue.Void; end; +function TAstToAuraNodeVisitor.VisitMemberAccess(const Node: IMemberAccessNode): TAstValue; +var + details: String; +begin + // Implements the visualization for member access (e.g., series.field). + if (FMode = vmControlFlow) and TryGetDescr(Node, details) then + begin + var memberNode := CreateNodeControl('Expression', details); + FLastResult.OutputPin := CreateOutput(memberNode); + FinalizeNodeLayout(memberNode); + end + else + begin + VisitOperatorNode( + [Node.Base], + function(const InputResults: TAuraNodeResultList): TAuraNodeResult + var + memberAccessNode: TAuraNode; + basePin, outPin: TControl; + begin + memberAccessNode := BuildNodeControl('Get ' + Node.Member.Name, ''); + + basePin := CreateInput(memberAccessNode, 'Base'); + outPin := CreateOutput(memberAccessNode); + + if Assigned(InputResults[0].OutputPin) then + FConnections.Add(TPinConnection.Create(InputResults[0].OutputPin, basePin)); + + FinalizeNodeLayout(memberAccessNode); + Result.LayoutNode := memberAccessNode; + Result.OutputPin := outPin; + end + ); + end; + Result := TAstValue.Void; +end; + function TAstToAuraNodeVisitor.VisitOperatorNode( const InputExpressions: TArray; const CreateParentProc: TCreateParentNodeProc; diff --git a/Src/AST/Myc.Ast.Evaluator.pas b/Src/AST/Myc.Ast.Evaluator.pas index bf1f9be..6005843 100644 --- a/Src/AST/Myc.Ast.Evaluator.pas +++ b/Src/AST/Myc.Ast.Evaluator.pas @@ -32,6 +32,7 @@ type function VisitVariableDeclaration(const Node: IVariableDeclarationNode): TAstValue; virtual; function VisitAssignment(const Node: IAssignmentNode): TAstValue; virtual; function VisitIndexer(const Node: IIndexerNode): TAstValue; virtual; + function VisitMemberAccess(const Node: IMemberAccessNode): TAstValue; virtual; end; // TDebugEvaluatorVisitor now overrides all visit methods for full tracing @@ -61,6 +62,7 @@ type function VisitVariableDeclaration(const Node: IVariableDeclarationNode): TAstValue; override; function VisitAssignment(const Node: IAssignmentNode): TAstValue; override; function VisitIndexer(const Node: IIndexerNode): TAstValue; override; + function VisitMemberAccess(const Node: IMemberAccessNode): TAstValue; override; end; // Registers all native core functions in the given scope. @@ -208,7 +210,7 @@ begin skInteger: Result := (AValue.AsScalar.Value.AsInteger <> 0); skInt64: Result := (AValue.AsScalar.Value.AsInt64 <> 0); skUInt64: Result := (AValue.AsScalar.Value.AsUInt64 <> 0); - skBoolean: Result := AValue.AsScalar.Value.AsBoolean; + skBoolean: Result := (AValue.AsScalar.Value.AsBoolean); else Result := False; end; @@ -248,17 +250,13 @@ end; function TEvaluatorVisitor.VisitIndexer(const Node: IIndexerNode): TAstValue; var baseValue, indexValue: TAstValue; - series: TScalarRecordSeries; - rec: TScalarRecord; index: Int64; indexScalar: TScalar; begin baseValue := Node.Base.Accept(Self); indexValue := Node.Index.Accept(Self); - if (baseValue.Kind <> avkRecordSeries) then - raise EArgumentException.Create('Indexer `[]` can only be applied to a RecordSeries.'); - + // Index validation (common for all indexable types) if (indexValue.Kind <> avkScalar) then raise EArgumentException.Create('Indexer `[]` requires a scalar integer argument.'); @@ -270,13 +268,54 @@ begin raise EArgumentException.Create('Indexer `[]` requires an integer type argument.'); end; - series := baseValue.AsRecordSeries; + // Base type dispatching + case baseValue.Kind of + avkRecordSeries: + begin + var series := baseValue.AsRecordSeries; + 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]); - 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]); + var recordValue := series.Items[Integer(index)]; + Result := TAstValue.FromRecord(recordValue); + end; + avkMemberSeries: + begin + var memberSeries := baseValue.AsMemberSeries; + 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]); - rec := series.Items[Integer(index)]; - Result := TAstValue.FromRecord(rec); + var scalarValue := memberSeries.Items[Integer(index)]; + Result := TAstValue.FromScalar(scalarValue); + end; + else + raise EArgumentException.Create('Indexer `[]` is not supported for this value type.'); + end; +end; + +function TEvaluatorVisitor.VisitMemberAccess(const Node: IMemberAccessNode): TAstValue; +var + baseValue: TAstValue; + memberName: string; +begin + baseValue := Node.Base.Accept(Self); + memberName := Node.Member.Name; + + case baseValue.Kind of + avkRecordSeries: + begin + var series := baseValue.AsRecordSeries; + Result := TAstValue.FromMemberSeries(series.CreateMemberSeries(memberName)); + end; + avkRecord: + begin + var recordValue := baseValue.AsRecord; + Result := TAstValue.FromScalar(recordValue.Items[memberName]); + end; + else + raise EArgumentException.Create('Member access operator `.` is not supported for this value type.'); + end; end; function TEvaluatorVisitor.VisitVariableDeclaration(const Node: IVariableDeclarationNode): TAstValue; @@ -607,6 +646,18 @@ begin AppendLine(Format('} -> %s', [Result.ToString])); end; +function TDebugEvaluatorVisitor.VisitMemberAccess(const Node: IMemberAccessNode): TAstValue; +begin + AppendLine(Format('MemberAccess (Member: %s) {', [Node.Member.Name])); + Indent; + try + Result := inherited VisitMemberAccess(Node); + finally + Unindent; + end; + AppendLine(Format('} -> %s', [Result.ToString])); +end; + function TDebugEvaluatorVisitor.VisitTernaryExpression(const Node: ITernaryExpressionNode): TAstValue; begin AppendLine('TernaryExpr{'); diff --git a/Src/AST/Myc.Ast.Nodes.pas b/Src/AST/Myc.Ast.Nodes.pas index f76af6b..93340e1 100644 --- a/Src/AST/Myc.Ast.Nodes.pas +++ b/Src/AST/Myc.Ast.Nodes.pas @@ -20,8 +20,8 @@ type function ToString: string; end; - // Added avkRecord to represent a TScalarRecord value. - TAstValueKind = (avkUndefined, avkScalar, avkClosure, avkText, avkRecordSeries, avkRecord); + // Added avkMemberSeries to represent a TScalarMemberSeries value. + TAstValueKind = (avkUndefined, avkScalar, avkClosure, avkText, avkRecordSeries, avkRecord, avkMemberSeries); // --- Forward Declarations to break cycles --- IExecutionScope = interface; @@ -33,15 +33,15 @@ type IBinaryExpressionNode = interface; IUnaryExpressionNode = interface; IIfExpressionNode = interface; - // Added forward declaration for the new ternary expression node. ITernaryExpressionNode = interface; ILambdaExpressionNode = interface; IFunctionCallNode = interface; IBlockExpressionNode = interface; IVariableDeclarationNode = interface; IAssignmentNode = interface; - // Added forward declaration for the new indexer node. IIndexerNode = interface; + // Added forward declaration for the new member access node. + IMemberAccessNode = interface; IEvaluatorClosure = interface; // --- Concrete Type Definitions --- @@ -88,11 +88,13 @@ type class function FromText(const AValue: String): TAstValue; static; class function FromRecordSeries(const AValue: TScalarRecordSeries): TAstValue; static; class function FromRecord(const AValue: TScalarRecord): TAstValue; static; + class function FromMemberSeries(const AValue: TScalarMemberSeries): TAstValue; static; function AsScalar: TScalar; function AsClosure: IEvaluatorClosure; function AsText: String; function AsRecordSeries: TScalarRecordSeries; function AsRecord: TScalarRecord; + function AsMemberSeries: TScalarMemberSeries; function ToString: String; property IsVoid: Boolean read GetIsVoid; property Kind: TAstValueKind read GetKind; @@ -123,6 +125,7 @@ type function VisitVariableDeclaration(const Node: IVariableDeclarationNode): TAstValue; function VisitAssignment(const Node: IAssignmentNode): TAstValue; function VisitIndexer(const Node: IIndexerNode): TAstValue; + function VisitMemberAccess(const Node: IMemberAccessNode): TAstValue; end; IAstNode = interface(IInterface) @@ -243,6 +246,16 @@ type property Index: IExpressionNode read GetIndex; end; + // Represents a member access expression (e.g., series.field or record.field). + IMemberAccessNode = interface(IExpressionNode) + {$region 'private'} + function GetBase: IExpressionNode; + function GetMember: IIdentifierNode; + {$endregion} + property Base: IExpressionNode read GetBase; + property Member: IIdentifierNode read GetMember; + end; + implementation uses @@ -276,6 +289,13 @@ begin Result := IEvaluatorClosure(FInterface); end; +function TAstValue.AsMemberSeries: TScalarMemberSeries; +begin + if (FKind <> avkMemberSeries) then + raise EInvalidCast.Create('Cannot read value as MemberSeries.'); + Result := (FInterface as IVal).Value; +end; + function TAstValue.AsRecord: TScalarRecord; begin if (FKind <> avkRecord) then @@ -312,6 +332,13 @@ begin Result.FScalar := Default(TScalar); end; +class function TAstValue.FromMemberSeries(const 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; begin Result.FKind := avkRecord; @@ -358,6 +385,7 @@ begin avkText: Result := AsText; avkRecordSeries: Result := ''; avkRecord: Result := ''; + avkMemberSeries: Result := ''; avkUndefined: Result := ''; else Result := '[Unknown AstValue]'; diff --git a/Src/AST/Myc.Ast.Printer.pas b/Src/AST/Myc.Ast.Printer.pas index 1f9adb9..be8b2da 100644 --- a/Src/AST/Myc.Ast.Printer.pas +++ b/Src/AST/Myc.Ast.Printer.pas @@ -34,6 +34,7 @@ type function VisitVariableDeclaration(const Node: IVariableDeclarationNode): TAstValue; function VisitAssignment(const Node: IAssignmentNode): TAstValue; function VisitIndexer(const Node: IIndexerNode): TAstValue; + function VisitMemberAccess(const Node: IMemberAccessNode): TAstValue; end; implementation @@ -242,4 +243,16 @@ begin Result := TAstValue.Void; end; +function TPrettyPrintVisitor.VisitMemberAccess(const Node: IMemberAccessNode): TAstValue; +begin + AppendLine(Format('MemberAccess (Member: %s)', [Node.Member.Name])); + Indent; + AppendLine('Base:'); + Indent; + Node.Base.Accept(Self); + Unindent; + Unindent; + Result := TAstValue.Void; +end; + end. diff --git a/Src/AST/Myc.Ast.pas b/Src/AST/Myc.Ast.pas index e2e3571..de3f015 100644 --- a/Src/AST/Myc.Ast.pas +++ b/Src/AST/Myc.Ast.pas @@ -22,7 +22,6 @@ type const ACondition: IExpressionNode; const AThenBranch, AElseBranch: IExpressionNode ): IIfExpressionNode; static; - // Added factory function for the new ternary expression node. class function TernaryExpr( const ACondition: IExpressionNode; const AThenBranch, AElseBranch: IExpressionNode @@ -33,8 +32,9 @@ type class function VarDecl(const AIdentifier: IIdentifierNode; AInitializer: IExpressionNode): IVariableDeclarationNode; static; class function Assign(const AIdentifier: IIdentifierNode; const AValue: IExpressionNode): IAssignmentNode; static; class function AssignResult(const AValue: IExpressionNode): IAssignmentNode; static; - // Added factory for the new indexer node. class function Indexer(const ABase: IExpressionNode; const AIndex: IExpressionNode): IIndexerNode; static; + // Added factory for the new member access node. + class function MemberAccess(const ABase: IExpressionNode; const AMember: IIdentifierNode): IMemberAccessNode; static; end; implementation @@ -186,7 +186,6 @@ type end; { TIndexerNode } - // Concrete implementation for the indexer node. TIndexerNode = class(TAstNode, IIndexerNode) private FBase: IExpressionNode; @@ -198,6 +197,19 @@ type function Accept(const Visitor: IAstVisitor): TAstValue; override; end; + { TMemberAccessNode } + // Concrete implementation for the member access node. + TMemberAccessNode = class(TAstNode, IMemberAccessNode) + private + FBase: IExpressionNode; + FMember: IIdentifierNode; + function GetBase: IExpressionNode; + function GetMember: IIdentifierNode; + public + constructor Create(const ABase: IExpressionNode; const AMember: IIdentifierNode); + function Accept(const Visitor: IAstVisitor): TAstValue; override; + end; + { TConstantNode } constructor TConstantNode.Create(AValue: TScalar); @@ -506,6 +518,30 @@ begin Result := FIndex; end; +{ TMemberAccessNode } + +constructor TMemberAccessNode.Create(const ABase: IExpressionNode; const AMember: IIdentifierNode); +begin + inherited Create; + FBase := ABase; + FMember := AMember; +end; + +function TMemberAccessNode.Accept(const Visitor: IAstVisitor): TAstValue; +begin + Result := Visitor.VisitMemberAccess(Self); +end; + +function TMemberAccessNode.GetBase: IExpressionNode; +begin + Result := FBase; +end; + +function TMemberAccessNode.GetMember: IIdentifierNode; +begin + Result := FMember; +end; + { TAst } class function TAst.Assign(const AIdentifier: IIdentifierNode; const AValue: IExpressionNode): IAssignmentNode; @@ -553,7 +589,12 @@ begin Result := TIndexerNode.Create(ABase, AIndex); end; -class function TAst.TernaryExpr(const ACondition, AThenBranch, AElseBranch: IExpressionNode): ITernaryExpressionNode; +class function TAst.MemberAccess(const ABase: IExpressionNode; const AMember: IIdentifierNode): IMemberAccessNode; +begin + Result := TMemberAccessNode.Create(ABase, AMember); +end; + +class function TAst.TernaryExpr(const ACondition: IExpressionNode; const AThenBranch, AElseBranch: IExpressionNode): ITernaryExpressionNode; begin Result := TTernaryExpressionNode.Create(ACondition, AThenBranch, AElseBranch); end; diff --git a/Src/Data/Myc.Data.POD.pas b/Src/Data/Myc.Data.POD.pas index 470665a..3db82e0 100644 --- a/Src/Data/Myc.Data.POD.pas +++ b/Src/Data/Myc.Data.POD.pas @@ -160,17 +160,21 @@ type end; TScalarMemberSeries = record + strict private + FKind: TScalarKind; private FDef: TScalarRecordDefinition; FArray: TChunkArray; FElement: Integer; function GetCount: Int64; inline; - function GetItems(Idx: Integer): TScalarValue; + function GetItems(Idx: Integer): TScalar; public constructor Create(const ADef: TScalarRecordDefinition; const AArray: TChunkArray; AElement: Integer); + function GetKind: TScalarKind; inline; property Count: Int64 read GetCount; property Element: Integer read FElement; - property Items[Idx: Integer]: TScalarValue read GetItems; default; + property Items[Idx: Integer]: TScalar read GetItems; default; + property Kind: TScalarKind read GetKind; end; // A time series of scalar records, optimized for memory and access speed. @@ -423,23 +427,13 @@ end; function TScalarRecord.GetItems(const Name: String): TScalar; var - i: Integer; - fieldDef: TScalarRecordField; + idx: Integer; begin - // Find the index of the field by name (case-insensitive) - for i := 0 to High(FDef.Fields) do - begin - fieldDef := FDef.Fields[i]; - if (CompareText(fieldDef.Name, Name) = 0) then - begin - // Found it. Construct the TScalar result from the kind and value. - Result.Create(fieldDef.Kind, FFields[i]); - exit; - end; - end; + idx := FDef.IndexOf(Name); + if idx < 0 then + raise EArgumentException.CreateFmt('Field "%s" not found in record definition.', [Name]); - // If we get here, the field was not found. - raise EArgumentException.CreateFmt('Field "%s" not found in record definition.', [Name]); + Result.Create(FDef.Fields[idx].Kind, FFields[idx]); end; { TScalarSeries } @@ -513,17 +507,20 @@ end; function TScalarMemberSeries.GetCount: Int64; begin - Result := FArray.Count; + Result := FArray.Count div Length(FDef.Fields); end; -function TScalarMemberSeries.GetItems(Idx: Integer): TScalarValue; +function TScalarMemberSeries.GetItems(Idx: Integer): TScalar; var - values: TArray; fieldCount: Integer; begin fieldCount := Length(FDef.Fields); - SetLength(values, fieldCount); - Result := FArray.ItemRef[(FArray.Count - fieldCount) - (Idx * fieldCount)]^; + Result.Create(FDef.Fields[FElement].Kind, FArray.Items[(FArray.Count - fieldCount) - (Idx * fieldCount) + FElement]); +end; + +function TScalarMemberSeries.GetKind: TScalarKind; +begin + Result := FDef.Fields[FElement].Kind; end; constructor TScalarRecordDefinition.Create(const AFields: TArray); @@ -534,15 +531,13 @@ end; function TScalarRecordDefinition.IndexOf(const Name: String): Integer; var i: Integer; - fieldDef: TScalarRecordField; begin - // Find the index of the field by name (case-insensitive) for i := 0 to High(FFields) do + begin if (CompareText(FFields[i].Name, Name) = 0) then exit(i); - - // If we get here, the field was not found. - raise EArgumentException.CreateFmt('Field "%s" not found in record definition.', [Name]); + end; + Result := -1; end; initialization