Fix in Upvalue-Logic
This commit is contained in:
@@ -17,7 +17,8 @@ uses
|
||||
Myc.Fmx.AstEditor.Text in 'Myc.Fmx.AstEditor.Text.pas',
|
||||
Myc.Ast.Traverser in '..\Src\AST\Myc.Ast.Traverser.pas',
|
||||
Myc.Ast.Binding in '..\Src\AST\Myc.Ast.Binding.pas',
|
||||
Myc.Ast.RTL in '..\Src\AST\Myc.Ast.RTL.pas';
|
||||
Myc.Ast.RTL in '..\Src\AST\Myc.Ast.RTL.pas',
|
||||
Myc.Ast.Dumper in '..\Src\AST\Myc.Ast.Dumper.pas';
|
||||
|
||||
{$R *.res}
|
||||
|
||||
|
||||
@@ -149,6 +149,7 @@
|
||||
<DCCReference Include="..\Src\AST\Myc.Ast.Traverser.pas"/>
|
||||
<DCCReference Include="..\Src\AST\Myc.Ast.Binding.pas"/>
|
||||
<DCCReference Include="..\Src\AST\Myc.Ast.RTL.pas"/>
|
||||
<DCCReference Include="..\Src\AST\Myc.Ast.Dumper.pas"/>
|
||||
<BuildConfiguration Include="Base">
|
||||
<Key>Base</Key>
|
||||
</BuildConfiguration>
|
||||
|
||||
@@ -33,7 +33,7 @@ object Form1: TForm1
|
||||
end
|
||||
object PrettyPrintButton: TButton
|
||||
Position.X = 24.000000000000000000
|
||||
Position.Y = 405.000000000000000000
|
||||
Position.Y = 463.000000000000000000
|
||||
TabOrder = 3
|
||||
Text = 'Print'
|
||||
TextSettings.Trimming = None
|
||||
@@ -52,7 +52,7 @@ object Form1: TForm1
|
||||
end
|
||||
object ShowScopeBox: TCheckBox
|
||||
Position.X = 24.000000000000000000
|
||||
Position.Y = 465.000000000000000000
|
||||
Position.Y = 436.000000000000000000
|
||||
TabOrder = 5
|
||||
Text = 'Scope'
|
||||
end
|
||||
@@ -89,7 +89,7 @@ object Form1: TForm1
|
||||
end
|
||||
object ClearButton: TButton
|
||||
Position.X = 24.000000000000000000
|
||||
Position.Y = 496.000000000000000000
|
||||
Position.Y = 538.000000000000000000
|
||||
Size.Width = 80.000000000000000000
|
||||
Size.Height = 22.000000000000000000
|
||||
Size.PlatformDefault = False
|
||||
@@ -107,7 +107,7 @@ object Form1: TForm1
|
||||
end
|
||||
object SeriesTestButton: TButton
|
||||
Position.X = 24.000000000000000000
|
||||
Position.Y = 274.000000000000000000
|
||||
Position.Y = 258.000000000000000000
|
||||
Size.Width = 80.000000000000000000
|
||||
Size.Height = 22.000000000000000000
|
||||
Size.PlatformDefault = False
|
||||
@@ -118,7 +118,7 @@ object Form1: TForm1
|
||||
end
|
||||
object OHLCButton: TButton
|
||||
Position.X = 24.000000000000000000
|
||||
Position.Y = 304.000000000000000000
|
||||
Position.Y = 288.000000000000000000
|
||||
TabOrder = 14
|
||||
Text = 'OHLC'
|
||||
TextSettings.Trimming = None
|
||||
@@ -126,13 +126,13 @@ object Form1: TForm1
|
||||
end
|
||||
object DebugBox: TCheckBox
|
||||
Position.X = 24.000000000000000000
|
||||
Position.Y = 384.000000000000000000
|
||||
Position.Y = 423.000000000000000000
|
||||
TabOrder = 15
|
||||
Text = 'Debug'
|
||||
end
|
||||
object FromJSONButton: TButton
|
||||
Position.X = 24.000000000000000000
|
||||
Position.Y = 544.000000000000000000
|
||||
Position.Y = 586.000000000000000000
|
||||
TabOrder = 16
|
||||
Text = 'From JSON'
|
||||
TextSettings.Trimming = None
|
||||
@@ -140,7 +140,7 @@ object Form1: TForm1
|
||||
end
|
||||
object ToJSONButton: TButton
|
||||
Position.X = 24.000000000000000000
|
||||
Position.Y = 574.000000000000000000
|
||||
Position.Y = 616.000000000000000000
|
||||
TabOrder = 17
|
||||
Text = 'To JSON'
|
||||
TextSettings.Trimming = None
|
||||
@@ -148,12 +148,36 @@ object Form1: TForm1
|
||||
end
|
||||
object ExternalFuncButton: TButton
|
||||
Position.X = 24.000000000000000000
|
||||
Position.Y = 336.000000000000000000
|
||||
Position.Y = 318.000000000000000000
|
||||
TabOrder = 18
|
||||
Text = 'External Func'
|
||||
TextSettings.Trimming = None
|
||||
OnClick = ExternalFuncButtonClick
|
||||
end
|
||||
object InnerLambdaButton: TButton
|
||||
Position.X = 24.000000000000000000
|
||||
Position.Y = 348.000000000000000000
|
||||
TabOrder = 19
|
||||
Text = 'Inner Lambda'
|
||||
TextSettings.Trimming = None
|
||||
OnClick = InnerLambdaButtonClick
|
||||
end
|
||||
object DumpButton: TButton
|
||||
Position.X = 24.000000000000000000
|
||||
Position.Y = 493.000000000000000000
|
||||
TabOrder = 20
|
||||
Text = 'Dump'
|
||||
TextSettings.Trimming = None
|
||||
OnClick = DumpButtonClick
|
||||
end
|
||||
object FailingUpvalueButton: TButton
|
||||
Position.X = 24.000000000000000000
|
||||
Position.Y = 378.000000000000000000
|
||||
TabOrder = 22
|
||||
Text = 'Upvalue'
|
||||
TextSettings.Trimming = None
|
||||
OnClick = FailingUpvalueButtonClick
|
||||
end
|
||||
end
|
||||
object Panel2: TPanel
|
||||
Align = Client
|
||||
|
||||
@@ -30,6 +30,7 @@ uses
|
||||
Myc.Ast,
|
||||
Myc.Ast.Evaluator,
|
||||
Myc.Ast.Printer,
|
||||
Myc.Ast.Dumper,
|
||||
FMX.Layouts,
|
||||
FMX.Objects,
|
||||
Myc.Ast.Debugger;
|
||||
@@ -66,12 +67,18 @@ type
|
||||
FromJSONButton: TButton;
|
||||
ToJSONButton: TButton;
|
||||
ExternalFuncButton: TButton;
|
||||
InnerLambdaButton: TButton;
|
||||
DumpButton: TButton;
|
||||
FailingUpvalueButton: TButton;
|
||||
procedure InnerLambdaButtonClick(Sender: TObject);
|
||||
procedure ClearButtonClick(Sender: TObject);
|
||||
procedure FormCreate(Sender: TObject);
|
||||
procedure CreateTriggerExampleButtonClick(Sender: TObject);
|
||||
procedure DoTrigger2ButtonClick(Sender: TObject);
|
||||
procedure DoTriggerButtonClick(Sender: TObject);
|
||||
procedure DumpButtonClick(Sender: TObject);
|
||||
procedure ExternalFuncButtonClick(Sender: TObject);
|
||||
procedure FailingUpvalueButtonClick(Sender: TObject);
|
||||
procedure FibonacciButtonClick(Sender: TObject);
|
||||
procedure OHLCButtonClick(Sender: TObject);
|
||||
procedure PrettyPrintButtonClick(Sender: TObject);
|
||||
@@ -108,6 +115,76 @@ uses
|
||||
|
||||
{$R *.fmx}
|
||||
|
||||
procedure TForm1.InnerLambdaButtonClick(Sender: TObject);
|
||||
var
|
||||
// Only the root node is needed as a local variable.
|
||||
mainBlock: IAstNode;
|
||||
|
||||
// Execution and result variables
|
||||
resultValue: TDataValue;
|
||||
begin
|
||||
// Build the entire AST inline to ensure no node instances are shared.
|
||||
mainBlock :=
|
||||
TAst.Block(
|
||||
[
|
||||
// var outer = lambda() { ... };
|
||||
TAst.VarDecl(
|
||||
TAst.Identifier('outer'),
|
||||
TAst.LambdaExpr(
|
||||
[],
|
||||
TAst.Block(
|
||||
[
|
||||
// var x = 10;
|
||||
TAst.VarDecl(TAst.Identifier('x'), TAst.Constant(TScalar.FromInteger(10))),
|
||||
// var inner = lambda() { ... };
|
||||
TAst.VarDecl(
|
||||
TAst.Identifier('inner'),
|
||||
TAst.LambdaExpr(
|
||||
[],
|
||||
TAst.Block(
|
||||
[
|
||||
// var innermost = lambda() { ... };
|
||||
TAst.VarDecl(
|
||||
TAst.Identifier('innermost'),
|
||||
TAst.LambdaExpr(
|
||||
[],
|
||||
// x = x + 5;
|
||||
TAst.Assign(
|
||||
TAst.Identifier('x'),
|
||||
TAst.BinaryExpr(
|
||||
TAst.Identifier('x'),
|
||||
boAdd,
|
||||
TAst.Constant(TScalar.FromInteger(5))
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
// innermost();
|
||||
TAst.FunctionCall(TAst.Identifier('innermost'), [])
|
||||
]
|
||||
)
|
||||
)
|
||||
),
|
||||
// inner();
|
||||
TAst.FunctionCall(TAst.Identifier('inner'), []),
|
||||
// return x;
|
||||
TAst.Identifier('x')
|
||||
]
|
||||
)
|
||||
)
|
||||
),
|
||||
// var finalResult = outer();
|
||||
TAst.VarDecl(TAst.Identifier('finalResult'), TAst.FunctionCall(TAst.Identifier('outer'), []))
|
||||
]
|
||||
);
|
||||
|
||||
FLastAst := mainBlock;
|
||||
|
||||
resultValue := ExecuteAst(mainBlock, FGScope);
|
||||
|
||||
Assert(TScalar.FromInteger(15) = resultValue.AsScalar, 'The final result should be 15.');
|
||||
end;
|
||||
|
||||
procedure TForm1.ClearButtonClick(Sender: TObject);
|
||||
begin
|
||||
FWorkspace.DeleteChildren;
|
||||
@@ -689,6 +766,21 @@ begin
|
||||
Memo1.Lines.Add(Format('Tick(2)! New value of X: %s', [X.ToString]));
|
||||
end;
|
||||
|
||||
procedure TForm1.DumpButtonClick(Sender: TObject);
|
||||
begin
|
||||
// Call the dumper for the last AST.
|
||||
Memo1.Lines.Clear;
|
||||
Memo1.Lines.Add('--- AST Dump ---');
|
||||
|
||||
if not Assigned(FLastAst) then
|
||||
begin
|
||||
Memo1.Lines.Add('No AST has been generated yet. Click a test button first.');
|
||||
exit;
|
||||
end;
|
||||
|
||||
TAstDumper.Dump(FLastAst, Memo1.Lines);
|
||||
end;
|
||||
|
||||
procedure TForm1.ExternalFuncButtonClick(Sender: TObject);
|
||||
var
|
||||
scope: IExecutionScope;
|
||||
@@ -722,6 +814,64 @@ begin
|
||||
Memo1.Lines.Add(Format('Result from delphiAdd(100, 23): %s', [resultValue.ToString]));
|
||||
end;
|
||||
|
||||
procedure TForm1.FailingUpvalueButtonClick(Sender: TObject);
|
||||
var
|
||||
mainBlock: IAstNode;
|
||||
resultValue: TDataValue;
|
||||
begin
|
||||
mainBlock :=
|
||||
TAst.Block(
|
||||
[
|
||||
// 1. Define the shared variable.
|
||||
TAst.VarDecl(TAst.Identifier('a'), TAst.Constant(TScalar.FromInt64(10))),
|
||||
// 2. Define a function that returns a closure that MODIFIES 'a'.
|
||||
// This creates the multi-level capture scenario.
|
||||
TAst.VarDecl(
|
||||
TAst.Identifier('modifier'),
|
||||
TAst.LambdaExpr(
|
||||
[], // Outer modifier shell
|
||||
TAst.LambdaExpr(
|
||||
[], // Inner closure that is returned
|
||||
TAst.Assign(
|
||||
TAst.Identifier('a'),
|
||||
TAst.BinaryExpr(TAst.Identifier('a'), boAdd, TAst.Constant(TScalar.FromInt64(5)))
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
// 3. Define a simple closure that READS 'a'.
|
||||
TAst.VarDecl(TAst.Identifier('reader'), TAst.LambdaExpr([], TAst.Identifier('a'))),
|
||||
// --- Execute the test ---
|
||||
// 4. Get the inner modifier closure.
|
||||
TAst.VarDecl(TAst.Identifier('innermost_closure'), TAst.FunctionCall(TAst.Identifier('modifier'), [])),
|
||||
// 5. Call the inner closure to modify 'a'.
|
||||
TAst.FunctionCall(TAst.Identifier('innermost_closure'), []),
|
||||
// 6. Call the reader to get the final value.
|
||||
TAst.FunctionCall(TAst.Identifier('reader'), [])
|
||||
]
|
||||
);
|
||||
|
||||
FLastAst := mainBlock;
|
||||
Memo1.Lines.Clear;
|
||||
Memo1.Lines.Add('--- Executing Corrected Upvalue Test ---');
|
||||
|
||||
resultValue := ExecuteAst(mainBlock, FGScope);
|
||||
|
||||
// With a correct binder and evaluator, the result must be 15.
|
||||
var res := resultValue.AsScalar.Value.AsInt64;
|
||||
if res = 15 then
|
||||
begin
|
||||
Memo1.Lines.Add('SUCCESS: The result is 15.');
|
||||
end
|
||||
else
|
||||
begin
|
||||
Memo1.Lines.Add(Format('FAILURE: Expected 15, but got %s.', [resultValue.ToString]));
|
||||
end;
|
||||
|
||||
Assert(TScalar.FromInteger(15) = resultValue.AsScalar, 'The final result should be 15.');
|
||||
Memo1.Lines.Add('Please check the new dump.');
|
||||
end;
|
||||
|
||||
procedure TForm1.FromJSONButtonClick(Sender: TObject);
|
||||
var
|
||||
jsonString: string;
|
||||
|
||||
@@ -78,6 +78,30 @@ type
|
||||
property Symbols: TDictionary<string, Integer> read FSymbols;
|
||||
end;
|
||||
|
||||
// A custom equality comparer for TResolvedAddress to ensure correct behavior in TDictionary.
|
||||
TResolvedAddressComparer = class(TEqualityComparer<TResolvedAddress>)
|
||||
public
|
||||
function Equals(const Left, Right: TResolvedAddress): Boolean; override;
|
||||
function GetHashCode(const Value: TResolvedAddress): Integer; override;
|
||||
end;
|
||||
|
||||
{ TResolvedAddressComparer }
|
||||
|
||||
function TResolvedAddressComparer.Equals(const Left, Right: TResolvedAddress): Boolean;
|
||||
begin
|
||||
// Use the existing equality operator for the record.
|
||||
Result := (Left = Right);
|
||||
end;
|
||||
|
||||
function TResolvedAddressComparer.GetHashCode(const Value: TResolvedAddress): Integer;
|
||||
begin
|
||||
// Classic hash combining algorithm using prime numbers.
|
||||
Result := 17;
|
||||
Result := Result * 23 + Ord(Value.Kind);
|
||||
Result := Result * 23 + Value.ScopeDepth;
|
||||
Result := Result * 23 + Value.SlotIndex;
|
||||
end;
|
||||
|
||||
{ TAstBinder }
|
||||
|
||||
constructor TAstBinder.Create(const AInitialScope: IExecutionScope);
|
||||
@@ -190,6 +214,44 @@ begin
|
||||
inherited;
|
||||
end;
|
||||
|
||||
function TAstBinder.VisitIdentifier(const Node: IIdentifierNode): TDataValue;
|
||||
var
|
||||
depth, idx: Integer;
|
||||
identNode: TIdentifierNode;
|
||||
upvalue: TUpvalueMapping;
|
||||
originalAddress: TResolvedAddress;
|
||||
upvalueIndex: Integer;
|
||||
begin
|
||||
identNode := Node as TIdentifierNode;
|
||||
if identNode.Address.Kind <> akUnresolved then
|
||||
exit;
|
||||
|
||||
if FCurrentDescriptor.FindSymbol(identNode.Name, depth, idx) then
|
||||
begin
|
||||
if (depth > 0) and (FUpvalueStack.Count > 0) then
|
||||
begin
|
||||
upvalue := FUpvalueStack.Peek;
|
||||
|
||||
// Address is relative to the lambda's parent scope.
|
||||
dec(depth);
|
||||
originalAddress := TResolvedAddress.Create(akLocalOrParent, depth, idx);
|
||||
|
||||
if not upvalue.Map.TryGetValue(originalAddress, upvalueIndex) then
|
||||
begin
|
||||
upvalueIndex := upvalue.Map.Count;
|
||||
upvalue.Map.Add(originalAddress, upvalueIndex);
|
||||
end;
|
||||
|
||||
(Node as TIdentifierNode).Address := TResolvedAddress.Create(akUpvalue, 0, upvalueIndex);
|
||||
end
|
||||
else
|
||||
(Node as TIdentifierNode).Address := TResolvedAddress.Create(akLocalOrParent, depth, idx);
|
||||
end
|
||||
else
|
||||
raise Exception.CreateFmt('Undefined identifier: "%s"', [identNode.Name]);
|
||||
end;
|
||||
|
||||
(*
|
||||
function TAstBinder.VisitIdentifier(const Node: IIdentifierNode): TDataValue;
|
||||
var
|
||||
depth, idx: Integer;
|
||||
@@ -227,7 +289,7 @@ begin
|
||||
else
|
||||
raise Exception.CreateFmt('Undefined identifier: "%s"', [identNode.Name]);
|
||||
end;
|
||||
|
||||
*)
|
||||
function TAstBinder.VisitIfExpression(const Node: IIfExpressionNode): TDataValue;
|
||||
begin
|
||||
// The condition is never in a tail position.
|
||||
@@ -400,7 +462,8 @@ end;
|
||||
constructor TAstBinder.TUpvalueMapping.Create;
|
||||
begin
|
||||
inherited Create;
|
||||
Map := TDictionary<TResolvedAddress, Integer>.Create;
|
||||
// Use the custom equality comparer to ensure correct dictionary behavior.
|
||||
Map := TDictionary<TResolvedAddress, Integer>.Create(TResolvedAddressComparer.Create);
|
||||
Nodes := TList<IIdentifierNode>.Create();
|
||||
end;
|
||||
|
||||
|
||||
@@ -0,0 +1,327 @@
|
||||
unit Myc.Ast.Dumper;
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
System.SysUtils,
|
||||
System.Classes,
|
||||
System.Generics.Collections,
|
||||
Myc.Ast.Nodes,
|
||||
Myc.Ast.Traverser,
|
||||
Myc.Data.Value,
|
||||
Myc.Data.Scalar;
|
||||
|
||||
type
|
||||
// Dumps a bound AST into a human-readable format for debugging purposes.
|
||||
TAstDumper = class(TAstTraverser)
|
||||
private
|
||||
FOutput: TStrings;
|
||||
FIndent: Integer;
|
||||
procedure Indent;
|
||||
procedure Unindent;
|
||||
procedure Log(const Text: string); overload;
|
||||
procedure LogFmt(const Fmt: string; const Args: array of const); overload;
|
||||
function FormatAddress(const Addr: TResolvedAddress): string;
|
||||
protected
|
||||
function Accept(const Node: IAstNode): TDataValue; override;
|
||||
public
|
||||
// Creates a new instance of the AST dumper.
|
||||
constructor Create(const AOutput: TStrings);
|
||||
|
||||
// Traverses the given root node and writes the structural information into the output.
|
||||
class procedure Dump(const RootNode: IAstNode; const Output: TStrings);
|
||||
|
||||
function VisitConstant(const Node: IConstantNode): TDataValue; override;
|
||||
function VisitIdentifier(const Node: IIdentifierNode): TDataValue; override;
|
||||
function VisitBinaryExpression(const Node: IBinaryExpressionNode): TDataValue; override;
|
||||
function VisitUnaryExpression(const Node: IUnaryExpressionNode): TDataValue; override;
|
||||
function VisitIfExpression(const Node: IIfExpressionNode): TDataValue; override;
|
||||
function VisitTernaryExpression(const Node: ITernaryExpressionNode): TDataValue; override;
|
||||
function VisitLambdaExpression(const Node: ILambdaExpressionNode): TDataValue; override;
|
||||
function VisitFunctionCall(const Node: IFunctionCallNode): TDataValue; override;
|
||||
function VisitBlockExpression(const Node: IBlockExpressionNode): TDataValue; override;
|
||||
function VisitVariableDeclaration(const Node: IVariableDeclarationNode): TDataValue; override;
|
||||
function VisitAssignment(const Node: IAssignmentNode): TDataValue; override;
|
||||
function VisitIndexer(const Node: IIndexerNode): TDataValue; override;
|
||||
function VisitMemberAccess(const Node: IMemberAccessNode): TDataValue; override;
|
||||
function VisitCreateSeries(const Node: ICreateSeriesNode): TDataValue; override;
|
||||
function VisitAddSeriesItem(const Node: IAddSeriesItemNode): TDataValue; override;
|
||||
function VisitSeriesLength(const Node: ISeriesLengthNode): TDataValue; override;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
{ TAstDumper }
|
||||
|
||||
class procedure TAstDumper.Dump(const RootNode: IAstNode; const Output: TStrings);
|
||||
var
|
||||
dumper: TAstDumper;
|
||||
begin
|
||||
if not Assigned(Output) then
|
||||
exit;
|
||||
|
||||
Output.Clear;
|
||||
dumper := TAstDumper.Create(Output);
|
||||
try
|
||||
dumper.Accept(RootNode);
|
||||
finally
|
||||
dumper.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
constructor TAstDumper.Create(const AOutput: TStrings);
|
||||
begin
|
||||
inherited Create;
|
||||
FOutput := AOutput;
|
||||
FIndent := 0;
|
||||
end;
|
||||
|
||||
function TAstDumper.Accept(const Node: IAstNode): TDataValue;
|
||||
begin
|
||||
Result := TDataValue.Void;
|
||||
if Assigned(Node) then
|
||||
Result := inherited Accept(Node);
|
||||
end;
|
||||
|
||||
procedure TAstDumper.Indent;
|
||||
begin
|
||||
inc(FIndent, 2);
|
||||
end;
|
||||
|
||||
procedure TAstDumper.Unindent;
|
||||
begin
|
||||
dec(FIndent, 2);
|
||||
end;
|
||||
|
||||
procedure TAstDumper.Log(const Text: string);
|
||||
begin
|
||||
FOutput.Add(StringOfChar(' ', FIndent) + Text);
|
||||
end;
|
||||
|
||||
procedure TAstDumper.LogFmt(const Fmt: string; const Args: array of const);
|
||||
begin
|
||||
Log(Format(Fmt, Args));
|
||||
end;
|
||||
|
||||
function TAstDumper.FormatAddress(const Addr: TResolvedAddress): string;
|
||||
begin
|
||||
case Addr.Kind of
|
||||
akUnresolved: Result := '!! UNRESOLVED !!';
|
||||
akLocalOrParent: Result := Format('LocalOrParent (Depth: %d, Slot: %d)', [Addr.ScopeDepth, Addr.SlotIndex]);
|
||||
akUpvalue: Result := Format('Upvalue (Index: %d)', [Addr.SlotIndex]);
|
||||
else
|
||||
Result := 'Unknown Address Kind';
|
||||
end;
|
||||
end;
|
||||
|
||||
function TAstDumper.VisitConstant(const Node: IConstantNode): TDataValue;
|
||||
begin
|
||||
LogFmt('Constant: %s', [Node.Value.ToString]);
|
||||
Result := TDataValue.Void;
|
||||
end;
|
||||
|
||||
function TAstDumper.VisitIdentifier(const Node: IIdentifierNode): TDataValue;
|
||||
begin
|
||||
LogFmt('Identifier: %s -> %s', [Node.Name, FormatAddress(Node.Address)]);
|
||||
Result := TDataValue.Void;
|
||||
end;
|
||||
|
||||
function TAstDumper.VisitBinaryExpression(const Node: IBinaryExpressionNode): TDataValue;
|
||||
begin
|
||||
LogFmt('BinaryExpression: %s', [Node.Operator.ToString]);
|
||||
Indent;
|
||||
Accept(Node.Left);
|
||||
Accept(Node.Right);
|
||||
Unindent;
|
||||
Result := TDataValue.Void;
|
||||
end;
|
||||
|
||||
function TAstDumper.VisitUnaryExpression(const Node: IUnaryExpressionNode): TDataValue;
|
||||
begin
|
||||
LogFmt('UnaryExpression: %s', [Node.Operator.ToString]);
|
||||
Indent;
|
||||
Accept(Node.Right);
|
||||
Unindent;
|
||||
Result := TDataValue.Void;
|
||||
end;
|
||||
|
||||
function TAstDumper.VisitIfExpression(const Node: IIfExpressionNode): TDataValue;
|
||||
begin
|
||||
Log('IfExpression');
|
||||
Indent;
|
||||
Log('Condition:');
|
||||
Accept(Node.Condition);
|
||||
Log('Then:');
|
||||
Accept(Node.ThenBranch);
|
||||
if Assigned(Node.ElseBranch) then
|
||||
begin
|
||||
Log('Else:');
|
||||
Accept(Node.ElseBranch);
|
||||
end;
|
||||
Unindent;
|
||||
Result := TDataValue.Void;
|
||||
end;
|
||||
|
||||
function TAstDumper.VisitTernaryExpression(const Node: ITernaryExpressionNode): TDataValue;
|
||||
begin
|
||||
Log('TernaryExpression');
|
||||
Indent;
|
||||
Log('Condition:');
|
||||
Accept(Node.Condition);
|
||||
Log('Then:');
|
||||
Accept(Node.ThenBranch);
|
||||
Log('Else:');
|
||||
Accept(Node.ElseBranch);
|
||||
Unindent;
|
||||
Result := TDataValue.Void;
|
||||
end;
|
||||
|
||||
function TAstDumper.VisitLambdaExpression(const Node: ILambdaExpressionNode): TDataValue;
|
||||
var
|
||||
param: IIdentifierNode;
|
||||
upvalueAddr: TResolvedAddress;
|
||||
pair: TPair<string, Integer>;
|
||||
begin
|
||||
LogFmt('LambdaExpression (HasNested: %s)', [Node.HasNestedLambdas.ToString]);
|
||||
Indent;
|
||||
|
||||
Log('Parameters:');
|
||||
Indent;
|
||||
for param in Node.Parameters do
|
||||
Accept(param);
|
||||
Unindent;
|
||||
|
||||
LogFmt('Upvalues (%d):', [Length(Node.Upvalues)]);
|
||||
Indent;
|
||||
for upvalueAddr in Node.Upvalues do
|
||||
Log(FormatAddress(upvalueAddr));
|
||||
Unindent;
|
||||
|
||||
Log('Scope Descriptor:');
|
||||
Indent;
|
||||
if Assigned(Node.ScopeDescriptor) then
|
||||
for pair in Node.ScopeDescriptor.Symbols do
|
||||
LogFmt('"%s" -> Slot %d', [pair.Key, pair.Value])
|
||||
else
|
||||
Log('(none)');
|
||||
Unindent;
|
||||
|
||||
Log('Body:');
|
||||
Accept(Node.Body);
|
||||
|
||||
Unindent;
|
||||
Result := TDataValue.Void;
|
||||
end;
|
||||
|
||||
function TAstDumper.VisitFunctionCall(const Node: IFunctionCallNode): TDataValue;
|
||||
var
|
||||
arg: IAstNode;
|
||||
begin
|
||||
LogFmt('FunctionCall (IsTailCall: %s)', [Node.IsTailCall.ToString]);
|
||||
Indent;
|
||||
Log('Callee:');
|
||||
Accept(Node.Callee);
|
||||
LogFmt('Arguments (%d):', [Length(Node.Arguments)]);
|
||||
Indent;
|
||||
for arg in Node.Arguments do
|
||||
Accept(arg);
|
||||
Unindent;
|
||||
Unindent;
|
||||
Result := TDataValue.Void;
|
||||
end;
|
||||
|
||||
function TAstDumper.VisitBlockExpression(const Node: IBlockExpressionNode): TDataValue;
|
||||
var
|
||||
expr: IAstNode;
|
||||
begin
|
||||
Log('BlockExpression');
|
||||
Indent;
|
||||
for expr in Node.Expressions do
|
||||
Accept(expr);
|
||||
Unindent;
|
||||
Result := TDataValue.Void;
|
||||
end;
|
||||
|
||||
function TAstDumper.VisitVariableDeclaration(const Node: IVariableDeclarationNode): TDataValue;
|
||||
begin
|
||||
Log('VariableDeclaration');
|
||||
Indent;
|
||||
Accept(Node.Identifier);
|
||||
if Assigned(Node.Initializer) then
|
||||
begin
|
||||
Log('Initializer:');
|
||||
Accept(Node.Initializer);
|
||||
end;
|
||||
Unindent;
|
||||
Result := TDataValue.Void;
|
||||
end;
|
||||
|
||||
function TAstDumper.VisitAssignment(const Node: IAssignmentNode): TDataValue;
|
||||
begin
|
||||
Log('Assignment');
|
||||
Indent;
|
||||
Accept(Node.Identifier);
|
||||
Log('Value:');
|
||||
Accept(Node.Value);
|
||||
Unindent;
|
||||
Result := TDataValue.Void;
|
||||
end;
|
||||
|
||||
function TAstDumper.VisitIndexer(const Node: IIndexerNode): TDataValue;
|
||||
begin
|
||||
Log('Indexer');
|
||||
Indent;
|
||||
Log('Base:');
|
||||
Accept(Node.Base);
|
||||
Log('Index:');
|
||||
Accept(Node.Index);
|
||||
Unindent;
|
||||
Result := TDataValue.Void;
|
||||
end;
|
||||
|
||||
function TAstDumper.VisitMemberAccess(const Node: IMemberAccessNode): TDataValue;
|
||||
begin
|
||||
Log('MemberAccess');
|
||||
Indent;
|
||||
Log('Base:');
|
||||
Accept(Node.Base);
|
||||
Log('Member:');
|
||||
Accept(Node.Member);
|
||||
Unindent;
|
||||
Result := TDataValue.Void;
|
||||
end;
|
||||
|
||||
function TAstDumper.VisitCreateSeries(const Node: ICreateSeriesNode): TDataValue;
|
||||
begin
|
||||
LogFmt('CreateSeries: %s', [Node.Definition]);
|
||||
Result := TDataValue.Void;
|
||||
end;
|
||||
|
||||
function TAstDumper.VisitAddSeriesItem(const Node: IAddSeriesItemNode): TDataValue;
|
||||
begin
|
||||
Log('AddSeriesItem');
|
||||
Indent;
|
||||
Log('Series:');
|
||||
Accept(Node.Series);
|
||||
Log('Value:');
|
||||
Accept(Node.Value);
|
||||
if Assigned(Node.Lookback) then
|
||||
begin
|
||||
Log('Lookback:');
|
||||
Accept(Node.Lookback);
|
||||
end;
|
||||
Unindent;
|
||||
Result := TDataValue.Void;
|
||||
end;
|
||||
|
||||
function TAstDumper.VisitSeriesLength(const Node: ISeriesLengthNode): TDataValue;
|
||||
begin
|
||||
Log('SeriesLength');
|
||||
Indent;
|
||||
Log('Series:');
|
||||
Accept(Node.Series);
|
||||
Unindent;
|
||||
Result := TDataValue.Void;
|
||||
end;
|
||||
|
||||
end.
|
||||
@@ -65,7 +65,7 @@ function NativeCreateRecordSeries(const Args: TArray<TDataValue>): TDataValue;
|
||||
var
|
||||
jsonDef: string;
|
||||
recordDef: TScalarRecordDefinition;
|
||||
series: TScalarRecordSeries;
|
||||
series: IRecordSeries;
|
||||
begin
|
||||
if (Length(Args) <> 1) or (Args[0].Kind <> vkText) then
|
||||
raise EArgumentException.Create('CreateRecordSeries requires one string argument.');
|
||||
@@ -162,7 +162,7 @@ begin
|
||||
adr.Kind := akLocalOrParent;
|
||||
adr.ScopeDepth := 0;
|
||||
|
||||
// Capture 'Self' (slot 0) for recursion.
|
||||
// Capture 'Self' (slot 0) for recursion using the captured variable.
|
||||
adr.SlotIndex := 0;
|
||||
lambdaScope[adr] := closureValue;
|
||||
|
||||
@@ -279,8 +279,7 @@ begin
|
||||
else
|
||||
begin
|
||||
var scalarKind := TScalar.StringToKind(def);
|
||||
var scalarSeries := TScalarSeries.Create(scalarKind);
|
||||
Result := TDataValue.FromSeries(scalarSeries);
|
||||
Result := TDataValue.FromSeries(TScalarSeries.Create(scalarKind));
|
||||
end;
|
||||
end;
|
||||
|
||||
|
||||
@@ -140,7 +140,7 @@ begin
|
||||
case Address.Kind of
|
||||
akUpvalue:
|
||||
begin
|
||||
// TODO Dieser Pfad wir nie erreicht!
|
||||
// TODO Dieser Pfad wird nie erreicht!
|
||||
Assert(Assigned(FCapturedUpvalues), 'Attempt to access an upvalue in a scope with no closure context.');
|
||||
Assert(
|
||||
(Address.SlotIndex >= 0) and (Address.SlotIndex < Length(FCapturedUpvalues)),
|
||||
|
||||
+2
-1
@@ -40,7 +40,8 @@ uses
|
||||
Myc.Data.POD in '..\Src\Data\Myc.Data.Scalar.pas',
|
||||
Myc.Data.Decimal in '..\Src\Data\Myc.Data.Decimal.pas',
|
||||
TestDataDecimal in 'TestDataDecimal.pas',
|
||||
TestDataPOD in 'TestDataPOD.pas';
|
||||
TestDataPOD in 'TestDataPOD.pas',
|
||||
Test.Ast.Interpreter.Scope in 'Test.Ast.Interpreter.Scope.pas';
|
||||
|
||||
{ keep comment here to protect the following conditional from being removed by the IDE when adding a unit }
|
||||
{$IFNDEF TESTINSIGHT}
|
||||
|
||||
+1
-6
@@ -144,6 +144,7 @@ $(PreBuildEvent)]]></PreBuildEvent>
|
||||
<DCCReference Include="..\Src\Data\Myc.Data.Decimal.pas"/>
|
||||
<DCCReference Include="TestDataDecimal.pas"/>
|
||||
<DCCReference Include="TestDataPOD.pas"/>
|
||||
<DCCReference Include="Test.Ast.Interpreter.Scope.pas"/>
|
||||
<BuildConfiguration Include="Base">
|
||||
<Key>Base</Key>
|
||||
</BuildConfiguration>
|
||||
@@ -187,12 +188,6 @@ $(PreBuildEvent)]]></PreBuildEvent>
|
||||
<Overwrite>true</Overwrite>
|
||||
</Platform>
|
||||
</DeployFile>
|
||||
<DeployFile LocalName="..\Src\Data\Myc.Data.POD.pas" Configuration="Debug" Class="ProjectFile">
|
||||
<Platform Name="Win64">
|
||||
<RemoteDir>.\</RemoteDir>
|
||||
<Overwrite>true</Overwrite>
|
||||
</Platform>
|
||||
</DeployFile>
|
||||
<DeployFile LocalName="Win64\Debug\MycTests.exe" Configuration="Debug" Class="ProjectOutput">
|
||||
<Platform Name="Win64">
|
||||
<RemoteName>MycTests.exe</RemoteName>
|
||||
|
||||
@@ -0,0 +1,234 @@
|
||||
unit Test.Ast.Interpreter.Scope;
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
Myc.Ast,
|
||||
Myc.Ast.Nodes,
|
||||
Myc.Ast.Binding,
|
||||
Myc.Ast.Evaluator,
|
||||
Myc.Ast.Scope,
|
||||
Myc.Data.Scalar,
|
||||
Myc.Data.Value,
|
||||
DUnitX.TestFramework;
|
||||
|
||||
type
|
||||
[TestFixture]
|
||||
TInterpreterScopeTests = class(TObject)
|
||||
private
|
||||
FGlobalScope: IExecutionScope;
|
||||
function Execute(const ANode: IAstNode): TDataValue;
|
||||
public
|
||||
[Setup]
|
||||
procedure Setup;
|
||||
[TearDown]
|
||||
procedure TearDown;
|
||||
|
||||
[Test]
|
||||
procedure Test_DeeplyNestedLambda_ModifiesUpvalue;
|
||||
|
||||
[Test]
|
||||
procedure Test_SeparateClosures_ShareSameUpvalue;
|
||||
|
||||
[Test]
|
||||
procedure Test_NestedLambda_CapturesParameter;
|
||||
|
||||
[Test]
|
||||
procedure Test_VariableShadowing_DoesNotAffectUpvalue;
|
||||
|
||||
[Test]
|
||||
procedure Test_CaptureFromGlobalScope;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
{ TInterpreterScopeTests }
|
||||
|
||||
function TInterpreterScopeTests.Execute(const ANode: IAstNode): TDataValue;
|
||||
var
|
||||
boundScope: IExecutionScope;
|
||||
visitor: IAstVisitor;
|
||||
begin
|
||||
// Helper to encapsulate the Bind -> CreateScope -> Evaluate pattern.
|
||||
boundScope := TAstBinder.Bind(ANode, FGlobalScope).CreateScope(FGlobalScope);
|
||||
visitor := TEvaluatorVisitor.Create(boundScope);
|
||||
Result := ANode.Accept(visitor);
|
||||
end;
|
||||
|
||||
procedure TInterpreterScopeTests.Setup;
|
||||
begin
|
||||
FGlobalScope := TAst.CreateScope(nil);
|
||||
end;
|
||||
|
||||
procedure TInterpreterScopeTests.TearDown;
|
||||
begin
|
||||
FGlobalScope := nil;
|
||||
end;
|
||||
|
||||
procedure TInterpreterScopeTests.Test_DeeplyNestedLambda_ModifiesUpvalue;
|
||||
var
|
||||
mainBlock: IAstNode;
|
||||
resultValue: TDataValue;
|
||||
begin
|
||||
// This is our original, simple test case with three nested lambdas.
|
||||
mainBlock :=
|
||||
TAst.Block(
|
||||
[
|
||||
TAst.VarDecl(
|
||||
TAst.Identifier('outer'),
|
||||
TAst.LambdaExpr(
|
||||
[],
|
||||
TAst.Block(
|
||||
[
|
||||
TAst.VarDecl(TAst.Identifier('x'), TAst.Constant(TScalar.FromInt64(10))),
|
||||
TAst.VarDecl(
|
||||
TAst.Identifier('inner'),
|
||||
TAst.LambdaExpr(
|
||||
[],
|
||||
TAst.Block(
|
||||
[
|
||||
TAst.VarDecl(
|
||||
TAst.Identifier('innermost'),
|
||||
TAst.LambdaExpr(
|
||||
[],
|
||||
TAst.Assign(
|
||||
TAst.Identifier('x'),
|
||||
TAst.BinaryExpr(
|
||||
TAst.Identifier('x'),
|
||||
boAdd,
|
||||
TAst.Constant(TScalar.FromInt64(5))
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
TAst.FunctionCall(TAst.Identifier('innermost'), [])
|
||||
]
|
||||
)
|
||||
)
|
||||
),
|
||||
TAst.FunctionCall(TAst.Identifier('inner'), []),
|
||||
TAst.Identifier('x')
|
||||
]
|
||||
)
|
||||
)
|
||||
),
|
||||
TAst.VarDecl(TAst.Identifier('finalResult'), TAst.FunctionCall(TAst.Identifier('outer'), []))
|
||||
]
|
||||
);
|
||||
|
||||
resultValue := Execute(mainBlock);
|
||||
Assert.AreEqual<Int64>(15, resultValue.AsScalar.Value.AsInt64, 'The final result should be 15.');
|
||||
end;
|
||||
|
||||
procedure TInterpreterScopeTests.Test_SeparateClosures_ShareSameUpvalue;
|
||||
var
|
||||
mainBlock: IAstNode;
|
||||
resultValue: TDataValue;
|
||||
begin
|
||||
// This is our more complex "modifier/reader" test case.
|
||||
mainBlock :=
|
||||
TAst.Block(
|
||||
[
|
||||
TAst.VarDecl(TAst.Identifier('a'), TAst.Constant(TScalar.FromInt64(10))),
|
||||
TAst.VarDecl(
|
||||
TAst.Identifier('modifier'),
|
||||
TAst.LambdaExpr(
|
||||
[], // Outer modifier shell
|
||||
TAst.LambdaExpr(
|
||||
[], // Inner closure that is returned
|
||||
TAst.Assign(
|
||||
TAst.Identifier('a'),
|
||||
TAst.BinaryExpr(TAst.Identifier('a'), boAdd, TAst.Constant(TScalar.FromInt64(5)))
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
TAst.VarDecl(TAst.Identifier('reader'), TAst.LambdaExpr([], TAst.Identifier('a'))),
|
||||
TAst.VarDecl(TAst.Identifier('innermost_closure'), TAst.FunctionCall(TAst.Identifier('modifier'), [])),
|
||||
TAst.FunctionCall(TAst.Identifier('innermost_closure'), []),
|
||||
TAst.FunctionCall(TAst.Identifier('reader'), [])
|
||||
]
|
||||
);
|
||||
|
||||
resultValue := Execute(mainBlock);
|
||||
Assert.AreEqual<Int64>(15, resultValue.AsScalar.Value.AsInt64, 'The final result should be 15.');
|
||||
end;
|
||||
|
||||
procedure TInterpreterScopeTests.Test_NestedLambda_CapturesParameter;
|
||||
var
|
||||
mainBlock: IAstNode;
|
||||
resultValue: TDataValue;
|
||||
begin
|
||||
// Tests if a nested lambda can correctly capture a PARAMETER of its parent lambda.
|
||||
mainBlock :=
|
||||
TAst.Block(
|
||||
[
|
||||
TAst.VarDecl(
|
||||
TAst.Identifier('factory'),
|
||||
TAst.LambdaExpr(
|
||||
[TAst.Identifier('p')], // Parameter 'p'
|
||||
TAst.LambdaExpr(
|
||||
[], // Returned lambda captures 'p'
|
||||
TAst.BinaryExpr(TAst.Identifier('p'), boMultiply, TAst.Constant(TScalar.FromInt64(2)))
|
||||
)
|
||||
)
|
||||
),
|
||||
TAst.VarDecl(
|
||||
TAst.Identifier('multiplier'),
|
||||
TAst.FunctionCall(TAst.Identifier('factory'), [TAst.Constant(TScalar.FromInt64(21))])
|
||||
),
|
||||
TAst.FunctionCall(TAst.Identifier('multiplier'), [])
|
||||
]
|
||||
);
|
||||
|
||||
resultValue := Execute(mainBlock);
|
||||
Assert.AreEqual<Int64>(42, resultValue.AsScalar.Value.AsInt64, 'The result of 21 * 2 should be 42.');
|
||||
end;
|
||||
|
||||
procedure TInterpreterScopeTests.Test_VariableShadowing_DoesNotAffectUpvalue;
|
||||
var
|
||||
mainBlock: IAstNode;
|
||||
resultValue: TDataValue;
|
||||
begin
|
||||
// Tests that a local variable 'x' correctly "shadows" a parent's variable 'x'.
|
||||
// The modification of the inner 'x' must not affect the outer 'x'.
|
||||
mainBlock :=
|
||||
TAst.Block(
|
||||
[
|
||||
TAst.VarDecl(TAst.Identifier('x'), TAst.Constant(TScalar.FromInt64(10))),
|
||||
TAst.FunctionCall(
|
||||
TAst.LambdaExpr(
|
||||
[],
|
||||
TAst.Block(
|
||||
[
|
||||
// This 'x' should shadow the outer 'x'.
|
||||
TAst.VarDecl(TAst.Identifier('x'), TAst.Constant(TScalar.FromInt64(50))),
|
||||
TAst.Assign(TAst.Identifier('x'), TAst.Constant(TScalar.FromInt64(99)))
|
||||
]
|
||||
)
|
||||
),
|
||||
[]
|
||||
),
|
||||
// This final expression should return the value of the original, outer 'x'.
|
||||
TAst.Identifier('x')
|
||||
]
|
||||
);
|
||||
|
||||
resultValue := Execute(mainBlock);
|
||||
Assert.AreEqual<Int64>(10, resultValue.AsScalar.Value.AsInt64, 'The outer "x" should remain unchanged.');
|
||||
end;
|
||||
|
||||
procedure TInterpreterScopeTests.Test_CaptureFromGlobalScope;
|
||||
var
|
||||
mainBlock: IAstNode;
|
||||
resultValue: TDataValue;
|
||||
begin
|
||||
// Defines a variable in the global scope and ensures a simple script can access it.
|
||||
FGlobalScope.Define('g', TScalar.FromInt64(99));
|
||||
mainBlock := TAst.BinaryExpr(TAst.Identifier('g'), boAdd, TAst.Constant(TScalar.FromInt64(1)));
|
||||
|
||||
resultValue := Execute(mainBlock);
|
||||
Assert.AreEqual<Int64>(100, resultValue.AsScalar.Value.AsInt64, 'Should be able to access variables from the parent scope.');
|
||||
end;
|
||||
|
||||
end.
|
||||
@@ -48,11 +48,6 @@ type
|
||||
procedure TestScalarTuple;
|
||||
[Test]
|
||||
procedure TestScalarRecordAndDefinition;
|
||||
[Test]
|
||||
procedure TestScalarSeries;
|
||||
|
||||
[Test]
|
||||
procedure TestScalarRecordSeries;
|
||||
end;
|
||||
|
||||
implementation
|
||||
@@ -254,73 +249,6 @@ begin
|
||||
);
|
||||
end;
|
||||
|
||||
procedure TTestPOD.TestScalarSeries;
|
||||
var
|
||||
seriesData: TArray<TScalarValue>;
|
||||
series: TSeries<TScalarValue>;
|
||||
scalarSeries: TScalarSeries;
|
||||
begin
|
||||
seriesData :=
|
||||
[
|
||||
TScalar.FromDouble(101.5).Value,
|
||||
TScalar.FromDouble(102.0).Value,
|
||||
TScalar.FromDouble(101.8).Value,
|
||||
TScalar.FromDouble(103.2).Value
|
||||
];
|
||||
|
||||
series := TSeries<TScalarValue>.CreateFromArray(seriesData, -1);
|
||||
scalarSeries := TScalarSeries.Create(skDouble, series);
|
||||
|
||||
Assert.AreEqual(skDouble, scalarSeries.Kind, 'Series kind mismatch');
|
||||
Assert.AreEqual(4, scalarSeries.Items.Count, 'Series count mismatch');
|
||||
Assert.AreEqual(Int64(4), scalarSeries.Items.TotalCount, 'Series total count mismatch');
|
||||
|
||||
// TSeries index is reversed: 0 is the last item
|
||||
Assert.AreEqual(103.2, scalarSeries.Items[0].AsDouble, 1E-12, 'Series item [0] mismatch');
|
||||
Assert.AreEqual(101.5, scalarSeries.Items[3].AsDouble, 1E-12, 'Series item [3] mismatch');
|
||||
end;
|
||||
|
||||
procedure TTestPOD.TestScalarRecordSeries;
|
||||
var
|
||||
recDef: TScalarRecordDefinition;
|
||||
series: TScalarRecordSeries;
|
||||
rec1, rec2, rec3: TScalarRecord;
|
||||
retrievedRec: TScalarRecord;
|
||||
begin
|
||||
// 1. Define the structure of the records in the series
|
||||
recDef := TScalarRecordDefinition.Create([TScalarRecordField.Create('ID', skInt64), TScalarRecordField.Create('Price', skDecimal)]);
|
||||
|
||||
// 2. Create the series with the definition
|
||||
series := TScalarRecordSeries.Create(recDef);
|
||||
Assert.AreEqual(Int64(0), series.TotalCount, 'Initial series should be empty');
|
||||
|
||||
// 3. Create some records
|
||||
rec1 := TScalarRecord.Create(recDef, [TScalarValue.FromInt64(1), TScalarValue.FromDecimal(TDecimal.Create(100, 2))]);
|
||||
rec2 := TScalarRecord.Create(recDef, [TScalarValue.FromInt64(2), TScalarValue.FromDecimal(TDecimal.Create(101, 2))]);
|
||||
rec3 := TScalarRecord.Create(recDef, [TScalarValue.FromInt64(3), TScalarValue.FromDecimal(TDecimal.Create(102, 2))]);
|
||||
|
||||
// 4. Add records to the series
|
||||
series.Add(rec1);
|
||||
series.Add(rec2);
|
||||
series.Add(rec3);
|
||||
|
||||
// 5. Verify the state of the series
|
||||
Assert.AreEqual(Int64(3), series.TotalCount, 'Series total count mismatch after adding items');
|
||||
|
||||
// 6. Check items. Index 0 is the last added item.
|
||||
retrievedRec := series.Items[0]; // Should be rec3
|
||||
Assert.AreEqual(skInt64, retrievedRec['ID'].Kind, 'Item[0] ID kind mismatch');
|
||||
Assert.AreEqual(Int64(3), retrievedRec['ID'].Value.AsInt64, 'Item[0] ID value mismatch');
|
||||
Assert.AreEqual(TDecimal.Create(102, 2), retrievedRec['Price'].Value.AsDecimal, 'Item[0] Price value mismatch');
|
||||
|
||||
retrievedRec := series.Items[1]; // Should be rec2
|
||||
Assert.AreEqual(Int64(2), retrievedRec.Items['ID'].Value.AsInt64, 'Item[1] ID value mismatch');
|
||||
|
||||
retrievedRec := series.Items[2]; // Should be rec1
|
||||
Assert.AreEqual(Int64(1), retrievedRec.Items['ID'].Value.AsInt64, 'Item[2] ID value mismatch');
|
||||
Assert.AreEqual(TDecimal.Create(100, 2), retrievedRec.Items['Price'].Value.AsDecimal, 'Item[2] Price value mismatch');
|
||||
end;
|
||||
|
||||
initialization
|
||||
FormatSettings.DecimalSeparator := '.';
|
||||
TDUnitX.RegisterTestFixture(TTestPOD);
|
||||
|
||||
Reference in New Issue
Block a user