AST testing
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -531,7 +531,7 @@ begin
|
|||||||
if (expr.Kind = akVariableDeclaration) then
|
if (expr.Kind = akVariableDeclaration) then
|
||||||
begin
|
begin
|
||||||
var decl := expr.AsVariableDeclaration;
|
var decl := expr.AsVariableDeclaration;
|
||||||
definitions.Add(decl.Identifier.Name, decl.Initializer);
|
definitions.Add(decl.Target.AsIdentifier.Name, decl.Initializer);
|
||||||
end
|
end
|
||||||
// Handle macro definitions inside the block
|
// Handle macro definitions inside the block
|
||||||
else if expr.Kind = akMacroDefinition then
|
else if expr.Kind = akMacroDefinition then
|
||||||
@@ -546,7 +546,7 @@ begin
|
|||||||
else if rootNode.Kind = akVariableDeclaration then // Handle single definition
|
else if rootNode.Kind = akVariableDeclaration then // Handle single definition
|
||||||
begin
|
begin
|
||||||
var decl := rootNode.AsVariableDeclaration;
|
var decl := rootNode.AsVariableDeclaration;
|
||||||
definitions.Add(decl.Identifier.Name, decl.Initializer);
|
definitions.Add(decl.Target.AsIdentifier.Name, decl.Initializer);
|
||||||
end
|
end
|
||||||
// Handle a single macro definition
|
// Handle a single macro definition
|
||||||
else if rootNode.Kind = akMacroDefinition then
|
else if rootNode.Kind = akMacroDefinition then
|
||||||
@@ -825,6 +825,7 @@ begin
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Execute with RootScope as parent.
|
// Execute with RootScope as parent.
|
||||||
|
//TODO Endlosschleife in HandleTCO
|
||||||
result := ExecuteAst(root);
|
result := ExecuteAst(root);
|
||||||
|
|
||||||
sw.Stop;
|
sw.Stop;
|
||||||
|
|||||||
@@ -375,6 +375,7 @@ type
|
|||||||
TVariableDeclarationNodeHandler = class(TInterfacedObject, IAuraNodeHandler)
|
TVariableDeclarationNodeHandler = class(TInterfacedObject, IAuraNodeHandler)
|
||||||
private
|
private
|
||||||
FNode: IVariableDeclarationNode;
|
FNode: IVariableDeclarationNode;
|
||||||
|
FTargetNode: TAuraNode;
|
||||||
FInitializerNode: TAuraNode; // Can be nil
|
FInitializerNode: TAuraNode; // Can be nil
|
||||||
function GetAstNode: IAstNode;
|
function GetAstNode: IAstNode;
|
||||||
public
|
public
|
||||||
@@ -387,6 +388,7 @@ type
|
|||||||
TAssignmentNodeHandler = class(TInterfacedObject, IAuraNodeHandler)
|
TAssignmentNodeHandler = class(TInterfacedObject, IAuraNodeHandler)
|
||||||
private
|
private
|
||||||
FNode: IAssignmentNode;
|
FNode: IAssignmentNode;
|
||||||
|
FTargetNode: TAuraNode;
|
||||||
FValueNode: TAuraNode;
|
FValueNode: TAuraNode;
|
||||||
function GetAstNode: IAstNode;
|
function GetAstNode: IAstNode;
|
||||||
public
|
public
|
||||||
@@ -1782,9 +1784,9 @@ begin
|
|||||||
OwnerNode.Orientation := loHorizontal;
|
OwnerNode.Orientation := loHorizontal;
|
||||||
|
|
||||||
varLabel := OwnerNode.AddLabel(OwnerNode, 'var');
|
varLabel := OwnerNode.AddLabel(OwnerNode, 'var');
|
||||||
varLabel.Font.Style := varLabel.Font.Style + [TFontStyle.fsBold];
|
|
||||||
|
|
||||||
OwnerNode.AddLabel(OwnerNode, FNode.Identifier.Name);
|
varLabel.Font.Style := varLabel.Font.Style + [TFontStyle.fsBold];
|
||||||
|
FTargetNode := visu.CallAccept(FNode.Target);
|
||||||
|
|
||||||
if Assigned(FNode.Initializer) then
|
if Assigned(FNode.Initializer) then
|
||||||
begin
|
begin
|
||||||
@@ -1804,7 +1806,7 @@ begin
|
|||||||
initAst := FInitializerNode.CreateAst
|
initAst := FInitializerNode.CreateAst
|
||||||
else
|
else
|
||||||
initAst := nil;
|
initAst := nil;
|
||||||
Result := TAst.VarDecl(TAst.Identifier(FNode.Identifier.Name), initAst);
|
Result := TAst.VarDecl(FTargetNode.CreateAst, initAst);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
{ TAssignmentNodeHandler }
|
{ TAssignmentNodeHandler }
|
||||||
@@ -1829,7 +1831,7 @@ begin
|
|||||||
try
|
try
|
||||||
OwnerNode.Frameless := True;
|
OwnerNode.Frameless := True;
|
||||||
OwnerNode.Orientation := loHorizontal;
|
OwnerNode.Orientation := loHorizontal;
|
||||||
OwnerNode.AddLabel(OwnerNode, FNode.Identifier.Name);
|
FTargetNode := visu.CallAccept(FNode.Target);
|
||||||
OwnerNode.AddLabel(OwnerNode, ':=');
|
OwnerNode.AddLabel(OwnerNode, ':=');
|
||||||
FValueNode := visu.CallAccept(FNode.Value);
|
FValueNode := visu.CallAccept(FNode.Value);
|
||||||
finally
|
finally
|
||||||
@@ -1839,7 +1841,7 @@ end;
|
|||||||
|
|
||||||
function TAssignmentNodeHandler.ReconstructAst(OwnerNode: TAuraNode): IAstNode;
|
function TAssignmentNodeHandler.ReconstructAst(OwnerNode: TAuraNode): IAstNode;
|
||||||
begin
|
begin
|
||||||
Result := TAst.Assign(TAst.Identifier(FNode.Identifier.Name), FValueNode.CreateAst);
|
Result := TAst.Assign(FTargetNode.CreateAst, FValueNode.CreateAst);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
{ TMacroDefinitionNodeHandler }
|
{ TMacroDefinitionNodeHandler }
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ end;
|
|||||||
|
|
||||||
function TAstToTextVisitor.VisitAssignment(const Node: IAssignmentNode): string;
|
function TAstToTextVisitor.VisitAssignment(const Node: IAssignmentNode): string;
|
||||||
begin
|
begin
|
||||||
Result := Node.Identifier.Name + ' := ' + Accept(Node.Value);
|
Result := Accept(Node.Target) + ' := ' + Accept(Node.Value);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TAstToTextVisitor.VisitBlockExpression(const Node: IBlockExpressionNode): string;
|
function TAstToTextVisitor.VisitBlockExpression(const Node: IBlockExpressionNode): string;
|
||||||
@@ -259,7 +259,7 @@ begin
|
|||||||
initStr := ' := ' + Accept(Node.Initializer)
|
initStr := ' := ' + Accept(Node.Initializer)
|
||||||
else
|
else
|
||||||
initStr := '';
|
initStr := '';
|
||||||
Result := 'var ' + Node.Identifier.Name + initStr;
|
Result := 'var ' + Accept(Node.Target) + initStr;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
end.
|
end.
|
||||||
|
|||||||
@@ -212,7 +212,7 @@ begin
|
|||||||
|
|
||||||
// 2. Define the variable in the CURRENT scope
|
// 2. Define the variable in the CURRENT scope
|
||||||
// Store the Node reference so we can add it to FBoxedDeclarations if captured.
|
// Store the Node reference so we can add it to FBoxedDeclarations if captured.
|
||||||
FCurrentScope.Define(Node.Identifier.Name, Node);
|
FCurrentScope.Define(Node.Target.AsIdentifier.Name, Node);
|
||||||
|
|
||||||
Result := Node;
|
Result := Node;
|
||||||
end;
|
end;
|
||||||
|
|||||||
@@ -256,10 +256,11 @@ var
|
|||||||
newIdent: IIdentifierNode;
|
newIdent: IIdentifierNode;
|
||||||
isBoxed: Boolean;
|
isBoxed: Boolean;
|
||||||
begin
|
begin
|
||||||
if not IsValidIdentifier(Node.Identifier.Name) then
|
var identifier := Node.Target.AsIdentifier;
|
||||||
raise Exception.CreateFmt('Invalid identifier name: "%s".', [Node.Identifier.Name]);
|
if not IsValidIdentifier(identifier.Name) then
|
||||||
|
raise Exception.CreateFmt('Invalid identifier name: "%s".', [identifier.Name]);
|
||||||
|
|
||||||
slot := FCurrentBuilder.Define(Node.Identifier.Name);
|
slot := FCurrentBuilder.Define(identifier.Name);
|
||||||
addr := TResolvedAddress.Create(akLocalOrParent, 0, slot);
|
addr := TResolvedAddress.Create(akLocalOrParent, 0, slot);
|
||||||
|
|
||||||
if Assigned(Node.Initializer) then
|
if Assigned(Node.Initializer) then
|
||||||
@@ -267,7 +268,7 @@ begin
|
|||||||
else
|
else
|
||||||
newInit := nil;
|
newInit := nil;
|
||||||
|
|
||||||
newIdent := TAst.Identifier(Node.Identifier.Name, addr, TTypes.Unknown);
|
newIdent := TAst.Identifier(identifier.Name, addr, TTypes.Unknown);
|
||||||
isBoxed := (FBoxedDeclarations <> nil) and FBoxedDeclarations.Contains(Node);
|
isBoxed := (FBoxedDeclarations <> nil) and FBoxedDeclarations.Contains(Node);
|
||||||
|
|
||||||
Result := TAst.VarDecl(newIdent, newInit, TTypes.Unknown, isBoxed);
|
Result := TAst.VarDecl(newIdent, newInit, TTypes.Unknown, isBoxed);
|
||||||
@@ -281,7 +282,7 @@ var
|
|||||||
newIdent: IAstNode;
|
newIdent: IAstNode;
|
||||||
newValue: IAstNode;
|
newValue: IAstNode;
|
||||||
begin
|
begin
|
||||||
newIdent := Accept(Node.Identifier);
|
newIdent := Accept(Node.Target);
|
||||||
newValue := Accept(Node.Value);
|
newValue := Accept(Node.Value);
|
||||||
|
|
||||||
Result := TAst.Assign(newIdent.AsIdentifier, newValue, TTypes.Unknown);
|
Result := TAst.Assign(newIdent.AsIdentifier, newValue, TTypes.Unknown);
|
||||||
|
|||||||
@@ -87,6 +87,19 @@ type
|
|||||||
): IAstNode; static;
|
): IAstNode; static;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
TMacroRegistry = class(TInterfacedObject, IMacroRegistry)
|
||||||
|
private
|
||||||
|
FParent: IMacroRegistry;
|
||||||
|
FMacros: TDictionary<string, IMacroDefinitionNode>;
|
||||||
|
function GetParent: IMacroRegistry;
|
||||||
|
public
|
||||||
|
constructor Create(AParent: IMacroRegistry);
|
||||||
|
destructor Destroy; override;
|
||||||
|
procedure Define(const Node: IMacroDefinitionNode);
|
||||||
|
function Find(const Name: string): IMacroDefinitionNode;
|
||||||
|
function CreateChildRegistry: IMacroRegistry;
|
||||||
|
end;
|
||||||
|
|
||||||
implementation
|
implementation
|
||||||
|
|
||||||
uses
|
uses
|
||||||
@@ -193,14 +206,23 @@ end;
|
|||||||
|
|
||||||
function TExpansionVisitor.VisitVariableDeclaration(const Node: IVariableDeclarationNode): IAstNode;
|
function TExpansionVisitor.VisitVariableDeclaration(const Node: IVariableDeclarationNode): IAstNode;
|
||||||
var
|
var
|
||||||
|
newTarget, newInit: IAstNode;
|
||||||
newName: string;
|
newName: string;
|
||||||
newIdent: IIdentifierNode;
|
|
||||||
newInit: IAstNode;
|
|
||||||
begin
|
begin
|
||||||
newInit := Accept(Node.Initializer);
|
newInit := Accept(Node.Initializer);
|
||||||
newName := Gensym(Node.Identifier.Name);
|
|
||||||
newIdent := TAst.Identifier(newName, TTypes.Unknown);
|
// Check if target is identifier before trying to rename
|
||||||
Result := TAst.VarDecl(newIdent, newInit, TTypes.Unknown);
|
if Node.Target.Kind = akIdentifier then
|
||||||
|
begin
|
||||||
|
newName := Gensym(Node.Target.AsIdentifier.Name);
|
||||||
|
newTarget := TAst.Identifier(newName, TTypes.Unknown);
|
||||||
|
end
|
||||||
|
else
|
||||||
|
begin
|
||||||
|
newTarget := Accept(Node.Target); // Recursively expand unquotes in target position!
|
||||||
|
end;
|
||||||
|
|
||||||
|
Result := TAst.VarDecl(newTarget, newInit, TTypes.Unknown);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TExpansionVisitor.VisitLambdaExpression(const Node: ILambdaExpressionNode): IAstNode;
|
function TExpansionVisitor.VisitLambdaExpression(const Node: ILambdaExpressionNode): IAstNode;
|
||||||
@@ -438,4 +460,48 @@ begin
|
|||||||
raise Exception.Create('Unquote-splicing (`~@`) can only be used inside a quasiquote.');
|
raise Exception.Create('Unquote-splicing (`~@`) can only be used inside a quasiquote.');
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
{ TMacroRegistry }
|
||||||
|
|
||||||
|
constructor TMacroRegistry.Create(AParent: IMacroRegistry);
|
||||||
|
begin
|
||||||
|
inherited Create;
|
||||||
|
FParent := AParent;
|
||||||
|
FMacros := TDictionary<string, IMacroDefinitionNode>.Create;
|
||||||
|
end;
|
||||||
|
|
||||||
|
destructor TMacroRegistry.Destroy;
|
||||||
|
begin
|
||||||
|
FMacros.Free;
|
||||||
|
inherited Destroy;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TMacroRegistry.GetParent: IMacroRegistry;
|
||||||
|
begin
|
||||||
|
Result := FParent;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TMacroRegistry.Define(const Node: IMacroDefinitionNode);
|
||||||
|
begin
|
||||||
|
FMacros.AddOrSetValue(Node.Name.Name, Node);
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TMacroRegistry.Find(const Name: string): IMacroDefinitionNode;
|
||||||
|
var
|
||||||
|
current: IMacroRegistry;
|
||||||
|
begin
|
||||||
|
current := Self;
|
||||||
|
while Assigned(current) do
|
||||||
|
begin
|
||||||
|
if (current as TMacroRegistry).FMacros.TryGetValue(Name, Result) then
|
||||||
|
exit;
|
||||||
|
current := current.Parent;
|
||||||
|
end;
|
||||||
|
Result := nil;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TMacroRegistry.CreateChildRegistry: IMacroRegistry;
|
||||||
|
begin
|
||||||
|
Result := TMacroRegistry.Create(Self);
|
||||||
|
end;
|
||||||
|
|
||||||
end.
|
end.
|
||||||
|
|||||||
@@ -240,7 +240,7 @@ var
|
|||||||
placeholderType: IStaticType;
|
placeholderType: IStaticType;
|
||||||
i: Integer;
|
i: Integer;
|
||||||
begin
|
begin
|
||||||
adr := Node.Identifier.Address;
|
adr := Node.Target.AsIdentifier.Address;
|
||||||
initType := TTypes.Unknown;
|
initType := TTypes.Unknown;
|
||||||
|
|
||||||
// Recursive lambda bootstrap logic
|
// Recursive lambda bootstrap logic
|
||||||
@@ -274,7 +274,7 @@ begin
|
|||||||
if initType.Kind <> stUnknown then
|
if initType.Kind <> stUnknown then
|
||||||
FCurrentContext.SetType(adr.SlotIndex, initType);
|
FCurrentContext.SetType(adr.SlotIndex, initType);
|
||||||
|
|
||||||
newIdent := TAst.Identifier(Node.Identifier.Name, adr, initType);
|
newIdent := TAst.Identifier(Node.Target.AsIdentifier.Name, adr, initType);
|
||||||
|
|
||||||
Result := TAst.VarDecl(newIdent.AsIdentifier, newInitializer, initType, Node.IsBoxed);
|
Result := TAst.VarDecl(newIdent.AsIdentifier, newInitializer, initType, Node.IsBoxed);
|
||||||
end;
|
end;
|
||||||
@@ -288,7 +288,7 @@ var
|
|||||||
placeholderType: IStaticType;
|
placeholderType: IStaticType;
|
||||||
i: Integer;
|
i: Integer;
|
||||||
begin
|
begin
|
||||||
newIdent := Accept(Node.Identifier);
|
newIdent := Accept(Node.Target);
|
||||||
targetType := newIdent.AsTypedNode.StaticType;
|
targetType := newIdent.AsTypedNode.StaticType;
|
||||||
adr := newIdent.AsIdentifier.Address;
|
adr := newIdent.AsIdentifier.Address;
|
||||||
|
|
||||||
|
|||||||
@@ -162,7 +162,7 @@ end;
|
|||||||
|
|
||||||
function TDebugEvaluatorVisitor.VisitAssignment(const Node: IAssignmentNode): TDataValue;
|
function TDebugEvaluatorVisitor.VisitAssignment(const Node: IAssignmentNode): TDataValue;
|
||||||
begin
|
begin
|
||||||
AppendLine(Format('Assignment to "%s" {', [Node.Identifier.Name]));
|
AppendLine(Format('Assignment to "%s" {', [Node.Target.AsIdentifier.Name]));
|
||||||
Indent;
|
Indent;
|
||||||
try
|
try
|
||||||
Result := inherited VisitAssignment(Node);
|
Result := inherited VisitAssignment(Node);
|
||||||
@@ -278,7 +278,7 @@ end;
|
|||||||
|
|
||||||
function TDebugEvaluatorVisitor.VisitVariableDeclaration(const Node: IVariableDeclarationNode): TDataValue;
|
function TDebugEvaluatorVisitor.VisitVariableDeclaration(const Node: IVariableDeclarationNode): TDataValue;
|
||||||
begin
|
begin
|
||||||
AppendLine(Format('VarDecl %s :=', [Node.Identifier.Name]));
|
AppendLine(Format('VarDecl %s :=', [Node.Target.AsIdentifier.Name]));
|
||||||
Indent;
|
Indent;
|
||||||
try
|
try
|
||||||
Result := inherited VisitVariableDeclaration(Node);
|
Result := inherited VisitVariableDeclaration(Node);
|
||||||
|
|||||||
@@ -355,7 +355,7 @@ procedure TAstDumper.VisitVariableDeclaration(const Node: IVariableDeclarationNo
|
|||||||
begin
|
begin
|
||||||
LogFmt('VariableDeclaration (IsBoxed: %s)', [Node.IsBoxed.ToString(TUseBoolStrs.True)], Node);
|
LogFmt('VariableDeclaration (IsBoxed: %s)', [Node.IsBoxed.ToString(TUseBoolStrs.True)], Node);
|
||||||
Indent;
|
Indent;
|
||||||
Node.Identifier.Accept(Self);
|
Node.Target.Accept(Self);
|
||||||
if Assigned(Node.Initializer) then
|
if Assigned(Node.Initializer) then
|
||||||
begin
|
begin
|
||||||
Log('Initializer:');
|
Log('Initializer:');
|
||||||
@@ -368,7 +368,7 @@ procedure TAstDumper.VisitAssignment(const Node: IAssignmentNode);
|
|||||||
begin
|
begin
|
||||||
Log('Assignment', Node);
|
Log('Assignment', Node);
|
||||||
Indent;
|
Indent;
|
||||||
Node.Identifier.Accept(Self);
|
Node.Target.Accept(Self);
|
||||||
Log('Value:');
|
Log('Value:');
|
||||||
Node.Value.Accept(Self);
|
Node.Value.Accept(Self);
|
||||||
Unindent;
|
Unindent;
|
||||||
|
|||||||
@@ -228,20 +228,6 @@ type
|
|||||||
function CreateVisitor(const AScope: IExecutionScope): IEvaluatorVisitor;
|
function CreateVisitor(const AScope: IExecutionScope): IEvaluatorVisitor;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
{ TMacroRegistryImpl }
|
|
||||||
TMacroRegistryImpl = class(TInterfacedObject, IMacroRegistry)
|
|
||||||
private
|
|
||||||
FParent: IMacroRegistry;
|
|
||||||
FMacros: TDictionary<string, IMacroDefinitionNode>;
|
|
||||||
function GetParent: IMacroRegistry;
|
|
||||||
public
|
|
||||||
constructor Create(AParent: IMacroRegistry);
|
|
||||||
destructor Destroy; override;
|
|
||||||
procedure Define(const Node: IMacroDefinitionNode);
|
|
||||||
function Find(const Name: string): IMacroDefinitionNode;
|
|
||||||
function CreateChildRegistry: IMacroRegistry;
|
|
||||||
end;
|
|
||||||
|
|
||||||
TFunctionDefinitionRegistry = class(TInterfacedObject, IFunctionDefinitionRegistry)
|
TFunctionDefinitionRegistry = class(TInterfacedObject, IFunctionDefinitionRegistry)
|
||||||
private
|
private
|
||||||
FMap: TDictionary<TResolvedAddress, IFunctionDefinition>;
|
FMap: TDictionary<TResolvedAddress, IFunctionDefinition>;
|
||||||
@@ -336,7 +322,7 @@ begin
|
|||||||
// Initialize root scope with library registration
|
// Initialize root scope with library registration
|
||||||
RootScope := TAst.CreateScope(nil, nil, True);
|
RootScope := TAst.CreateScope(nil, nil, True);
|
||||||
|
|
||||||
Result.Create(TEnvironment.Create(RootScope, TMacroRegistryImpl.Create(nil), TStandardExecutionStrategy.Create));
|
Result.Create(TEnvironment.Create(RootScope, TMacroRegistry.Create(nil), TStandardExecutionStrategy.Create));
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TAstEnvironment.CreateEnvironment: TAstEnvironment;
|
function TAstEnvironment.CreateEnvironment: TAstEnvironment;
|
||||||
@@ -393,50 +379,6 @@ begin
|
|||||||
Result := TDebugEvaluatorVisitor.Create(AScope, FLog, FShowScope, 0);
|
Result := TDebugEvaluatorVisitor.Create(AScope, FLog, FShowScope, 0);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
{ TMacroRegistryImpl }
|
|
||||||
|
|
||||||
constructor TMacroRegistryImpl.Create(AParent: IMacroRegistry);
|
|
||||||
begin
|
|
||||||
inherited Create;
|
|
||||||
FParent := AParent;
|
|
||||||
FMacros := TDictionary<string, IMacroDefinitionNode>.Create;
|
|
||||||
end;
|
|
||||||
|
|
||||||
destructor TMacroRegistryImpl.Destroy;
|
|
||||||
begin
|
|
||||||
FMacros.Free;
|
|
||||||
inherited Destroy;
|
|
||||||
end;
|
|
||||||
|
|
||||||
function TMacroRegistryImpl.GetParent: IMacroRegistry;
|
|
||||||
begin
|
|
||||||
Result := FParent;
|
|
||||||
end;
|
|
||||||
|
|
||||||
procedure TMacroRegistryImpl.Define(const Node: IMacroDefinitionNode);
|
|
||||||
begin
|
|
||||||
FMacros.AddOrSetValue(Node.Name.Name, Node);
|
|
||||||
end;
|
|
||||||
|
|
||||||
function TMacroRegistryImpl.Find(const Name: string): IMacroDefinitionNode;
|
|
||||||
var
|
|
||||||
current: IMacroRegistry;
|
|
||||||
begin
|
|
||||||
current := Self;
|
|
||||||
while Assigned(current) do
|
|
||||||
begin
|
|
||||||
if (current as TMacroRegistryImpl).FMacros.TryGetValue(Name, Result) then
|
|
||||||
exit;
|
|
||||||
current := current.Parent;
|
|
||||||
end;
|
|
||||||
Result := nil;
|
|
||||||
end;
|
|
||||||
|
|
||||||
function TMacroRegistryImpl.CreateChildRegistry: IMacroRegistry;
|
|
||||||
begin
|
|
||||||
Result := TMacroRegistryImpl.Create(Self);
|
|
||||||
end;
|
|
||||||
|
|
||||||
{ TFunctionDefinitionRegistry }
|
{ TFunctionDefinitionRegistry }
|
||||||
|
|
||||||
constructor TFunctionDefinitionRegistry.Create;
|
constructor TFunctionDefinitionRegistry.Create;
|
||||||
@@ -514,7 +456,7 @@ end;
|
|||||||
|
|
||||||
function TEnvironment.CreateEnvironment: IEnvironment;
|
function TEnvironment.CreateEnvironment: IEnvironment;
|
||||||
begin
|
begin
|
||||||
Result := TEnvironment.Create(TAst.CreateScope(FRootScope), TMacroRegistryImpl.Create(FMacroRegistry), FExecutionStrategy);
|
Result := TEnvironment.Create(TAst.CreateScope(FRootScope), TMacroRegistry.Create(FMacroRegistry), FExecutionStrategy);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TEnvironment.Compile(
|
function TEnvironment.Compile(
|
||||||
|
|||||||
@@ -168,6 +168,7 @@ begin
|
|||||||
TScalar.TKind.Ordinal: Result := AValue.AsScalar.Value.AsInt64 <> 0;
|
TScalar.TKind.Ordinal: Result := AValue.AsScalar.Value.AsInt64 <> 0;
|
||||||
TScalar.TKind.Float: Result := AValue.AsScalar.Value.AsDouble <> 0.0;
|
TScalar.TKind.Float: Result := AValue.AsScalar.Value.AsDouble <> 0.0;
|
||||||
TScalar.TKind.Keyword: Result := AValue.AsScalar.Value.AsInt64 <> 0;
|
TScalar.TKind.Keyword: Result := AValue.AsScalar.Value.AsInt64 <> 0;
|
||||||
|
TScalar.TKind.Boolean: Result := AValue.AsScalar.Value.AsInt64 <> 0;
|
||||||
else
|
else
|
||||||
Result := false;
|
Result := false;
|
||||||
end;
|
end;
|
||||||
@@ -392,10 +393,13 @@ end;
|
|||||||
|
|
||||||
function TEvaluatorVisitor.VisitAssignment(const Node: IAssignmentNode): TDataValue;
|
function TEvaluatorVisitor.VisitAssignment(const Node: IAssignmentNode): TDataValue;
|
||||||
begin
|
begin
|
||||||
// Evaluate the new value.
|
if Node.Target.Kind <> akIdentifier then
|
||||||
|
raise ETypeException.Create('Runtime Error: Assignment target must be an identifier.');
|
||||||
|
|
||||||
|
// Evaluate value
|
||||||
Result := Node.Value.Accept(Self);
|
Result := Node.Value.Accept(Self);
|
||||||
// Assign it.
|
// Assign
|
||||||
FScope[Node.Identifier.Address] := Result;
|
FScope[Node.Target.AsIdentifier.Address] := Result;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TEvaluatorVisitor.VisitConstant(const Node: IConstantNode): TDataValue;
|
function TEvaluatorVisitor.VisitConstant(const Node: IConstantNode): TDataValue;
|
||||||
@@ -561,6 +565,9 @@ function TEvaluatorVisitor.VisitVariableDeclaration(const Node: IVariableDeclara
|
|||||||
var
|
var
|
||||||
address: TResolvedAddress;
|
address: TResolvedAddress;
|
||||||
begin
|
begin
|
||||||
|
if Node.Target.Kind <> akIdentifier then
|
||||||
|
raise ETypeException.Create('Runtime Error: Variable declaration target must be an identifier.');
|
||||||
|
|
||||||
// 1. Evaluate Initializer
|
// 1. Evaluate Initializer
|
||||||
if Assigned(Node.Initializer) then
|
if Assigned(Node.Initializer) then
|
||||||
Result := Node.Initializer.Accept(Self)
|
Result := Node.Initializer.Accept(Self)
|
||||||
@@ -568,7 +575,7 @@ begin
|
|||||||
Result := TDataValue.Void;
|
Result := TDataValue.Void;
|
||||||
|
|
||||||
// 2. Get Address (assigned by Binder)
|
// 2. Get Address (assigned by Binder)
|
||||||
address := Node.Identifier.Address;
|
address := Node.Target.AsIdentifier.Address;
|
||||||
|
|
||||||
// 3. Store Value
|
// 3. Store Value
|
||||||
if Node.IsBoxed then
|
if Node.IsBoxed then
|
||||||
|
|||||||
@@ -319,7 +319,7 @@ function TJsonAstConverter.VisitVariableDeclaration(const Node: IVariableDeclara
|
|||||||
var
|
var
|
||||||
identObj, initObj: TJSONObject;
|
identObj, initObj: TJSONObject;
|
||||||
begin
|
begin
|
||||||
identObj := Accept(Node.Identifier);
|
identObj := Accept(Node.Target);
|
||||||
initObj := Accept(Node.Initializer); // Accept handles nil
|
initObj := Accept(Node.Initializer); // Accept handles nil
|
||||||
|
|
||||||
Result := TJSONObject.Create;
|
Result := TJSONObject.Create;
|
||||||
@@ -336,7 +336,7 @@ function TJsonAstConverter.VisitAssignment(const Node: IAssignmentNode): TJSONOb
|
|||||||
var
|
var
|
||||||
identObj, valueObj: TJSONObject;
|
identObj, valueObj: TJSONObject;
|
||||||
begin
|
begin
|
||||||
identObj := Accept(Node.Identifier);
|
identObj := Accept(Node.Target);
|
||||||
valueObj := Accept(Node.Value);
|
valueObj := Accept(Node.Value);
|
||||||
|
|
||||||
Result := TJSONObject.Create;
|
Result := TJSONObject.Create;
|
||||||
|
|||||||
@@ -287,21 +287,21 @@ type
|
|||||||
|
|
||||||
IVariableDeclarationNode = interface(IAstTypedNode)
|
IVariableDeclarationNode = interface(IAstTypedNode)
|
||||||
{$region 'private'}
|
{$region 'private'}
|
||||||
function GetIdentifier: IIdentifierNode;
|
function GetTarget: IAstNode;
|
||||||
function GetInitializer: IAstNode;
|
function GetInitializer: IAstNode;
|
||||||
function GetIsBoxed: Boolean;
|
function GetIsBoxed: Boolean;
|
||||||
{$endregion}
|
{$endregion}
|
||||||
property Identifier: IIdentifierNode read GetIdentifier;
|
property Target: IAstNode read GetTarget;
|
||||||
property Initializer: IAstNode read GetInitializer;
|
property Initializer: IAstNode read GetInitializer;
|
||||||
property IsBoxed: Boolean read GetIsBoxed;
|
property IsBoxed: Boolean read GetIsBoxed;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
IAssignmentNode = interface(IAstTypedNode)
|
IAssignmentNode = interface(IAstTypedNode)
|
||||||
{$region 'private'}
|
{$region 'private'}
|
||||||
function GetIdentifier: IIdentifierNode;
|
function GetTarget: IAstNode;
|
||||||
function GetValue: IAstNode;
|
function GetValue: IAstNode;
|
||||||
{$endregion}
|
{$endregion}
|
||||||
property Identifier: IIdentifierNode read GetIdentifier;
|
property Target: IAstNode read GetTarget;
|
||||||
property Value: IAstNode read GetValue;
|
property Value: IAstNode read GetValue;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
|||||||
@@ -574,20 +574,15 @@ begin
|
|||||||
end
|
end
|
||||||
else if SameText(head.Token.Text, 'def') then
|
else if SameText(head.Token.Text, 'def') then
|
||||||
begin
|
begin
|
||||||
// Validate argument count for 'def' special form.
|
// Identifier check removed to support macros (e.g. def ~x 1)
|
||||||
if not (Length(tailNodes) in [1, 2]) then
|
if not (Length(tailNodes) in [1, 2]) then
|
||||||
raise Exception.CreateFmt(
|
raise Exception.CreateFmt('Syntax Error: ''def'' requires a target and an optional initializer.', []);
|
||||||
'Syntax Error: ''def'' requires an identifier and an optional initializer (1 or 2 arguments), but got %d.',
|
|
||||||
[Length(tailNodes)]);
|
|
||||||
|
|
||||||
if tailTokens[0].Kind <> tkIdentifier then
|
|
||||||
raise Exception.Create('Syntax Error: Expected an identifier for def statement.');
|
|
||||||
|
|
||||||
initializer := nil;
|
initializer := nil;
|
||||||
if Length(tailNodes) = 2 then
|
if Length(tailNodes) = 2 then
|
||||||
initializer := tailNodes[1];
|
initializer := tailNodes[1];
|
||||||
|
|
||||||
Result := TAst.VarDecl(IIdentifierNode(tailNodes[0]), initializer);
|
Result := TAst.VarDecl(tailNodes[0], initializer);
|
||||||
end
|
end
|
||||||
else if SameText(head.Token.Text, 'defmacro') then
|
else if SameText(head.Token.Text, 'defmacro') then
|
||||||
begin
|
begin
|
||||||
@@ -606,11 +601,11 @@ begin
|
|||||||
end
|
end
|
||||||
else if SameText(head.Token.Text, 'assign') then
|
else if SameText(head.Token.Text, 'assign') then
|
||||||
begin
|
begin
|
||||||
|
// Identifier check removed
|
||||||
if Length(tailNodes) <> 2 then
|
if Length(tailNodes) <> 2 then
|
||||||
raise Exception.Create('Syntax Error: ''assign'' requires exactly 2 arguments (identifier and value).');
|
raise Exception.Create('Syntax Error: ''assign'' requires exactly 2 arguments (target and value).');
|
||||||
if tailTokens[0].Kind <> tkIdentifier then
|
|
||||||
raise Exception.Create('Syntax Error: Expected an identifier for assignment.');
|
Result := TAst.Assign(tailNodes[0], tailNodes[1]);
|
||||||
Result := TAst.Assign(IIdentifierNode(tailNodes[0]), tailNodes[1]);
|
|
||||||
end
|
end
|
||||||
else if SameText(head.Token.Text, 'fn') then
|
else if SameText(head.Token.Text, 'fn') then
|
||||||
begin
|
begin
|
||||||
@@ -988,7 +983,7 @@ end;
|
|||||||
procedure TPrettyPrintVisitor.VisitVariableDeclaration(const Node: IVariableDeclarationNode);
|
procedure TPrettyPrintVisitor.VisitVariableDeclaration(const Node: IVariableDeclarationNode);
|
||||||
begin
|
begin
|
||||||
Append('(def ');
|
Append('(def ');
|
||||||
Node.Identifier.Accept(Self);
|
Node.Target.Accept(Self);
|
||||||
if Assigned(Node.Initializer) then
|
if Assigned(Node.Initializer) then
|
||||||
begin
|
begin
|
||||||
Append(' ');
|
Append(' ');
|
||||||
@@ -1000,7 +995,7 @@ end;
|
|||||||
procedure TPrettyPrintVisitor.VisitAssignment(const Node: IAssignmentNode);
|
procedure TPrettyPrintVisitor.VisitAssignment(const Node: IAssignmentNode);
|
||||||
begin
|
begin
|
||||||
Append('(assign ');
|
Append('(assign ');
|
||||||
Node.Identifier.Accept(Self);
|
Node.Target.Accept(Self);
|
||||||
Append(' ');
|
Append(' ');
|
||||||
Node.Value.Accept(Self);
|
Node.Value.Accept(Self);
|
||||||
Append(')');
|
Append(')');
|
||||||
|
|||||||
@@ -545,35 +545,32 @@ end;
|
|||||||
|
|
||||||
function TAstTransformer.VisitVariableDeclaration(const Node: IVariableDeclarationNode): IAstNode;
|
function TAstTransformer.VisitVariableDeclaration(const Node: IVariableDeclarationNode): IAstNode;
|
||||||
var
|
var
|
||||||
newIdent: IIdentifierNode;
|
newTarget: IAstNode;
|
||||||
newInit: IAstNode;
|
newInit: IAstNode;
|
||||||
begin
|
begin
|
||||||
// No longer cast to concrete class, use interface
|
newTarget := Accept(Node.Target);
|
||||||
newIdent := Accept(Node.Identifier).AsIdentifier;
|
newInit := Accept(Node.Initializer);
|
||||||
newInit := Accept(Node.Initializer); // Accept handles nil
|
|
||||||
|
|
||||||
if (newIdent = Node.Identifier) and (newInit = Node.Initializer) then
|
if (newTarget = Node.Target) and (newInit = Node.Initializer) then
|
||||||
Result := Node
|
Result := Node
|
||||||
else
|
else
|
||||||
begin
|
begin
|
||||||
// Use TAst factory and copy properties via interface getters
|
Result := TAst.VarDecl(newTarget, newInit, Node.StaticType, Node.IsBoxed);
|
||||||
Result := TAst.VarDecl(newIdent, newInit, Node.StaticType, Node.IsBoxed);
|
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TAstTransformer.VisitAssignment(const Node: IAssignmentNode): IAstNode;
|
function TAstTransformer.VisitAssignment(const Node: IAssignmentNode): IAstNode;
|
||||||
var
|
var
|
||||||
newIdent: IIdentifierNode;
|
newTarget: IAstNode;
|
||||||
newValue: IAstNode;
|
newValue: IAstNode;
|
||||||
begin
|
begin
|
||||||
|
newTarget := Accept(Node.Target);
|
||||||
newValue := Accept(Node.Value);
|
newValue := Accept(Node.Value);
|
||||||
newIdent := Accept(Node.Identifier).AsIdentifier;
|
|
||||||
|
|
||||||
if (newValue = Node.Value) and (newIdent = Node.Identifier) then
|
if (newTarget = Node.Target) and (newValue = Node.Value) then
|
||||||
Result := Node
|
Result := Node
|
||||||
else
|
else
|
||||||
// Use TAst factory
|
Result := TAst.Assign(newTarget, newValue, Node.StaticType);
|
||||||
Result := TAst.Assign(newIdent, newValue, Node.StaticType);
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TAstTransformer.VisitMacroDefinition(const Node: IMacroDefinitionNode): IAstNode;
|
function TAstTransformer.VisitMacroDefinition(const Node: IMacroDefinitionNode): IAstNode;
|
||||||
|
|||||||
+20
-34
@@ -95,17 +95,13 @@ type
|
|||||||
class function Block(const AExpressions: array of IAstNode; const AStaticType: IStaticType = nil): IBlockExpressionNode; static;
|
class function Block(const AExpressions: array of IAstNode; const AStaticType: IStaticType = nil): IBlockExpressionNode; static;
|
||||||
|
|
||||||
class function VarDecl(
|
class function VarDecl(
|
||||||
const AIdentifier: IIdentifierNode;
|
const AIdentifier: IAstNode;
|
||||||
AInitializer: IAstNode = nil;
|
AInitializer: IAstNode = nil;
|
||||||
const AStaticType: IStaticType = nil;
|
const AStaticType: IStaticType = nil;
|
||||||
const AIsBoxed: Boolean = False
|
const AIsBoxed: Boolean = False
|
||||||
): IVariableDeclarationNode; static;
|
): IVariableDeclarationNode; static;
|
||||||
|
|
||||||
class function Assign(
|
class function Assign(const ATarget, AValue: IAstNode; const AStaticType: IStaticType = nil): IAssignmentNode; static;
|
||||||
const AIdentifier: IIdentifierNode;
|
|
||||||
const AValue: IAstNode;
|
|
||||||
const AStaticType: IStaticType = nil
|
|
||||||
): IAssignmentNode; static;
|
|
||||||
class function AssignResult(const AValue: IAstNode): IAssignmentNode; static; deprecated;
|
class function AssignResult(const AValue: IAstNode): IAssignmentNode; static; deprecated;
|
||||||
class function Indexer(const ABase: IAstNode; const AIndex: IAstNode; const AStaticType: IStaticType = nil): IIndexerNode; static;
|
class function Indexer(const ABase: IAstNode; const AIndex: IAstNode; const AStaticType: IStaticType = nil): IIndexerNode; static;
|
||||||
class function MemberAccess(
|
class function MemberAccess(
|
||||||
@@ -249,23 +245,17 @@ type
|
|||||||
function AsFunctionCall: IFunctionCallNode; override;
|
function AsFunctionCall: IFunctionCallNode; override;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// ... (Other node definitions unchanged) ...
|
|
||||||
TVariableDeclarationNode = class(TAstTypedNode, IVariableDeclarationNode)
|
TVariableDeclarationNode = class(TAstTypedNode, IVariableDeclarationNode)
|
||||||
private
|
private
|
||||||
FIdentifier: IIdentifierNode;
|
|
||||||
FInitializer: IAstNode;
|
FInitializer: IAstNode;
|
||||||
|
FTarget: IAstNode;
|
||||||
FIsBoxed: Boolean;
|
FIsBoxed: Boolean;
|
||||||
function GetIdentifier: IIdentifierNode;
|
function GetTarget: IAstNode;
|
||||||
function GetInitializer: IAstNode;
|
function GetInitializer: IAstNode;
|
||||||
function GetIsBoxed: Boolean;
|
function GetIsBoxed: Boolean;
|
||||||
function GetKind: TAstNodeKind; override;
|
function GetKind: TAstNodeKind; override;
|
||||||
public
|
public
|
||||||
constructor Create(
|
constructor Create(const ATarget: IAstNode; AInitializer: IAstNode; const AStaticType: IStaticType; const AIsBoxed: Boolean);
|
||||||
const AIdentifier: IIdentifierNode;
|
|
||||||
AInitializer: IAstNode;
|
|
||||||
const AStaticType: IStaticType;
|
|
||||||
const AIsBoxed: Boolean
|
|
||||||
);
|
|
||||||
function Accept(const Visitor: IAstVisitor): TDataValue; override;
|
function Accept(const Visitor: IAstVisitor): TDataValue; override;
|
||||||
function AsVariableDeclaration: IVariableDeclarationNode; override;
|
function AsVariableDeclaration: IVariableDeclarationNode; override;
|
||||||
end;
|
end;
|
||||||
@@ -440,16 +430,16 @@ type
|
|||||||
|
|
||||||
TAssignmentNode = class(TAstTypedNode, IAssignmentNode)
|
TAssignmentNode = class(TAstTypedNode, IAssignmentNode)
|
||||||
private
|
private
|
||||||
FIdentifier: IIdentifierNode;
|
FTarget: IAstNode;
|
||||||
FValue: IAstNode;
|
FValue: IAstNode;
|
||||||
function GetIdentifier: IIdentifierNode;
|
function GetTarget: IAstNode;
|
||||||
function GetValue: IAstNode;
|
function GetValue: IAstNode;
|
||||||
function GetKind: TAstNodeKind; override;
|
function GetKind: TAstNodeKind; override;
|
||||||
public
|
public
|
||||||
constructor Create(const AIdentifier: IIdentifierNode; const AValue: IAstNode; const AStaticType: IStaticType);
|
constructor Create(const ATarget: IAstNode; const AValue: IAstNode; const AStaticType: IStaticType);
|
||||||
function Accept(const Visitor: IAstVisitor): TDataValue; override;
|
function Accept(const Visitor: IAstVisitor): TDataValue; override;
|
||||||
function AsAssignment: IAssignmentNode; override;
|
function AsAssignment: IAssignmentNode; override;
|
||||||
property Identifier: IIdentifierNode read FIdentifier;
|
property Target: IAstNode read FTarget;
|
||||||
property Value: IAstNode read FValue;
|
property Value: IAstNode read FValue;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -619,15 +609,11 @@ begin
|
|||||||
);
|
);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
class function TAst.Assign(
|
class function TAst.Assign(const ATarget, AValue: IAstNode; const AStaticType: IStaticType = nil): IAssignmentNode;
|
||||||
const AIdentifier: IIdentifierNode;
|
|
||||||
const AValue: IAstNode;
|
|
||||||
const AStaticType: IStaticType = nil
|
|
||||||
): IAssignmentNode;
|
|
||||||
begin
|
begin
|
||||||
Result :=
|
Result :=
|
||||||
TAssignmentNode.Create(
|
TAssignmentNode.Create(
|
||||||
AIdentifier,
|
ATarget,
|
||||||
AValue,
|
AValue,
|
||||||
if AStaticType <> nil then AStaticType
|
if AStaticType <> nil then AStaticType
|
||||||
else TTypes.Unknown
|
else TTypes.Unknown
|
||||||
@@ -884,7 +870,7 @@ begin
|
|||||||
end;
|
end;
|
||||||
|
|
||||||
class function TAst.VarDecl(
|
class function TAst.VarDecl(
|
||||||
const AIdentifier: IIdentifierNode;
|
const AIdentifier: IAstNode;
|
||||||
AInitializer: IAstNode = nil;
|
AInitializer: IAstNode = nil;
|
||||||
const AStaticType: IStaticType = nil;
|
const AStaticType: IStaticType = nil;
|
||||||
const AIsBoxed: Boolean = False
|
const AIsBoxed: Boolean = False
|
||||||
@@ -1614,14 +1600,14 @@ end;
|
|||||||
{ TVariableDeclarationNode }
|
{ TVariableDeclarationNode }
|
||||||
|
|
||||||
constructor TVariableDeclarationNode.Create(
|
constructor TVariableDeclarationNode.Create(
|
||||||
const AIdentifier: IIdentifierNode;
|
const ATarget: IAstNode;
|
||||||
AInitializer: IAstNode;
|
AInitializer: IAstNode;
|
||||||
const AStaticType: IStaticType;
|
const AStaticType: IStaticType;
|
||||||
const AIsBoxed: Boolean
|
const AIsBoxed: Boolean
|
||||||
);
|
);
|
||||||
begin
|
begin
|
||||||
inherited Create(AStaticType);
|
inherited Create(AStaticType);
|
||||||
FIdentifier := AIdentifier;
|
FTarget := ATarget;
|
||||||
FInitializer := AInitializer;
|
FInitializer := AInitializer;
|
||||||
FIsBoxed := AIsBoxed;
|
FIsBoxed := AIsBoxed;
|
||||||
end;
|
end;
|
||||||
@@ -1636,9 +1622,9 @@ begin
|
|||||||
Result := Self;
|
Result := Self;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TVariableDeclarationNode.GetIdentifier: IIdentifierNode;
|
function TVariableDeclarationNode.GetTarget: IAstNode;
|
||||||
begin
|
begin
|
||||||
Result := FIdentifier;
|
Result := FTarget;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TVariableDeclarationNode.GetInitializer: IAstNode;
|
function TVariableDeclarationNode.GetInitializer: IAstNode;
|
||||||
@@ -1658,10 +1644,10 @@ end;
|
|||||||
|
|
||||||
{ TAssignmentNode }
|
{ TAssignmentNode }
|
||||||
|
|
||||||
constructor TAssignmentNode.Create(const AIdentifier: IIdentifierNode; const AValue: IAstNode; const AStaticType: IStaticType);
|
constructor TAssignmentNode.Create(const ATarget: IAstNode; const AValue: IAstNode; const AStaticType: IStaticType);
|
||||||
begin
|
begin
|
||||||
inherited Create(AStaticType);
|
inherited Create(AStaticType);
|
||||||
FIdentifier := AIdentifier;
|
FTarget := ATarget;
|
||||||
FValue := AValue;
|
FValue := AValue;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -1675,9 +1661,9 @@ begin
|
|||||||
Result := Self;
|
Result := Self;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TAssignmentNode.GetIdentifier: IIdentifierNode;
|
function TAssignmentNode.GetTarget: IAstNode;
|
||||||
begin
|
begin
|
||||||
Result := FIdentifier;
|
Result := FTarget;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TAssignmentNode.GetValue: IAstNode;
|
function TAssignmentNode.GetValue: IAstNode;
|
||||||
|
|||||||
@@ -1,411 +0,0 @@
|
|||||||
unit TestDataTypes.JSON;
|
|
||||||
|
|
||||||
interface
|
|
||||||
|
|
||||||
uses
|
|
||||||
System.JSON,
|
|
||||||
DUnitX.TestFramework;
|
|
||||||
|
|
||||||
type
|
|
||||||
[TestFixture]
|
|
||||||
[IgnoreMemoryLeaks]
|
|
||||||
TTestDataTypesJSON = class
|
|
||||||
private
|
|
||||||
FJson: TJSONValue;
|
|
||||||
public
|
|
||||||
[Setup]
|
|
||||||
procedure SetUp;
|
|
||||||
[TearDown]
|
|
||||||
procedure TearDown;
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
procedure TestVoid;
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
[TestCase('Positive', '12345')]
|
|
||||||
[TestCase('Negative', '-54321')]
|
|
||||||
[TestCase('Zero', '0')]
|
|
||||||
procedure TestOrdinal(const AValue: Int64);
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
[TestCase('Positive', '123.456')]
|
|
||||||
[TestCase('Negative', '-543.21')]
|
|
||||||
[TestCase('Zero', '0.0')]
|
|
||||||
procedure TestFloat(const AValue: Double);
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
[TestCase('Simple', 'Hello World')]
|
|
||||||
[TestCase('Empty', '')]
|
|
||||||
[TestCase('SpecialChars', 'äöüß#+*?%&/()=')]
|
|
||||||
procedure TestText(const AValue: string);
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
procedure TestTimestamp;
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
[TestCase('Scale4_Positive', '1234567,4,123.4567')]
|
|
||||||
[TestCase('Scale2_Negative', '-987,2,-9.87')]
|
|
||||||
[TestCase('Scale5_Small', '123,5,0.00123')]
|
|
||||||
[TestCase('Scale0_Int', '123,0,123')]
|
|
||||||
[TestCase('Scale2_Zero', '0,2,0.00')]
|
|
||||||
[TestCase('Scale0_Zero', '0,0,0')]
|
|
||||||
procedure TestDecimal(const AValue: Int64; AScale: Integer; const AExpectedJsonString: string);
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
procedure TestEnum;
|
|
||||||
[Test]
|
|
||||||
procedure TestRecord;
|
|
||||||
[Test]
|
|
||||||
procedure TestTuple;
|
|
||||||
[Test]
|
|
||||||
procedure TestArray;
|
|
||||||
[Test]
|
|
||||||
procedure TestVector;
|
|
||||||
[Test]
|
|
||||||
procedure TestMethodFails;
|
|
||||||
[Test]
|
|
||||||
procedure TestNullValue;
|
|
||||||
end;
|
|
||||||
|
|
||||||
implementation
|
|
||||||
|
|
||||||
uses
|
|
||||||
System.SysUtils,
|
|
||||||
System.DateUtils,
|
|
||||||
System.Math,
|
|
||||||
Myc.Data.Types,
|
|
||||||
Myc.Data.Types.JSON;
|
|
||||||
|
|
||||||
{ TTestDataTypesJSON }
|
|
||||||
|
|
||||||
procedure TTestDataTypesJSON.SetUp;
|
|
||||||
begin
|
|
||||||
FJson := nil;
|
|
||||||
end;
|
|
||||||
|
|
||||||
procedure TTestDataTypesJSON.TearDown;
|
|
||||||
begin
|
|
||||||
FJson.Free;
|
|
||||||
end;
|
|
||||||
|
|
||||||
procedure TTestDataTypesJSON.TestVoid;
|
|
||||||
var
|
|
||||||
voidType: TDataType.TVoid;
|
|
||||||
voidValue: TDataType.TVoid.TValue;
|
|
||||||
deserializedValue: TDataType.TValue;
|
|
||||||
begin
|
|
||||||
// Setup
|
|
||||||
voidType := TDataType.Void;
|
|
||||||
voidValue := voidType.Value;
|
|
||||||
|
|
||||||
// Serialize
|
|
||||||
FJson := TDataJsonConverter.ValueToJson(voidValue);
|
|
||||||
Assert.IsNotNull(FJson, 'JSON value should not be nil');
|
|
||||||
Assert.IsTrue(FJson is TJSONNull, 'Void should serialize to JSON Null');
|
|
||||||
|
|
||||||
// Deserialize
|
|
||||||
deserializedValue := TDataJsonConverter.JsonToValue(FJson, voidType);
|
|
||||||
Assert.IsNotNull(deserializedValue.DataValue, 'Deserialized value should not be nil');
|
|
||||||
Assert.AreEqual(dkVoid, deserializedValue.DataType.Kind, 'Deserialized type kind should be dkVoid');
|
|
||||||
end;
|
|
||||||
|
|
||||||
procedure TTestDataTypesJSON.TestOrdinal(const AValue: Int64);
|
|
||||||
var
|
|
||||||
ordType: TDataType.TOrdinal;
|
|
||||||
ordValue: TDataType.TOrdinal.TValue;
|
|
||||||
deserializedValue: TDataType.TValue;
|
|
||||||
begin
|
|
||||||
// Setup
|
|
||||||
ordType := TDataType.Ordinal;
|
|
||||||
ordValue := ordType.CreateValue(AValue);
|
|
||||||
|
|
||||||
// Serialize
|
|
||||||
FJson := TDataJsonConverter.ValueToJson(ordValue);
|
|
||||||
Assert.IsTrue(FJson is TJSONNumber, 'Ordinal should serialize to JSON Number');
|
|
||||||
Assert.AreEqual(AValue, (FJson as TJSONNumber).AsInt64, 'Serialized value mismatch');
|
|
||||||
|
|
||||||
// Deserialize
|
|
||||||
deserializedValue := TDataJsonConverter.JsonToValue(FJson, ordType);
|
|
||||||
Assert.AreEqual(dkOrdinal, deserializedValue.DataType.Kind, 'Deserialized type kind should be dkOrdinal');
|
|
||||||
Assert.AreEqual(AValue, deserializedValue.AsOrdinal.Value, 'Deserialized value mismatch');
|
|
||||||
end;
|
|
||||||
|
|
||||||
procedure TTestDataTypesJSON.TestFloat(const AValue: Double);
|
|
||||||
var
|
|
||||||
floatType: TDataType.TFloat;
|
|
||||||
floatValue: TDataType.TFloat.TValue;
|
|
||||||
deserializedValue: TDataType.TValue;
|
|
||||||
begin
|
|
||||||
// Setup
|
|
||||||
floatType := TDataType.Float;
|
|
||||||
floatValue := floatType.CreateValue(AValue);
|
|
||||||
|
|
||||||
// Serialize
|
|
||||||
FJson := TDataJsonConverter.ValueToJson(floatValue);
|
|
||||||
Assert.IsTrue(FJson is TJSONNumber, 'Float should serialize to JSON Number');
|
|
||||||
Assert.AreEqual(AValue, (FJson as TJSONNumber).AsDouble, 1E-9, 'Serialized value mismatch');
|
|
||||||
|
|
||||||
// Deserialize
|
|
||||||
deserializedValue := TDataJsonConverter.JsonToValue(FJson, floatType);
|
|
||||||
Assert.AreEqual(dkFloat, deserializedValue.DataType.Kind, 'Deserialized type kind should be dkFloat');
|
|
||||||
Assert.AreEqual(AValue, deserializedValue.AsFloat.Value, 1E-9, 'Deserialized value mismatch');
|
|
||||||
end;
|
|
||||||
|
|
||||||
procedure TTestDataTypesJSON.TestText(const AValue: string);
|
|
||||||
var
|
|
||||||
textType: TDataType.TText;
|
|
||||||
textValue: TDataType.TText.TValue;
|
|
||||||
deserializedValue: TDataType.TValue;
|
|
||||||
begin
|
|
||||||
// Setup
|
|
||||||
textType := TDataType.Text;
|
|
||||||
textValue := textType.CreateValue(AValue);
|
|
||||||
|
|
||||||
// Serialize
|
|
||||||
FJson := TDataJsonConverter.ValueToJson(textValue);
|
|
||||||
Assert.IsTrue(FJson is TJSONString, 'Text should serialize to JSON String');
|
|
||||||
Assert.AreEqual(AValue, (FJson as TJSONString).Value, 'Serialized value mismatch');
|
|
||||||
|
|
||||||
// Deserialize
|
|
||||||
deserializedValue := TDataJsonConverter.JsonToValue(FJson, textType);
|
|
||||||
Assert.AreEqual(dkText, deserializedValue.DataType.Kind, 'Deserialized type kind should be dkText');
|
|
||||||
Assert.AreEqual(AValue, deserializedValue.AsText.Value, 'Deserialized value mismatch');
|
|
||||||
end;
|
|
||||||
|
|
||||||
procedure TTestDataTypesJSON.TestTimestamp;
|
|
||||||
var
|
|
||||||
tsType: TDataType.TTimestamp;
|
|
||||||
tsValue: TDataType.TTimestamp.TValue;
|
|
||||||
deserializedValue: TDataType.TValue;
|
|
||||||
testVal: TDateTime;
|
|
||||||
begin
|
|
||||||
// Setup
|
|
||||||
testVal := EncodeDateTime(2025, 8, 27, 17, 30, 15, 500);
|
|
||||||
tsType := TDataType.Timestamp;
|
|
||||||
tsValue := tsType.CreateValue(testVal);
|
|
||||||
|
|
||||||
// Serialize
|
|
||||||
FJson := TDataJsonConverter.ValueToJson(tsValue);
|
|
||||||
Assert.IsTrue(FJson is TJSONString, 'Timestamp should serialize to JSON String');
|
|
||||||
Assert.AreEqual(DateToISO8601(testVal, true), (FJson as TJSONString).Value, 'Serialized value mismatch');
|
|
||||||
|
|
||||||
// Deserialize
|
|
||||||
deserializedValue := TDataJsonConverter.JsonToValue(FJson, tsType);
|
|
||||||
Assert.AreEqual(dkTimestamp, deserializedValue.DataType.Kind, 'Deserialized type kind should be dkTimestamp');
|
|
||||||
Assert.IsTrue(CompareDateTime(testVal, deserializedValue.AsTimestamp.Value) = 0, 'Deserialized value mismatch');
|
|
||||||
end;
|
|
||||||
|
|
||||||
procedure TTestDataTypesJSON.TestDecimal(const AValue: Int64; AScale: Integer; const AExpectedJsonString: string);
|
|
||||||
var
|
|
||||||
decType: TDataType.TDecimal;
|
|
||||||
decValue: TDataType.TDecimal.TValue;
|
|
||||||
deserializedValue: TDataType.TValue;
|
|
||||||
begin
|
|
||||||
// Setup
|
|
||||||
decType := TDataType.DecimalOf(AScale);
|
|
||||||
decValue := decType.CreateValue(AValue);
|
|
||||||
|
|
||||||
// Serialize
|
|
||||||
FJson := TDataJsonConverter.ValueToJson(decValue);
|
|
||||||
Assert.IsTrue(FJson is TJSONString, 'Decimal should serialize to JSON String');
|
|
||||||
Assert.AreEqual(AExpectedJsonString, (FJson as TJSONString).Value, 'Serialized string value mismatch');
|
|
||||||
|
|
||||||
// Deserialize
|
|
||||||
deserializedValue := TDataJsonConverter.JsonToValue(FJson, decType);
|
|
||||||
Assert.AreEqual(dkDecimal, deserializedValue.DataType.Kind, 'Deserialized type kind should be dkDecimal');
|
|
||||||
Assert.AreEqual(AValue, deserializedValue.AsDecimal.Value, 'Deserialized value mismatch');
|
|
||||||
Assert.AreEqual(AScale, deserializedValue.AsDecimal.DataType.Scale, 'Deserialized scale mismatch');
|
|
||||||
|
|
||||||
// Test Deserialization from Number (compatibility)
|
|
||||||
FJson.Free; // Free previous value before creating new one
|
|
||||||
FJson := TJSONNumber.Create(AValue / Power(10, AScale));
|
|
||||||
deserializedValue := TDataJsonConverter.JsonToValue(FJson, decType);
|
|
||||||
Assert.AreEqual(AValue, deserializedValue.AsDecimal.Value, 'Deserialized value from number mismatch');
|
|
||||||
end;
|
|
||||||
|
|
||||||
procedure TTestDataTypesJSON.TestEnum;
|
|
||||||
var
|
|
||||||
enumType: TDataType.TEnum;
|
|
||||||
enumValue: TDataType.TEnum.TValue;
|
|
||||||
deserializedValue: TDataType.TValue;
|
|
||||||
begin
|
|
||||||
// Setup
|
|
||||||
enumType := TDataType.EnumOf('MyEnum', ['Red', 'Green', 'Blue']);
|
|
||||||
enumValue := enumType.CreateValue(1); // Green
|
|
||||||
|
|
||||||
// Serialize
|
|
||||||
FJson := TDataJsonConverter.ValueToJson(enumValue);
|
|
||||||
Assert.IsTrue(FJson is TJSONString, 'Enum should serialize to JSON String');
|
|
||||||
Assert.AreEqual('Green', (FJson as TJSONString).Value, 'Serialized value mismatch');
|
|
||||||
|
|
||||||
// Deserialize
|
|
||||||
deserializedValue := TDataJsonConverter.JsonToValue(FJson, enumType);
|
|
||||||
Assert.AreEqual(dkEnum, deserializedValue.DataType.Kind, 'Deserialized type kind should be dkEnum');
|
|
||||||
Assert.AreEqual(1, deserializedValue.AsEnum.Value, 'Deserialized value mismatch');
|
|
||||||
end;
|
|
||||||
|
|
||||||
procedure TTestDataTypesJSON.TestRecord;
|
|
||||||
var
|
|
||||||
recordType: TDataType.TRecord;
|
|
||||||
idVal: TDataType.TOrdinal.TValue;
|
|
||||||
nameVal: TDataType.TText.TValue;
|
|
||||||
recordValue: TDataType.TRecord.TValue;
|
|
||||||
deserializedValue: TDataType.TValue;
|
|
||||||
begin
|
|
||||||
// Setup
|
|
||||||
recordType := TDataType.RecordOf([TDataRecordField.Create('ID', TDataType.Ordinal), TDataRecordField.Create('Name', TDataType.Text)]);
|
|
||||||
idVal := TDataType.Ordinal.CreateValue(99);
|
|
||||||
nameVal := TDataType.Text.CreateValue('TestRecord');
|
|
||||||
recordValue := recordType.CreateValue([idVal, nameVal]);
|
|
||||||
|
|
||||||
// Serialize
|
|
||||||
FJson := TDataJsonConverter.ValueToJson(recordValue);
|
|
||||||
Assert.IsTrue(FJson is TJSONObject, 'Record should serialize to JSON Object');
|
|
||||||
|
|
||||||
// Deserialize
|
|
||||||
deserializedValue := TDataJsonConverter.JsonToValue(FJson, recordType);
|
|
||||||
Assert.AreEqual(dkRecord, deserializedValue.DataType.Kind, 'Deserialized type kind should be dkRecord');
|
|
||||||
var desRecord := deserializedValue.AsRecord;
|
|
||||||
Assert.AreEqual(Int64(99), desRecord.Items[0].AsOrdinal.Value, 'Deserialized record field ID mismatch');
|
|
||||||
Assert.AreEqual('TestRecord', desRecord.Items[1].AsText.Value, 'Deserialized record field Name mismatch');
|
|
||||||
end;
|
|
||||||
|
|
||||||
procedure TTestDataTypesJSON.TestTuple;
|
|
||||||
var
|
|
||||||
tupleType: TDataType.TTuple;
|
|
||||||
idVal: TDataType.TOrdinal.TValue;
|
|
||||||
nameVal: TDataType.TText.TValue;
|
|
||||||
tupleValue: TDataType.TTuple.TValue;
|
|
||||||
deserializedValue: TDataType.TValue;
|
|
||||||
begin
|
|
||||||
// Setup
|
|
||||||
tupleType := TDataType.TupleOf([TDataType.Ordinal, TDataType.Text]);
|
|
||||||
idVal := TDataType.Ordinal.CreateValue(101);
|
|
||||||
nameVal := TDataType.Text.CreateValue('TestTuple');
|
|
||||||
tupleValue := tupleType.CreateValue([idVal, nameVal]);
|
|
||||||
|
|
||||||
// Serialize
|
|
||||||
FJson := TDataJsonConverter.ValueToJson(tupleValue);
|
|
||||||
Assert.IsTrue(FJson is TJSONArray, 'Tuple should serialize to JSON Array');
|
|
||||||
|
|
||||||
// Deserialize
|
|
||||||
deserializedValue := TDataJsonConverter.JsonToValue(FJson, tupleType);
|
|
||||||
Assert.AreEqual(dkTuple, deserializedValue.DataType.Kind, 'Deserialized type kind should be dkTuple');
|
|
||||||
var desTuple := deserializedValue.AsTuple;
|
|
||||||
Assert.AreEqual(Int64(101), desTuple.Items[0].AsOrdinal.Value, 'Deserialized tuple item 0 mismatch');
|
|
||||||
Assert.AreEqual('TestTuple', desTuple.Items[1].AsText.Value, 'Deserialized tuple item 1 mismatch');
|
|
||||||
end;
|
|
||||||
|
|
||||||
procedure TTestDataTypesJSON.TestArray;
|
|
||||||
var
|
|
||||||
arrayType: TDataType.TArray;
|
|
||||||
val1, val2: TDataType.TText.TValue;
|
|
||||||
arrayValue: TDataType.TArray.TValue;
|
|
||||||
deserializedValue: TDataType.TValue;
|
|
||||||
begin
|
|
||||||
// Setup
|
|
||||||
arrayType := TDataType.ArrayOf(TDataType.Text);
|
|
||||||
val1 := TDataType.Text.CreateValue('A');
|
|
||||||
val2 := TDataType.Text.CreateValue('B');
|
|
||||||
arrayValue := arrayType.CreateValue([val1, val2]);
|
|
||||||
|
|
||||||
// Serialize
|
|
||||||
FJson := TDataJsonConverter.ValueToJson(arrayValue);
|
|
||||||
Assert.IsTrue(FJson is TJSONArray, 'Array should serialize to JSON Array');
|
|
||||||
|
|
||||||
// Deserialize
|
|
||||||
deserializedValue := TDataJsonConverter.JsonToValue(FJson, arrayType);
|
|
||||||
Assert.AreEqual(dkArray, deserializedValue.DataType.Kind, 'Deserialized type kind should be dkArray');
|
|
||||||
var desArray := deserializedValue.AsArray;
|
|
||||||
Assert.AreEqual(2, desArray.ElementCount, 'Deserialized array element count mismatch');
|
|
||||||
Assert.AreEqual('A', desArray.Items[0].AsText.Value, 'Deserialized array item 0 mismatch');
|
|
||||||
Assert.AreEqual('B', desArray.Items[1].AsText.Value, 'Deserialized array item 1 mismatch');
|
|
||||||
end;
|
|
||||||
|
|
||||||
procedure TTestDataTypesJSON.TestVector;
|
|
||||||
var
|
|
||||||
vectorType: TDataType.TVector;
|
|
||||||
val1, val2: TDataType.TOrdinal.TValue;
|
|
||||||
vectorValue: TDataType.TVector.TValue;
|
|
||||||
deserializedValue: TDataType.TValue;
|
|
||||||
jsonArray: TJSONArray;
|
|
||||||
begin
|
|
||||||
// Setup
|
|
||||||
vectorType := TDataType.VectorOf(TDataType.Ordinal, 2);
|
|
||||||
val1 := TDataType.Ordinal.CreateValue(10);
|
|
||||||
val2 := TDataType.Ordinal.CreateValue(20);
|
|
||||||
vectorValue := vectorType.CreateValue([val1, val2]);
|
|
||||||
|
|
||||||
// Serialize
|
|
||||||
FJson := TDataJsonConverter.ValueToJson(vectorValue);
|
|
||||||
Assert.IsTrue(FJson is TJSONArray, 'Vector should serialize to JSON Array');
|
|
||||||
|
|
||||||
// Deserialize
|
|
||||||
deserializedValue := TDataJsonConverter.JsonToValue(FJson, vectorType);
|
|
||||||
Assert.AreEqual(dkVector, deserializedValue.DataType.Kind, 'Deserialized type kind should be dkVector');
|
|
||||||
var desVector := deserializedValue.AsVector;
|
|
||||||
Assert.AreEqual(2, desVector.ElementCount, 'Deserialized vector element count mismatch');
|
|
||||||
Assert.AreEqual(Int64(10), desVector.Items[0].AsOrdinal.Value, 'Deserialized vector item 0 mismatch');
|
|
||||||
Assert.AreEqual(Int64(20), desVector.Items[1].AsOrdinal.Value, 'Deserialized vector item 1 mismatch');
|
|
||||||
|
|
||||||
// Test wrong size deserialization
|
|
||||||
jsonArray := nil;
|
|
||||||
try
|
|
||||||
jsonArray := TJSONArray.Create;
|
|
||||||
jsonArray.Add(1);
|
|
||||||
Assert.WillRaise(
|
|
||||||
procedure begin TDataJsonConverter.JsonToValue(jsonArray, vectorType); end,
|
|
||||||
EConvertError,
|
|
||||||
'Deserializing a JSON array with wrong size for a vector should raise an exception'
|
|
||||||
);
|
|
||||||
finally
|
|
||||||
jsonArray.Free;
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
|
|
||||||
procedure TTestDataTypesJSON.TestMethodFails;
|
|
||||||
var
|
|
||||||
methodType: TDataType.TMethod;
|
|
||||||
methodValue: TDataType.TMethod.TValue;
|
|
||||||
begin
|
|
||||||
// Setup
|
|
||||||
methodType := TDataType.MethodOf(TDataType.Ordinal, TDataType.Ordinal);
|
|
||||||
methodValue := methodType.CreateValue(function(const AValue: TDataType.TValue): TDataType.TValue begin Result := AValue; end);
|
|
||||||
|
|
||||||
// Test Serialization
|
|
||||||
Assert.WillRaise(
|
|
||||||
procedure begin TDataJsonConverter.ValueToJson(methodValue).Free; end,
|
|
||||||
ENotSupportedException,
|
|
||||||
'Serializing a method should raise an exception'
|
|
||||||
);
|
|
||||||
|
|
||||||
// Test Deserialization
|
|
||||||
FJson := TJSONNull.Create;
|
|
||||||
Assert.WillRaise(
|
|
||||||
procedure begin TDataJsonConverter.JsonToValue(FJson, methodType); end,
|
|
||||||
ENotSupportedException,
|
|
||||||
'Deserializing a method should raise an exception'
|
|
||||||
);
|
|
||||||
end;
|
|
||||||
|
|
||||||
procedure TTestDataTypesJSON.TestNullValue;
|
|
||||||
var
|
|
||||||
value: TDataType.TValue;
|
|
||||||
deserializedValue: TDataType.TValue;
|
|
||||||
begin
|
|
||||||
// Setup
|
|
||||||
value := TDataType.Void.Value;
|
|
||||||
|
|
||||||
// Serialize
|
|
||||||
FJson := TDataJsonConverter.ValueToJson(value);
|
|
||||||
Assert.IsTrue(FJson is TJSONNull, 'nil IDataValue should serialize to JSON Null');
|
|
||||||
|
|
||||||
// Deserialize
|
|
||||||
deserializedValue := TDataJsonConverter.JsonToValue(FJson, TDataType.Void);
|
|
||||||
Assert.IsTrue(deserializedValue.DataType.Kind = dkVoid, 'JSON Null should deserialize to a TValue with a nil DataValue');
|
|
||||||
end;
|
|
||||||
|
|
||||||
initialization
|
|
||||||
TDUnitX.RegisterTestFixture(TTestDataTypesJSON);
|
|
||||||
|
|
||||||
end.
|
|
||||||
@@ -1,676 +0,0 @@
|
|||||||
unit TestDataTypes;
|
|
||||||
|
|
||||||
interface
|
|
||||||
|
|
||||||
uses
|
|
||||||
DUnitX.TestFramework;
|
|
||||||
|
|
||||||
type
|
|
||||||
[TestFixture]
|
|
||||||
[IgnoreMemoryLeaks]
|
|
||||||
TTestDataTypes = class
|
|
||||||
public
|
|
||||||
[Test]
|
|
||||||
procedure TestRecords;
|
|
||||||
[Test]
|
|
||||||
procedure TestRecords_Generic;
|
|
||||||
[Test]
|
|
||||||
procedure TestTuples;
|
|
||||||
[Test]
|
|
||||||
procedure TestTuples_Generic;
|
|
||||||
[Test]
|
|
||||||
procedure TestArrays;
|
|
||||||
[Test]
|
|
||||||
procedure TestArrays_OptimizedAndGeneric;
|
|
||||||
[Test]
|
|
||||||
procedure TestTexts;
|
|
||||||
[Test]
|
|
||||||
procedure TestTexts_OptimizedEmpty;
|
|
||||||
[Test]
|
|
||||||
procedure TestFloats_OptimizedAndGeneric;
|
|
||||||
[Test]
|
|
||||||
procedure TestTimestamps;
|
|
||||||
[Test]
|
|
||||||
procedure TestEnums;
|
|
||||||
[Test]
|
|
||||||
procedure TestAsString;
|
|
||||||
[Test]
|
|
||||||
procedure TestAsTValue;
|
|
||||||
[Test]
|
|
||||||
procedure TestMethods;
|
|
||||||
end;
|
|
||||||
|
|
||||||
implementation
|
|
||||||
|
|
||||||
uses
|
|
||||||
System.SysUtils,
|
|
||||||
System.Math,
|
|
||||||
System.Rtti,
|
|
||||||
System.TypInfo,
|
|
||||||
Myc.Data.Types,
|
|
||||||
Myc.Data.Types.RTTI,
|
|
||||||
Myc.Data.Types.JSON;
|
|
||||||
|
|
||||||
procedure TTestDataTypes.TestRecords;
|
|
||||||
var
|
|
||||||
intType: TDataType.TOrdinal;
|
|
||||||
floatType: TDataType.TFloat;
|
|
||||||
personType1, personType2, otherType: TDataType.TRecord;
|
|
||||||
personValue: TDataType.TRecord.TValue;
|
|
||||||
idValue: TDataType.TOrdinal.TValue;
|
|
||||||
floatValue: TDataType.TFloat.TValue;
|
|
||||||
begin
|
|
||||||
// This test covers the optimized implementation for 2 fields.
|
|
||||||
// --- 1. Setup: Define base types and a record structure ---
|
|
||||||
intType := TDataType.Ordinal;
|
|
||||||
floatType := TDataType.Float;
|
|
||||||
|
|
||||||
// --- 2. Test Type Creation and Caching ---
|
|
||||||
personType1 := TDataType.RecordOf([TDataRecordField.Create('ID', intType), TDataRecordField.Create('Value', floatType)]);
|
|
||||||
|
|
||||||
// Assertions for the created type
|
|
||||||
Assert.IsNotNull(IDataRecordType(personType1), 'RecordType should be created');
|
|
||||||
Assert.AreEqual(2, personType1.FieldCount, 'FieldCount should be 2');
|
|
||||||
Assert.AreEqual('ID', personType1.Fields[0].Name, 'First field name should be ID');
|
|
||||||
Assert.AreSame(IDataType(intType), personType1.Fields[0].DataType, 'First field type should be Integer');
|
|
||||||
Assert.AreEqual('Value', personType1.Fields[1].Name, 'Second field name should be Value');
|
|
||||||
Assert.AreSame(IDataType(floatType), personType1.Fields[1].DataType, 'Second field type should be Float');
|
|
||||||
Assert.AreEqual(0, personType1.IndexOf('ID'), 'IndexOf ID should be 0');
|
|
||||||
Assert.AreEqual(1, personType1.IndexOf('Value'), 'IndexOf Value should be 1');
|
|
||||||
Assert.AreEqual('Record<ID: Integer, Value: Float>', TDataType(personType1).Name, 'Type name should match expected format');
|
|
||||||
|
|
||||||
// Test if the same definition returns the same cached instance
|
|
||||||
personType2 := TDataType.RecordOf([TDataRecordField.Create('ID', intType), TDataRecordField.Create('Value', floatType)]);
|
|
||||||
Assert.AreSame(IDataRecordType(personType1), IDataRecordType(personType2), 'Types should be cached and return the same instance');
|
|
||||||
|
|
||||||
// Test if a different definition returns a new instance
|
|
||||||
otherType := TDataType.RecordOf([TDataRecordField.Create('ID', intType), TDataRecordField.Create('Data', floatType)]);
|
|
||||||
Assert.AreNotSame(IDataRecordType(personType1), IDataRecordType(otherType), 'Different definitions should result in different types');
|
|
||||||
|
|
||||||
// --- 3. Test Value Creation and Access ---
|
|
||||||
personValue := personType1.CreateValue([TDataType.Ordinal.CreateValue(123), TDataType.Float.CreateValue(45.67)]);
|
|
||||||
|
|
||||||
Assert.IsNotNull(IDataRecordValue(personValue), 'RecordValue should be created');
|
|
||||||
Assert.AreSame(IDataRecordType(personType1), IDataRecordType(personValue.DataType), 'Value should have the correct data type');
|
|
||||||
|
|
||||||
// Access by index
|
|
||||||
idValue := TDataType.TValue(personValue.Items[0]).AsOrdinal;
|
|
||||||
Assert.AreEqual(Int64(123), idValue.Value, 'Value at index 0 is incorrect');
|
|
||||||
floatValue := TDataType.TValue(personValue.Items[1]).AsFloat;
|
|
||||||
Assert.AreEqual(45.67, floatValue.Value, 'Value at index 1 is incorrect');
|
|
||||||
|
|
||||||
// Access by name
|
|
||||||
floatValue := TDataType.TValue(personValue.Items[personType1.IndexOf('Value')]).AsFloat;
|
|
||||||
Assert.AreEqual(45.67, floatValue.Value, 'Value accessed by name is incorrect');
|
|
||||||
|
|
||||||
// --- 4. Test Validation and Error Handling ---
|
|
||||||
// Test for duplicate field names during type creation
|
|
||||||
Assert.WillRaise(
|
|
||||||
procedure begin TDataType.RecordOf([TDataRecordField.Create('ID', intType), TDataRecordField.Create('ID', floatType)]); end,
|
|
||||||
EArgumentException,
|
|
||||||
'Duplicate field names should raise an exception'
|
|
||||||
);
|
|
||||||
|
|
||||||
// Test for wrong number of items during value creation
|
|
||||||
Assert.WillRaise(
|
|
||||||
procedure begin personType1.CreateValue([TDataType.Ordinal.CreateValue(99)]); end,
|
|
||||||
EArgumentException,
|
|
||||||
'Wrong number of items should raise exception'
|
|
||||||
);
|
|
||||||
|
|
||||||
// Test for wrong item type during value creation
|
|
||||||
Assert.WillRaise(
|
|
||||||
procedure
|
|
||||||
begin
|
|
||||||
// Passing Float instead of Integer for the first item
|
|
||||||
personType1.CreateValue([TDataType.Float.CreateValue(1.0), TDataType.Float.CreateValue(2.0)]);
|
|
||||||
end,
|
|
||||||
EArgumentException,
|
|
||||||
'Wrong item type should raise exception'
|
|
||||||
);
|
|
||||||
end;
|
|
||||||
|
|
||||||
procedure TTestDataTypes.TestRecords_Generic;
|
|
||||||
var
|
|
||||||
dataType: TDataType.TRecord;
|
|
||||||
dataValue: TDataType.TRecord.TValue;
|
|
||||||
begin
|
|
||||||
// Test the generic implementation for records with > 2 fields.
|
|
||||||
dataType :=
|
|
||||||
TDataType.RecordOf(
|
|
||||||
[
|
|
||||||
TDataRecordField.Create('A', TDataType.Ordinal),
|
|
||||||
TDataRecordField.Create('B', TDataType.Text),
|
|
||||||
TDataRecordField.Create('C', TDataType.Float)
|
|
||||||
]
|
|
||||||
);
|
|
||||||
dataValue :=
|
|
||||||
dataType.CreateValue([TDataType.Ordinal.CreateValue(1), TDataType.Text.CreateValue('two'), TDataType.Float.CreateValue(3.0)]);
|
|
||||||
|
|
||||||
Assert.IsNotNull(IDataRecordValue(dataValue), 'Generic record value should be created');
|
|
||||||
Assert.AreEqual(3, dataType.FieldCount, 'Generic record should have 3 fields');
|
|
||||||
|
|
||||||
var itemValue0 := dataValue.Items[0].AsOrdinal;
|
|
||||||
Assert.AreEqual(Int64(1), itemValue0.Value, 'Field A is incorrect');
|
|
||||||
var itemValue1 := dataValue.Items[1].AsText;
|
|
||||||
Assert.AreEqual('two', itemValue1.Value, 'Field B is incorrect');
|
|
||||||
var itemValue2 := dataValue.Items[2].AsFloat;
|
|
||||||
Assert.AreEqual(3.0, itemValue2.Value, 'Field C is incorrect');
|
|
||||||
|
|
||||||
Assert.AreEqual('<A: 1, B: two, C: 3>', IDataValue(dataValue).AsString, 'Generic record AsString is incorrect');
|
|
||||||
end;
|
|
||||||
|
|
||||||
procedure TTestDataTypes.TestTuples;
|
|
||||||
var
|
|
||||||
intValue: TDataType.TOrdinal.TValue;
|
|
||||||
floatValue: TDataType.TFloat.TValue;
|
|
||||||
tuple1, tuple2, tuple3: TDataType.TTuple.TValue;
|
|
||||||
type1, type2, type3: IDataType; // Keep as interface to test AreSame on the raw interface pointer
|
|
||||||
begin
|
|
||||||
// This test covers optimized implementations for 1 and 2 elements.
|
|
||||||
// --- 1. Setup: Create some values ---
|
|
||||||
intValue := TDataType.Ordinal.CreateValue(123);
|
|
||||||
floatValue := TDataType.Float.CreateValue(45.67);
|
|
||||||
|
|
||||||
// --- 2. Test Value Creation and basic properties ---
|
|
||||||
tuple1 := TDataType.TupleOf([intValue, floatValue]);
|
|
||||||
|
|
||||||
Assert.IsNotNull(IDataTupleValue(tuple1), 'Tuple value should be created');
|
|
||||||
Assert.AreEqual(2, tuple1.ItemCount, 'ItemCount should be on the value');
|
|
||||||
Assert.AreSame(IDataValue(intValue), tuple1.Items[0], 'Item at index 0 is incorrect');
|
|
||||||
Assert.AreSame(IDataValue(floatValue), tuple1.Items[1], 'Item at index 1 is incorrect');
|
|
||||||
|
|
||||||
// --- 3. Test Singleton Type Behavior ---
|
|
||||||
// Create more tuples with different structures
|
|
||||||
tuple2 := TDataType.TupleOf([TDataType.Ordinal.CreateValue(99), TDataType.Float.CreateValue(1.1)]);
|
|
||||||
tuple3 := TDataType.TupleOf([intValue]);
|
|
||||||
|
|
||||||
// Access the DataType via the underlying interface
|
|
||||||
type1 := tuple1.DataType;
|
|
||||||
type2 := tuple2.DataType;
|
|
||||||
type3 := tuple3.DataType;
|
|
||||||
|
|
||||||
Assert.IsNotNull(type1, 'DataType interface should be accessible');
|
|
||||||
Assert.AreEqual('Tuple<Integer, Float>', type1.Name, 'The type name for all tuples should be Tuple');
|
|
||||||
end;
|
|
||||||
|
|
||||||
procedure TTestDataTypes.TestTuples_Generic;
|
|
||||||
var
|
|
||||||
tupleValue: TDataType.TTuple.TValue;
|
|
||||||
item: TDataType.TOrdinal.TValue;
|
|
||||||
begin
|
|
||||||
// Test the generic implementation for tuples with > 5 elements.
|
|
||||||
tupleValue :=
|
|
||||||
TDataType.TupleOf(
|
|
||||||
[
|
|
||||||
TDataType.Ordinal.CreateValue(1),
|
|
||||||
TDataType.Ordinal.CreateValue(2),
|
|
||||||
TDataType.Ordinal.CreateValue(3),
|
|
||||||
TDataType.Ordinal.CreateValue(4),
|
|
||||||
TDataType.Ordinal.CreateValue(5),
|
|
||||||
TDataType.Ordinal.CreateValue(6)
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
Assert.IsNotNull(IDataTupleValue(tupleValue), 'Generic tuple value should be created');
|
|
||||||
Assert.AreEqual(6, tupleValue.ItemCount, 'Generic tuple should have 6 items');
|
|
||||||
|
|
||||||
item := TDataType.TValue(tupleValue.Items[5]).AsOrdinal;
|
|
||||||
Assert.AreEqual(Int64(6), item.Value, 'Last item is incorrect');
|
|
||||||
Assert.AreEqual('(1, 2, 3, 4, 5, 6)', IDataValue(tupleValue).AsString, 'Generic tuple AsString is incorrect');
|
|
||||||
end;
|
|
||||||
|
|
||||||
procedure TTestDataTypes.TestArrays;
|
|
||||||
var
|
|
||||||
intType: TDataType.TOrdinal;
|
|
||||||
floatType: TDataType.TFloat;
|
|
||||||
intArrayType1, intArrayType2: TDataType.TArray;
|
|
||||||
floatArrayType: TDataType.TArray;
|
|
||||||
arrayValue: TDataType.TArray.TValue;
|
|
||||||
v1, v2: TDataType.TOrdinal.TValue;
|
|
||||||
item: TDataType.TOrdinal.TValue;
|
|
||||||
begin
|
|
||||||
// --- 1. Setup ---
|
|
||||||
intType := TDataType.Ordinal;
|
|
||||||
floatType := TDataType.Float;
|
|
||||||
|
|
||||||
// --- 2. Test Type Creation and Caching ---
|
|
||||||
intArrayType1 := TDataType.ArrayOf(intType);
|
|
||||||
|
|
||||||
Assert.IsNotNull(IDataArrayType(intArrayType1), 'ArrayType should be created');
|
|
||||||
Assert.AreSame(IDataType(intType), intArrayType1.ElementType, 'ElementType should be Integer');
|
|
||||||
Assert.AreEqual('Array<Integer>', TDataType(intArrayType1).Name, 'Type name should be Array<Integer>');
|
|
||||||
|
|
||||||
// Test caching
|
|
||||||
intArrayType2 := TDataType.ArrayOf(intType);
|
|
||||||
Assert.AreSame(IDataArrayType(intArrayType1), IDataArrayType(intArrayType2), 'Array types should be cached');
|
|
||||||
|
|
||||||
// Test uniqueness
|
|
||||||
floatArrayType := TDataType.ArrayOf(floatType);
|
|
||||||
Assert.AreNotSame(
|
|
||||||
IDataArrayType(intArrayType1),
|
|
||||||
IDataArrayType(floatArrayType),
|
|
||||||
'Different element types should result in different array types'
|
|
||||||
);
|
|
||||||
Assert.AreEqual('Array<Float>', TDataType(floatArrayType).Name, 'Type name should be Array<Float>');
|
|
||||||
|
|
||||||
// --- 3. Test Value Creation and Access ---
|
|
||||||
v1 := TDataType.Ordinal.CreateValue(10);
|
|
||||||
v2 := TDataType.Ordinal.CreateValue(20);
|
|
||||||
arrayValue := intArrayType1.CreateValue([v1, v2]);
|
|
||||||
|
|
||||||
Assert.IsNotNull(IDataArrayValue(arrayValue), 'ArrayValue should be created');
|
|
||||||
Assert.AreEqual(2, arrayValue.ElementCount, 'ElementCount should be 2');
|
|
||||||
|
|
||||||
// Access items and check values
|
|
||||||
item := TDataType.TValue(arrayValue.Items[0]).AsOrdinal;
|
|
||||||
Assert.AreEqual(Int64(10), item.Value, 'Item at index 0 is incorrect');
|
|
||||||
item := TDataType.TValue(arrayValue.Items[1]).AsOrdinal;
|
|
||||||
Assert.AreEqual(Int64(20), item.Value, 'Item at index 1 is incorrect');
|
|
||||||
|
|
||||||
// --- 4. Test Validation and Error Handling ---
|
|
||||||
// Test creating an array with a nil element type
|
|
||||||
Assert.WillRaise(procedure begin TDataType.ArrayOf(nil); end, EArgumentException, 'Nil element type should raise an exception');
|
|
||||||
|
|
||||||
// Test creating a value with an incorrect element type (homogeneity check)
|
|
||||||
Assert.WillRaise(
|
|
||||||
procedure
|
|
||||||
begin
|
|
||||||
// Try to add a float value to an Array<Integer>
|
|
||||||
intArrayType1.CreateValue([v1, TDataType.Float.CreateValue(3.14)]);
|
|
||||||
end,
|
|
||||||
EArgumentException,
|
|
||||||
'Wrong element type should raise an exception'
|
|
||||||
);
|
|
||||||
end;
|
|
||||||
|
|
||||||
procedure TTestDataTypes.TestArrays_OptimizedAndGeneric;
|
|
||||||
var
|
|
||||||
arrayType: TDataType.TArray;
|
|
||||||
empty1, empty2, single, generic: TDataType.TArray.TValue;
|
|
||||||
item: TDataType.TOrdinal.TValue;
|
|
||||||
begin
|
|
||||||
arrayType := TDataType.ArrayOf(TDataType.Ordinal);
|
|
||||||
|
|
||||||
// Test optimized path for 0 elements (cached singleton per type)
|
|
||||||
empty1 := arrayType.CreateValue([]);
|
|
||||||
empty2 := arrayType.CreateValue([]);
|
|
||||||
Assert.IsNotNull(IDataArrayValue(empty1), 'Empty array value should be created');
|
|
||||||
Assert.AreSame(IDataArrayValue(empty1), IDataArrayValue(empty2), 'Empty array value should be cached and reused');
|
|
||||||
Assert.AreEqual(0, empty1.ElementCount, 'Empty array should have 0 elements');
|
|
||||||
Assert.AreEqual('[]', IDataValue(empty1).AsString, 'Empty array AsString is incorrect');
|
|
||||||
|
|
||||||
// Test optimized path for 1 element
|
|
||||||
single := arrayType.CreateValue([TDataType.Ordinal.CreateValue(42)]);
|
|
||||||
Assert.IsNotNull(IDataArrayValue(single), 'Single element array should be created');
|
|
||||||
Assert.AreEqual(1, single.ElementCount, 'Single element array should have 1 element');
|
|
||||||
|
|
||||||
item := TDataType.TValue(single.Items[0]).AsOrdinal;
|
|
||||||
Assert.AreEqual(Int64(42), item.Value, 'Single element value is incorrect');
|
|
||||||
Assert.AreEqual('[42]', IDataValue(single).AsString, 'Single element array AsString is incorrect');
|
|
||||||
|
|
||||||
// Test generic path for > 1 elements
|
|
||||||
generic :=
|
|
||||||
arrayType.CreateValue([TDataType.Ordinal.CreateValue(1), TDataType.Ordinal.CreateValue(2), TDataType.Ordinal.CreateValue(3)]);
|
|
||||||
Assert.IsNotNull(IDataArrayValue(generic), 'Generic array should be created');
|
|
||||||
Assert.AreEqual(3, generic.ElementCount, 'Generic array should have 3 elements');
|
|
||||||
Assert.AreEqual('[1, 2, 3]', IDataValue(generic).AsString, 'Generic array AsString is incorrect');
|
|
||||||
end;
|
|
||||||
|
|
||||||
procedure TTestDataTypes.TestTexts;
|
|
||||||
var
|
|
||||||
textType: TDataType.TText;
|
|
||||||
textValue1, textValue2, castedValue: TDataType.TText.TValue;
|
|
||||||
dataValue: IDataValue;
|
|
||||||
begin
|
|
||||||
// --- 1. Test Type Creation ---
|
|
||||||
textType := TDataType.Text;
|
|
||||||
|
|
||||||
Assert.IsNotNull(IDataTextType(textType), 'TextType should be created');
|
|
||||||
Assert.AreEqual('Text', TDataType(textType).Name, 'Type name should be Text');
|
|
||||||
Assert.AreEqual(dkText, TDataType(textType).Kind, 'Type kind should be dkText');
|
|
||||||
|
|
||||||
// --- 2. Test Value Creation and Access ---
|
|
||||||
textValue1 := TDataType.Text.CreateValue('Hello World');
|
|
||||||
|
|
||||||
Assert.IsNotNull(IDataTextValue(textValue1), 'TextValue should be created');
|
|
||||||
Assert.AreSame(IDataType(textType), IDataType(textValue1.DataType), 'Value should have the correct data type');
|
|
||||||
Assert.AreEqual('Hello World', textValue1.Value, 'Value should be ''Hello World''');
|
|
||||||
|
|
||||||
// Test another text value
|
|
||||||
textValue2 := TDataType.Text.CreateValue('Delphi');
|
|
||||||
Assert.AreEqual('Delphi', textValue2.Value, 'Value should be ''Delphi''');
|
|
||||||
|
|
||||||
// --- 3. Test AsText casting ---
|
|
||||||
dataValue := TDataType.Text.CreateValue('Test Text');
|
|
||||||
castedValue := TDataType.TValue(dataValue).AsText;
|
|
||||||
Assert.AreEqual('Test Text', castedValue.Value, 'AsText should return correct value');
|
|
||||||
|
|
||||||
// Test invalid cast
|
|
||||||
dataValue := TDataType.Ordinal.CreateValue(123);
|
|
||||||
Assert.WillRaise(
|
|
||||||
procedure begin TDataType.TValue(dataValue).AsText; end,
|
|
||||||
EInvalidCast,
|
|
||||||
'Casting non-text to AsText should raise EInvalidCast'
|
|
||||||
);
|
|
||||||
end;
|
|
||||||
|
|
||||||
procedure TTestDataTypes.TestTexts_OptimizedEmpty;
|
|
||||||
var
|
|
||||||
empty1, empty2: TDataType.TText.TValue;
|
|
||||||
begin
|
|
||||||
// Test the singleton implementation for the empty string.
|
|
||||||
empty1 := TDataType.Text.CreateValue('');
|
|
||||||
empty2 := TDataType.Text.CreateValue('');
|
|
||||||
|
|
||||||
Assert.IsNotNull(IDataTextValue(empty1), 'Empty text value should be created');
|
|
||||||
Assert.AreSame(IDataTextValue(empty1), IDataTextValue(empty2), 'Empty text value should be a singleton');
|
|
||||||
Assert.AreEqual('', empty1.Value, 'Value of empty text should be empty');
|
|
||||||
Assert.AreEqual('', IDataValue(empty1).AsString, 'AsString of empty text should be empty');
|
|
||||||
end;
|
|
||||||
|
|
||||||
procedure TTestDataTypes.TestFloats_OptimizedAndGeneric;
|
|
||||||
var
|
|
||||||
zero1, zero2, nan1, nan2, generic: TDataType.TFloat.TValue;
|
|
||||||
begin
|
|
||||||
// Test singleton for 0.0
|
|
||||||
zero1 := TDataType.Float.CreateValue(0.0);
|
|
||||||
zero2 := TDataType.Float.CreateValue(0.0);
|
|
||||||
Assert.IsNotNull(IDataFloatValue(zero1), 'Zero float should be created');
|
|
||||||
Assert.AreSame(IDataFloatValue(zero1), IDataFloatValue(zero2), 'Zero float should be a singleton');
|
|
||||||
Assert.AreEqual(0.0, zero1.Value, 'Value of zero float is incorrect');
|
|
||||||
|
|
||||||
// Test singleton for NaN
|
|
||||||
nan1 := TDataType.Float.CreateValue(NaN);
|
|
||||||
nan2 := TDataType.Float.CreateValue(NaN);
|
|
||||||
Assert.IsNotNull(IDataFloatValue(nan1), 'NaN float should be created');
|
|
||||||
Assert.AreSame(IDataFloatValue(nan1), IDataFloatValue(nan2), 'NaN float should be a singleton');
|
|
||||||
Assert.IsTrue(IsNaN(nan1.Value), 'Value of NaN float should be NaN');
|
|
||||||
Assert.AreEqual('NaN', IDataValue(nan1).AsString, 'NaN AsString is incorrect');
|
|
||||||
|
|
||||||
// Test generic path
|
|
||||||
generic := TDataType.Float.CreateValue(123.45);
|
|
||||||
Assert.IsNotNull(IDataFloatValue(generic), 'Generic float should be created');
|
|
||||||
Assert.AreNotSame(IDataFloatValue(zero1), IDataFloatValue(generic), 'Generic float should not be the zero singleton');
|
|
||||||
Assert.AreNotSame(IDataFloatValue(nan1), IDataFloatValue(generic), 'Generic float should not be the NaN singleton');
|
|
||||||
Assert.AreEqual(123.45, generic.Value, 'Value of generic float is incorrect');
|
|
||||||
end;
|
|
||||||
|
|
||||||
procedure TTestDataTypes.TestTimestamps;
|
|
||||||
var
|
|
||||||
tsType: TDataType.TTimestamp;
|
|
||||||
tsValue1, tsValue2, castedValue: TDataType.TTimestamp.TValue;
|
|
||||||
dataValue: IDataValue;
|
|
||||||
now: TDateTime;
|
|
||||||
begin
|
|
||||||
// --- 1. Test Type Creation ---
|
|
||||||
tsType := TDataType.Timestamp;
|
|
||||||
|
|
||||||
Assert.IsNotNull(IDataTimestampType(tsType), 'TimestampType should be created');
|
|
||||||
Assert.AreEqual('Timestamp', TDataType(tsType).Name, 'Type name should be Timestamp');
|
|
||||||
Assert.AreEqual(dkTimestamp, TDataType(tsType).Kind, 'Type kind should be dkTimestamp');
|
|
||||||
|
|
||||||
// --- 2. Test Value Creation and Access ---
|
|
||||||
now := System.Sysutils.Now;
|
|
||||||
tsValue1 := TDataType.Timestamp.CreateValue(now);
|
|
||||||
|
|
||||||
Assert.IsNotNull(IDataTimestampValue(tsValue1), 'TimestampValue should be created');
|
|
||||||
Assert.AreSame(IDataType(tsType), IDataType(tsValue1.DataType), 'Value should have the correct data type');
|
|
||||||
Assert.AreEqual(now, tsValue1.Value, 'Value should be the same');
|
|
||||||
|
|
||||||
// Test another text value
|
|
||||||
tsValue2 := TDataType.Timestamp.CreateValue(Date);
|
|
||||||
Assert.AreEqual(Date, tsValue2.Value, 'Value should be the same');
|
|
||||||
|
|
||||||
// --- 3. Test AsTimestamp casting ---
|
|
||||||
dataValue := TDataType.Timestamp.CreateValue(now);
|
|
||||||
castedValue := TDataType.TValue(dataValue).AsTimestamp;
|
|
||||||
Assert.AreEqual(now, castedValue.Value, 'AsTimestamp should return correct value');
|
|
||||||
|
|
||||||
// Test invalid cast
|
|
||||||
dataValue := TDataType.Ordinal.CreateValue(123);
|
|
||||||
Assert.WillRaise(
|
|
||||||
procedure begin TDataType.TValue(dataValue).AsTimestamp; end,
|
|
||||||
EInvalidCast,
|
|
||||||
'Casting non-timestamp to AsTimestamp should raise EInvalidCast'
|
|
||||||
);
|
|
||||||
end;
|
|
||||||
|
|
||||||
procedure TTestDataTypes.TestEnums;
|
|
||||||
var
|
|
||||||
colorType: TDataType.TEnum;
|
|
||||||
red, green, blue: TDataType.TEnum.TValue;
|
|
||||||
begin
|
|
||||||
// --- 1. Test Type Creation ---
|
|
||||||
colorType := TDataType.EnumOf('Color', ['Red', 'Green', 'Blue']);
|
|
||||||
|
|
||||||
Assert.IsNotNull(IDataEnumType(colorType), 'EnumType should be created');
|
|
||||||
Assert.AreEqual('Color', TDataType(colorType).Name, 'Name should be Color');
|
|
||||||
Assert.AreEqual(dkEnum, TDataType(colorType).Kind, 'Kind should be dkEnum');
|
|
||||||
Assert.AreEqual(3, colorType.IdentifierCount, 'IdentifierCount should be 3');
|
|
||||||
Assert.AreEqual('Red', colorType.Identifiers[0], 'Identifier at index 0 should be Red');
|
|
||||||
Assert.AreEqual('Green', colorType.Identifiers[1], 'Identifier at index 1 should be Green');
|
|
||||||
Assert.AreEqual('Blue', colorType.Identifiers[2], 'Identifier at index 2 should be Blue');
|
|
||||||
Assert.AreEqual(1, colorType.IndexOf('Green'), 'IndexOf Green should be 1');
|
|
||||||
Assert.AreEqual(-1, colorType.IndexOf('Yellow'), 'IndexOf Yellow should be -1');
|
|
||||||
|
|
||||||
// --- 2. Test Value Creation and Access ---
|
|
||||||
red := colorType.CreateValue(0);
|
|
||||||
green := colorType.CreateValue('Green');
|
|
||||||
blue := colorType.CreateValue(2);
|
|
||||||
|
|
||||||
Assert.IsNotNull(IDataEnumValue(red), 'EnumValue from index should be created');
|
|
||||||
Assert.AreSame(IDataType(colorType), IDataType(red.DataType), 'Red should have the correct data type');
|
|
||||||
Assert.AreEqual(0, red.Value, 'Value of Red should be 0');
|
|
||||||
Assert.AreEqual('Red', IDataValue(red).AsString, 'AsText of Red should be Red');
|
|
||||||
|
|
||||||
Assert.IsNotNull(IDataEnumValue(green), 'EnumValue from identifier should be created');
|
|
||||||
Assert.AreSame(IDataType(colorType), IDataType(green.DataType), 'Green should have the correct data type');
|
|
||||||
Assert.AreEqual(1, green.Value, 'Value of Green should be 1');
|
|
||||||
Assert.AreEqual('Green', IDataValue(green).AsString, 'AsText of Green should be Green');
|
|
||||||
|
|
||||||
Assert.AreEqual(2, blue.Value, 'Value of Blue should be 2');
|
|
||||||
|
|
||||||
// --- 3. Test Validation and Error Handling ---
|
|
||||||
// Test creating a type with duplicate identifiers
|
|
||||||
Assert.WillRaise(
|
|
||||||
procedure begin TDataType.EnumOf('Fails', ['A', 'B', 'A']); end,
|
|
||||||
EArgumentException,
|
|
||||||
'Duplicate identifiers should raise an exception'
|
|
||||||
);
|
|
||||||
|
|
||||||
// Test creating a value with an invalid index
|
|
||||||
Assert.WillRaise(procedure begin colorType.CreateValue(3); end, EArgumentException, 'Invalid index should raise an exception');
|
|
||||||
Assert.WillRaise(procedure begin colorType.CreateValue(-1); end, EArgumentException, 'Negative index should raise an exception');
|
|
||||||
|
|
||||||
// Test creating a value with an unknown identifier
|
|
||||||
Assert.WillRaise(
|
|
||||||
procedure begin colorType.CreateValue('Yellow'); end,
|
|
||||||
EArgumentException,
|
|
||||||
'Unknown identifier should raise an exception'
|
|
||||||
);
|
|
||||||
end;
|
|
||||||
|
|
||||||
procedure TTestDataTypes.TestAsString;
|
|
||||||
var
|
|
||||||
ordinalValue: TDataType.TOrdinal.TValue;
|
|
||||||
floatValue: TDataType.TFloat.TValue;
|
|
||||||
textValue: TDataType.TText.TValue;
|
|
||||||
tsValue: TDataType.TTimestamp.TValue;
|
|
||||||
enumValue: TDataType.TEnum.TValue;
|
|
||||||
arrayValue: TDataType.TArray.TValue;
|
|
||||||
recordValue: TDataType.TRecord.TValue;
|
|
||||||
tupleValue: TDataType.TTuple.TValue;
|
|
||||||
colorType: TDataType.TEnum;
|
|
||||||
personType: TDataType.TRecord;
|
|
||||||
intArrayType: TDataType.TArray;
|
|
||||||
now: TDateTime;
|
|
||||||
begin
|
|
||||||
// Ordinal
|
|
||||||
ordinalValue := TDataType.Ordinal.CreateValue(123);
|
|
||||||
Assert.AreEqual('123', IDataValue(ordinalValue).AsString, 'Ordinal AsString incorrect');
|
|
||||||
|
|
||||||
// Float
|
|
||||||
floatValue := TDataType.Float.CreateValue(45.67);
|
|
||||||
Assert.AreEqual(FloatToStr(45.67), IDataValue(floatValue).AsString, 'Float AsString incorrect');
|
|
||||||
|
|
||||||
// Text
|
|
||||||
textValue := TDataType.Text.CreateValue('Hello');
|
|
||||||
Assert.AreEqual('Hello', IDataValue(textValue).AsString, 'Text AsString incorrect');
|
|
||||||
|
|
||||||
// Timestamp
|
|
||||||
now := System.Sysutils.Now;
|
|
||||||
tsValue := TDataType.Timestamp.CreateValue(now);
|
|
||||||
Assert.AreEqual(DateTimeToStr(now), IDataValue(tsValue).AsString, 'Timestamp AsString incorrect');
|
|
||||||
|
|
||||||
// Enum
|
|
||||||
colorType := TDataType.EnumOf('Color', ['Red', 'Green', 'Blue']);
|
|
||||||
enumValue := colorType.CreateValue('Green');
|
|
||||||
Assert.AreEqual('Green', IDataValue(enumValue).AsString, 'Enum AsString incorrect');
|
|
||||||
|
|
||||||
// Array
|
|
||||||
intArrayType := TDataType.ArrayOf(TDataType.Ordinal);
|
|
||||||
arrayValue := intArrayType.CreateValue([TDataType.Ordinal.CreateValue(1), TDataType.Ordinal.CreateValue(2)]);
|
|
||||||
Assert.AreEqual('[1, 2]', IDataValue(arrayValue).AsString, 'Array AsString incorrect');
|
|
||||||
|
|
||||||
// Record
|
|
||||||
personType := TDataType.RecordOf([TDataRecordField.Create('ID', TDataType.Ordinal), TDataRecordField.Create('Name', TDataType.Text)]);
|
|
||||||
recordValue := personType.CreateValue([TDataType.Ordinal.CreateValue(1), TDataType.Text.CreateValue('Bob')]);
|
|
||||||
Assert.AreEqual('<ID: 1, Name: Bob>', IDataValue(recordValue).AsString, 'Record AsString incorrect');
|
|
||||||
|
|
||||||
// Tuple
|
|
||||||
tupleValue := TDataType.TupleOf([TDataType.Ordinal.CreateValue(10), TDataType.Text.CreateValue('Tuple')]);
|
|
||||||
Assert.AreEqual('(10, Tuple)', IDataValue(tupleValue).AsString, 'Tuple AsString incorrect');
|
|
||||||
end;
|
|
||||||
|
|
||||||
procedure TTestDataTypes.TestAsTValue;
|
|
||||||
var
|
|
||||||
dataValue: TDataType.TValue; // Keep as generic interface to test AsTValue
|
|
||||||
now: TDateTime;
|
|
||||||
colorType: TDataType.TEnum;
|
|
||||||
begin
|
|
||||||
// This test is specifically for the IDataValue.AsTValue method. No changes needed.
|
|
||||||
// --- Ordinal ---
|
|
||||||
dataValue := TDataType.Ordinal.CreateValue(123);
|
|
||||||
var tv := AsTValue(dataValue);
|
|
||||||
Assert.IsFalse(tv.IsEmpty, 'Ordinal TValue should not be empty');
|
|
||||||
Assert.AreEqual(tkInt64, tv.Kind, 'Ordinal TValue kind should be tkInt64');
|
|
||||||
Assert.AreEqual(Int64(123), tv.AsInt64, 'Ordinal TValue content is incorrect');
|
|
||||||
|
|
||||||
// --- Float ---
|
|
||||||
dataValue := TDataType.Float.CreateValue(45.67);
|
|
||||||
tv := AsTValue(dataValue);
|
|
||||||
Assert.IsFalse(tv.IsEmpty, 'Float TValue should not be empty');
|
|
||||||
Assert.AreEqual(tkFloat, tv.Kind, 'Float TValue kind should be tkFloat');
|
|
||||||
Assert.AreEqual(45.67, tv.AsExtended, 'Float TValue content is incorrect');
|
|
||||||
|
|
||||||
// --- Text ---
|
|
||||||
dataValue := TDataType.Text.CreateValue('Hello');
|
|
||||||
tv := AsTValue(dataValue);
|
|
||||||
Assert.IsFalse(tv.IsEmpty, 'Text TValue should not be empty');
|
|
||||||
Assert.AreEqual(tkUString, tv.Kind, 'Text TValue kind should be tkUString');
|
|
||||||
Assert.AreEqual('Hello', tv.AsString, 'Text TValue content is incorrect');
|
|
||||||
|
|
||||||
// --- Timestamp ---
|
|
||||||
now := System.SysUtils.Now;
|
|
||||||
dataValue := TDataType.Timestamp.CreateValue(now);
|
|
||||||
tv := AsTValue(dataValue);
|
|
||||||
Assert.IsFalse(tv.IsEmpty, 'Timestamp TValue should not be empty');
|
|
||||||
Assert.AreEqual(tkFloat, tv.Kind, 'Timestamp TValue kind is incorrect');
|
|
||||||
Assert.AreEqual(now, tv.AsType<TDateTime>, 'Timestamp TValue content is incorrect');
|
|
||||||
|
|
||||||
// --- Enum ---
|
|
||||||
colorType := TDataType.EnumOf('Color', ['Red', 'Green', 'Blue']);
|
|
||||||
dataValue := colorType.CreateValue('Green');
|
|
||||||
tv := AsTValue(dataValue);
|
|
||||||
Assert.IsFalse(tv.IsEmpty, 'Enum TValue should not be empty');
|
|
||||||
Assert.AreEqual(tkInteger, tv.Kind, 'Enum TValue kind should be tkInteger');
|
|
||||||
Assert.AreEqual(1, tv.AsInteger, 'Enum TValue content is incorrect');
|
|
||||||
end;
|
|
||||||
|
|
||||||
procedure TTestDataTypes.TestMethods;
|
|
||||||
var
|
|
||||||
ordinalType: TDataType.TOrdinal;
|
|
||||||
textType: TDataType.TText;
|
|
||||||
methodType1, methodType2, otherMethodType: TDataType.TMethod;
|
|
||||||
myFunc: TDataType.TMethod.TProc;
|
|
||||||
methodValue: TDataType.TMethod.TValue;
|
|
||||||
inputValue: TDataType.TOrdinal.TValue;
|
|
||||||
resultValue: TDataType.TValue;
|
|
||||||
textResult: TDataType.TText.TValue;
|
|
||||||
tv: TValue;
|
|
||||||
begin
|
|
||||||
// --- 1. Setup: Define base types ---
|
|
||||||
ordinalType := TDataType.Ordinal;
|
|
||||||
textType := TDataType.Text;
|
|
||||||
|
|
||||||
// --- 2. Test Type Creation and Caching ---
|
|
||||||
methodType1 := TDataType.MethodOf(ordinalType, textType);
|
|
||||||
|
|
||||||
// Assertions for the created type
|
|
||||||
Assert.IsNotNull(IDataMethodType(methodType1), 'MethodType should be created');
|
|
||||||
Assert.AreEqual(dkMethod, TDataType(methodType1).Kind, 'Kind should be dkMethod');
|
|
||||||
Assert.AreSame(IDataType(ordinalType), methodType1.ArgType, 'ArgType should be Ordinal');
|
|
||||||
Assert.AreSame(IDataType(textType), methodType1.ResultType, 'ResultType should be Text');
|
|
||||||
Assert.AreEqual('Method<Integer -> Text>', TDataType(methodType1).Name, 'Type name should match expected format');
|
|
||||||
|
|
||||||
// Test if the same definition returns the same cached instance
|
|
||||||
methodType2 := TDataType.MethodOf(ordinalType, textType);
|
|
||||||
Assert
|
|
||||||
.AreSame(IDataMethodType(methodType1), IDataMethodType(methodType2), 'Method types should be cached and return the same instance');
|
|
||||||
|
|
||||||
// Test if a different definition returns a new instance
|
|
||||||
otherMethodType := TDataType.MethodOf(textType, ordinalType);
|
|
||||||
Assert.AreNotSame(
|
|
||||||
IDataMethodType(methodType1),
|
|
||||||
IDataMethodType(otherMethodType),
|
|
||||||
'Different signatures should result in different types'
|
|
||||||
);
|
|
||||||
Assert.AreEqual('Method<Text -> Integer>', TDataType(otherMethodType).Name, 'Name of other method type is incorrect');
|
|
||||||
|
|
||||||
// --- 3. Test Value Creation ---
|
|
||||||
// Define a function that matches the signature Ordinal -> Text
|
|
||||||
myFunc :=
|
|
||||||
function(const AValue: TDataType.TValue): TDataType.TValue
|
|
||||||
var
|
|
||||||
ordinalValue: TDataType.TOrdinal.TValue;
|
|
||||||
val: Int64;
|
|
||||||
begin
|
|
||||||
ordinalValue := AValue.AsOrdinal;
|
|
||||||
val := ordinalValue.Value;
|
|
||||||
Result := TDataType.Text.CreateValue(val.ToString);
|
|
||||||
end;
|
|
||||||
|
|
||||||
methodValue := methodType1.CreateValue(myFunc);
|
|
||||||
Assert.IsNotNull(IDataMethodValue(methodValue), 'MethodValue should be created');
|
|
||||||
Assert.AreSame(IDataType(methodType1), IDataType(methodValue.DataType), 'Value should have the correct data type');
|
|
||||||
|
|
||||||
// --- 4. Test Execution (Simulated) ---
|
|
||||||
inputValue := TDataType.Ordinal.CreateValue(42);
|
|
||||||
// Execute the function retrieved from the value
|
|
||||||
resultValue := methodValue.Value(inputValue);
|
|
||||||
|
|
||||||
Assert.IsNotNull(IDataValue(resultValue), 'Execution result should not be nil');
|
|
||||||
Assert.AreSame(IDataType(textType), resultValue.DataType, 'Result value should have Text type');
|
|
||||||
|
|
||||||
textResult := resultValue.AsText;
|
|
||||||
Assert.AreEqual('42', textResult.Value, 'Result value content is incorrect');
|
|
||||||
|
|
||||||
// --- 5. Test AsString and AsTValue ---
|
|
||||||
Assert.AreEqual('<METHOD>', IDataValue(methodValue).AsString, 'Method AsString is incorrect');
|
|
||||||
|
|
||||||
tv := AsTValue(methodValue);
|
|
||||||
Assert.IsFalse(tv.IsEmpty, 'Method TValue should not be empty');
|
|
||||||
Assert.AreEqual(tkInterface, tv.Kind, 'TValue kind for a TMethodProc should be tkInterface, as it is a reference-to-function');
|
|
||||||
Assert.IsTrue(TypeInfo(TDataMethodProc) = tv.TypeInfo, 'TValue should hold TMethodProc type info');
|
|
||||||
|
|
||||||
// --- 6. Test Validation and Error Handling ---
|
|
||||||
// Test creating a value with a nil function
|
|
||||||
Assert.WillRaise(
|
|
||||||
procedure begin methodType1.CreateValue(nil); end,
|
|
||||||
EArgumentException,
|
|
||||||
'CreateValue with nil function should raise an exception'
|
|
||||||
);
|
|
||||||
end;
|
|
||||||
|
|
||||||
end.
|
|
||||||
@@ -0,0 +1,250 @@
|
|||||||
|
unit Test.Myc.Ast.Compiler.Macros;
|
||||||
|
|
||||||
|
interface
|
||||||
|
|
||||||
|
uses
|
||||||
|
DUnitX.TestFramework,
|
||||||
|
System.SysUtils,
|
||||||
|
System.Classes,
|
||||||
|
Myc.Ast,
|
||||||
|
Myc.Ast.Nodes,
|
||||||
|
Myc.Ast.Script,
|
||||||
|
Myc.Ast.Environment,
|
||||||
|
Myc.Data.Value,
|
||||||
|
Myc.Data.Scalar;
|
||||||
|
|
||||||
|
type
|
||||||
|
[TestFixture]
|
||||||
|
TMacroTests = class
|
||||||
|
private
|
||||||
|
FEnv: TAstEnvironment;
|
||||||
|
|
||||||
|
function Run(const Script: string): TDataValue;
|
||||||
|
|
||||||
|
// Returns the raw AST root node after expansion
|
||||||
|
function ExpandToNode(const Script: string): IAstNode;
|
||||||
|
|
||||||
|
// Helper to recursively find the first variable declaration in an AST subtree
|
||||||
|
function FindFirstVarName(const Node: IAstNode): string;
|
||||||
|
|
||||||
|
public
|
||||||
|
[Setup]
|
||||||
|
procedure Setup;
|
||||||
|
[TearDown]
|
||||||
|
procedure TearDown;
|
||||||
|
|
||||||
|
// --- Basic Functionality ---
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[TestCase('Identity', '(defmacro id [x] `~x), (id 42), 42')]
|
||||||
|
[TestCase('Constant', '(defmacro c [] `100), (c), 100')]
|
||||||
|
[TestCase('SimpleAdd', '(defmacro add [a b] `(+ ~a ~b)), (add 10 20), 30')]
|
||||||
|
procedure Test_Basic_Expansion(const MacroDef, Call, ExpectedResult: string);
|
||||||
|
|
||||||
|
// --- Quasiquoting & Unquoting ---
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
procedure Test_Quasiquote_Literal;
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
procedure Test_Unquote_Complex_Expression;
|
||||||
|
|
||||||
|
// --- Hygiene (The Critical Checks) ---
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
procedure Test_Hygiene_GlobalSymbol_Preserved;
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
procedure Test_Hygiene_LocalVariable_Renamed;
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
procedure Test_Hygiene_Parameter_Renamed;
|
||||||
|
|
||||||
|
// --- Corner Cases & Errors ---
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
procedure Test_Error_Unquote_Outside_Quasiquote;
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
procedure Test_Macro_Argument_Count_Mismatch;
|
||||||
|
end;
|
||||||
|
|
||||||
|
implementation
|
||||||
|
|
||||||
|
{ TMacroTests }
|
||||||
|
|
||||||
|
procedure TMacroTests.Setup;
|
||||||
|
begin
|
||||||
|
FEnv := TAstEnvironment.Construct(nil);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TMacroTests.TearDown;
|
||||||
|
begin
|
||||||
|
FEnv := Default(TAstEnvironment);
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TMacroTests.Run(const Script: string): TDataValue;
|
||||||
|
var
|
||||||
|
node: IAstNode;
|
||||||
|
begin
|
||||||
|
node := TAstScript.Parse(Script);
|
||||||
|
Result := FEnv.Run(node);
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TMacroTests.ExpandToNode(const Script: string): IAstNode;
|
||||||
|
var
|
||||||
|
node: IAstNode;
|
||||||
|
begin
|
||||||
|
node := TAstScript.Parse(Script);
|
||||||
|
Result := FEnv.Environment.ExpandMacros(node);
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TMacroTests.FindFirstVarName(const Node: IAstNode): string;
|
||||||
|
var
|
||||||
|
block: IBlockExpressionNode;
|
||||||
|
child: IAstNode;
|
||||||
|
begin
|
||||||
|
Result := '';
|
||||||
|
|
||||||
|
if not Assigned(Node) then
|
||||||
|
Exit;
|
||||||
|
|
||||||
|
// Direct match
|
||||||
|
if Node.Kind = akVariableDeclaration then
|
||||||
|
Exit(Node.AsVariableDeclaration.Target.AsIdentifier.Name);
|
||||||
|
|
||||||
|
// Recursive search in Blocks
|
||||||
|
if Node.Kind = akBlockExpression then
|
||||||
|
begin
|
||||||
|
block := Node.AsBlockExpression;
|
||||||
|
for child in block.Expressions do
|
||||||
|
begin
|
||||||
|
Result := FindFirstVarName(child);
|
||||||
|
if Result <> '' then
|
||||||
|
Exit;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
// Recursive search in MacroExpansion wrapper
|
||||||
|
if Node.Kind = akMacroExpansion then
|
||||||
|
begin
|
||||||
|
Result := FindFirstVarName(Node.AsMacroExpansion.ExpandedBody);
|
||||||
|
if Result <> '' then
|
||||||
|
Exit;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TMacroTests.Test_Basic_Expansion(const MacroDef, Call, ExpectedResult: string);
|
||||||
|
var
|
||||||
|
script: string;
|
||||||
|
res: TDataValue;
|
||||||
|
begin
|
||||||
|
script := Format('(do %s %s)', [MacroDef, Call]);
|
||||||
|
res := Run(script);
|
||||||
|
Assert.AreEqual(Trim(ExpectedResult), res.ToString);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TMacroTests.Test_Quasiquote_Literal;
|
||||||
|
var
|
||||||
|
script: string;
|
||||||
|
res: TDataValue;
|
||||||
|
begin
|
||||||
|
// We execute this to verify it returns a valid AST structure (represented as TDataValue)
|
||||||
|
// Since we can't easily inspect TDataValue(IAstNode) without casting in the test,
|
||||||
|
// we just ensure it runs without error and returns something that is NOT void.
|
||||||
|
script :=
|
||||||
|
'''
|
||||||
|
(do
|
||||||
|
(def print (fn [msg] msg))
|
||||||
|
(defmacro get-code [] `(print "hello"))
|
||||||
|
(get-code))
|
||||||
|
''';
|
||||||
|
|
||||||
|
res := Run(script);
|
||||||
|
Assert.IsTrue(res.Kind = vkText, 'Quasiquote expansion should return a value');
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TMacroTests.Test_Unquote_Complex_Expression;
|
||||||
|
var
|
||||||
|
script: string;
|
||||||
|
res: TDataValue;
|
||||||
|
begin
|
||||||
|
script := '(do ' + ' (defmacro calc [op a b] `(~op ~a ~b)) ' + ' (calc + 5 (* 2 3)))';
|
||||||
|
|
||||||
|
res := Run(script);
|
||||||
|
Assert.AreEqual(Int64(11), res.AsScalar.Value.AsInt64);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TMacroTests.Test_Hygiene_GlobalSymbol_Preserved;
|
||||||
|
var
|
||||||
|
script: string;
|
||||||
|
res: TDataValue;
|
||||||
|
begin
|
||||||
|
// '+' is global. It MUST NOT be renamed.
|
||||||
|
script := '(do ' + ' (defmacro inc [x] `(+ ~x 1)) ' + ' (inc 41))';
|
||||||
|
|
||||||
|
try
|
||||||
|
res := Run(script);
|
||||||
|
Assert.AreEqual(Int64(42), res.AsScalar.Value.AsInt64);
|
||||||
|
except
|
||||||
|
on E: Exception do
|
||||||
|
Assert.Fail('Hygiene check failed. Global symbol likely renamed. Error: ' + E.Message);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TMacroTests.Test_Hygiene_LocalVariable_Renamed;
|
||||||
|
var
|
||||||
|
script: string;
|
||||||
|
expandedNode: IAstNode;
|
||||||
|
varName: string;
|
||||||
|
begin
|
||||||
|
// Local variables defined in a macro MUST be renamed.
|
||||||
|
script := '(do ' + ' (defmacro define-x [] `(do (def x 99) x)) ' + ' (define-x))';
|
||||||
|
|
||||||
|
// 1. Run first to ensure code validity (Execution logic)
|
||||||
|
var res := Run(script);
|
||||||
|
Assert.AreEqual(Int64(99), res.AsScalar.Value.AsInt64, 'Execution failed');
|
||||||
|
|
||||||
|
// 2. Inspect AST Object Graph (Structural logic)
|
||||||
|
expandedNode := ExpandToNode(script);
|
||||||
|
|
||||||
|
// We search the AST for the VariableDeclaration of "x"
|
||||||
|
varName := FindFirstVarName(expandedNode);
|
||||||
|
|
||||||
|
Assert.IsNotEmpty(varName, 'Variable declaration not found in AST');
|
||||||
|
Assert.AreNotEqual('x', varName, 'Hygiene Failure: Variable "x" was NOT renamed.');
|
||||||
|
Assert.IsTrue(varName.StartsWith('x#'), Format('Variable should be renamed to x#... but was "%s"', [varName]));
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TMacroTests.Test_Hygiene_Parameter_Renamed;
|
||||||
|
var
|
||||||
|
script: string;
|
||||||
|
res: TDataValue;
|
||||||
|
begin
|
||||||
|
// 'temp' inside macro should be renamed to 'temp#1' to not clash with global 'temp'.
|
||||||
|
script :=
|
||||||
|
'''
|
||||||
|
(do
|
||||||
|
(defmacro swap-bad [a b]
|
||||||
|
`(do (def temp ~a) (assign ~a ~b) (assign ~b temp)))
|
||||||
|
(def temp 10)
|
||||||
|
(def other 20)
|
||||||
|
(swap-bad temp other) ; Call with 'temp' passed as 'a'
|
||||||
|
temp) ; Should hold the swapped value (20)
|
||||||
|
''';
|
||||||
|
|
||||||
|
res := Run(script);
|
||||||
|
Assert.AreEqual(Int64(20), res.AsScalar.Value.AsInt64);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TMacroTests.Test_Error_Unquote_Outside_Quasiquote;
|
||||||
|
begin
|
||||||
|
Assert.WillRaise(procedure begin Run('(print ~1)'); end, Exception, 'Unquote outside quasiquote should raise exception');
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TMacroTests.Test_Macro_Argument_Count_Mismatch;
|
||||||
|
begin
|
||||||
|
Assert.WillRaise(procedure begin Run('(do (defmacro two [a b] `(+ ~a ~b)) (two 1))'); end, Exception);
|
||||||
|
end;
|
||||||
|
|
||||||
|
end.
|
||||||
@@ -0,0 +1,382 @@
|
|||||||
|
unit Test.Myc.Ast.Script;
|
||||||
|
|
||||||
|
interface
|
||||||
|
|
||||||
|
uses
|
||||||
|
DUnitX.TestFramework,
|
||||||
|
System.SysUtils,
|
||||||
|
System.Generics.Collections,
|
||||||
|
Myc.Data.Value,
|
||||||
|
Myc.Data.Scalar,
|
||||||
|
Myc.Ast.Nodes,
|
||||||
|
Myc.Ast.Script,
|
||||||
|
Myc.Ast;
|
||||||
|
|
||||||
|
type
|
||||||
|
[TestFixture]
|
||||||
|
TTestMycAstScript = class
|
||||||
|
private
|
||||||
|
function Parse(const S: string): IAstNode;
|
||||||
|
public
|
||||||
|
// --- Atoms ---
|
||||||
|
[Test]
|
||||||
|
[TestCase('Integer', '42,42')]
|
||||||
|
[TestCase('Negative', '-10,-10')]
|
||||||
|
[TestCase('Zero', '0,0')]
|
||||||
|
procedure Parser_Number_Integer(const Source: string; Expected: Int64);
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[TestCase('Float', '3.14,3.14')]
|
||||||
|
[TestCase('FloatNeg', '-0.5,-0.5')]
|
||||||
|
procedure Parser_Number_Float(const Source: string; Expected: Double);
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[TestCase('Simple', '"hello",hello')]
|
||||||
|
[TestCase('Space', '"hello world",hello world')]
|
||||||
|
[TestCase('Empty', '"",')]
|
||||||
|
procedure Parser_String(const Source, Expected: string);
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[TestCase('Simple', 'myVar')]
|
||||||
|
[TestCase('Kebab', 'my-var')]
|
||||||
|
[TestCase('Snake', 'my_var')]
|
||||||
|
[TestCase('Hygienic', 'var#1')]
|
||||||
|
[TestCase('Predicate', 'empty?')]
|
||||||
|
[TestCase('Bang', 'set!')]
|
||||||
|
procedure Parser_Identifier(const Source: string);
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[IgnoreMemoryLeaks]
|
||||||
|
[TestCase('Simple', ':key,key')]
|
||||||
|
[TestCase('Kebab', ':my-key,my-key')]
|
||||||
|
procedure Parser_Keyword(const Source, ExpectedName: string);
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
procedure Parser_Nop_Token;
|
||||||
|
|
||||||
|
// --- Comments ---
|
||||||
|
[Test]
|
||||||
|
procedure Parser_Comments_AreIgnored;
|
||||||
|
|
||||||
|
// --- Structures ---
|
||||||
|
[Test]
|
||||||
|
procedure Parser_List_FunctionCall;
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[IgnoreMemoryLeaks]
|
||||||
|
procedure Parser_RecordLiteral;
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
procedure Parser_RecordLiteral_Empty;
|
||||||
|
|
||||||
|
// --- Special Forms ---
|
||||||
|
[Test]
|
||||||
|
procedure Parser_If_CreatesIfNode;
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
procedure Parser_Fn_CreatesLambdaNode;
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
procedure Parser_Def_CreatesVarDeclNode;
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[IgnoreMemoryLeaks]
|
||||||
|
procedure Parser_MemberAccess_DotNotation;
|
||||||
|
|
||||||
|
// --- Reader Macros ---
|
||||||
|
[Test]
|
||||||
|
procedure Parser_Quote_ExpandsToCall;
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
procedure Parser_Quasiquote_CreatesNode;
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
procedure Parser_Unquote_CreatesNode;
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
procedure Parser_UnquoteSplicing_CreatesNode;
|
||||||
|
|
||||||
|
// --- Error Handling ---
|
||||||
|
[Test]
|
||||||
|
procedure Parser_Error_UnbalancedParens_MissingRight;
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
procedure Parser_Error_UnbalancedParens_ExtraRight;
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
procedure Parser_Error_UnterminatedString;
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
procedure Parser_Error_EmptyList;
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
procedure Parser_Error_Record_MissingValue;
|
||||||
|
end;
|
||||||
|
|
||||||
|
implementation
|
||||||
|
|
||||||
|
{ TTestMycAstScript }
|
||||||
|
|
||||||
|
function TTestMycAstScript.Parse(const S: string): IAstNode;
|
||||||
|
begin
|
||||||
|
Result := TAstScript.Parse(S);
|
||||||
|
end;
|
||||||
|
|
||||||
|
// --- Atoms ---
|
||||||
|
|
||||||
|
procedure TTestMycAstScript.Parser_Number_Integer(const Source: string; Expected: Int64);
|
||||||
|
var
|
||||||
|
node: IAstNode;
|
||||||
|
begin
|
||||||
|
node := Parse(Source);
|
||||||
|
Assert.AreEqual<TAstNodeKind>(TAstNodeKind.akConstant, node.Kind);
|
||||||
|
Assert.AreEqual<TScalar.TKind>(TScalar.TKind.Ordinal, node.AsConstant.Value.AsScalar.Kind);
|
||||||
|
Assert.AreEqual<Int64>(Expected, node.AsConstant.Value.AsScalar.Value.AsInt64);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TTestMycAstScript.Parser_Number_Float(const Source: string; Expected: Double);
|
||||||
|
var
|
||||||
|
node: IAstNode;
|
||||||
|
begin
|
||||||
|
node := Parse(Source);
|
||||||
|
Assert.AreEqual<TAstNodeKind>(TAstNodeKind.akConstant, node.Kind);
|
||||||
|
Assert.AreEqual<TScalar.TKind>(TScalar.TKind.Float, node.AsConstant.Value.AsScalar.Kind);
|
||||||
|
|
||||||
|
// FIX 1: Use non-generic overload for Double with Delta
|
||||||
|
Assert.AreEqual(Expected, node.AsConstant.Value.AsScalar.Value.AsDouble, 0.0001);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TTestMycAstScript.Parser_String(const Source, Expected: string);
|
||||||
|
var
|
||||||
|
node: IAstNode;
|
||||||
|
s: string;
|
||||||
|
begin
|
||||||
|
node := Parse(Source);
|
||||||
|
Assert.AreEqual<TAstNodeKind>(TAstNodeKind.akConstant, node.Kind);
|
||||||
|
Assert.AreEqual<TDataValueKind>(TDataValueKind.vkText, node.AsConstant.Value.Kind);
|
||||||
|
|
||||||
|
s := node.AsConstant.Value.AsText;
|
||||||
|
Assert.AreEqual<string>(Expected, s);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TTestMycAstScript.Parser_Identifier(const Source: string);
|
||||||
|
var
|
||||||
|
node: IAstNode;
|
||||||
|
begin
|
||||||
|
node := Parse(Source);
|
||||||
|
Assert.AreEqual<TAstNodeKind>(TAstNodeKind.akIdentifier, node.Kind);
|
||||||
|
Assert.AreEqual<string>(Source, node.AsIdentifier.Name);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TTestMycAstScript.Parser_Keyword(const Source, ExpectedName: string);
|
||||||
|
var
|
||||||
|
node: IAstNode;
|
||||||
|
begin
|
||||||
|
node := Parse(Source);
|
||||||
|
Assert.AreEqual<TAstNodeKind>(TAstNodeKind.akKeyword, node.Kind);
|
||||||
|
Assert.AreEqual<string>(ExpectedName, node.AsKeyword.Value.Name);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TTestMycAstScript.Parser_Nop_Token;
|
||||||
|
var
|
||||||
|
node: IAstNode;
|
||||||
|
begin
|
||||||
|
node := Parse('...');
|
||||||
|
Assert.AreEqual<TAstNodeKind>(TAstNodeKind.akNop, node.Kind);
|
||||||
|
end;
|
||||||
|
|
||||||
|
// --- Comments ---
|
||||||
|
|
||||||
|
procedure TTestMycAstScript.Parser_Comments_AreIgnored;
|
||||||
|
var
|
||||||
|
node: IAstNode;
|
||||||
|
begin
|
||||||
|
node := Parse('123 ; this is a comment');
|
||||||
|
Assert.AreEqual<TAstNodeKind>(TAstNodeKind.akConstant, node.Kind);
|
||||||
|
Assert.AreEqual<Int64>(123, node.AsConstant.Value.AsScalar.Value.AsInt64);
|
||||||
|
end;
|
||||||
|
|
||||||
|
// --- Structures ---
|
||||||
|
|
||||||
|
procedure TTestMycAstScript.Parser_List_FunctionCall;
|
||||||
|
var
|
||||||
|
node: IAstNode;
|
||||||
|
call: IFunctionCallNode;
|
||||||
|
begin
|
||||||
|
node := Parse('(add 1 2)');
|
||||||
|
|
||||||
|
Assert.AreEqual<TAstNodeKind>(TAstNodeKind.akFunctionCall, node.Kind);
|
||||||
|
call := node.AsFunctionCall;
|
||||||
|
|
||||||
|
Assert.AreEqual<string>('add', call.Callee.AsIdentifier.Name);
|
||||||
|
Assert.AreEqual<Integer>(2, Length(call.Arguments));
|
||||||
|
|
||||||
|
Assert.AreEqual<Int64>(1, call.Arguments[0].AsConstant.Value.AsScalar.Value.AsInt64);
|
||||||
|
Assert.AreEqual<Int64>(2, call.Arguments[1].AsConstant.Value.AsScalar.Value.AsInt64);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TTestMycAstScript.Parser_RecordLiteral;
|
||||||
|
var
|
||||||
|
node: IAstNode;
|
||||||
|
rec: IRecordLiteralNode;
|
||||||
|
begin
|
||||||
|
node := Parse('{:a 1 :b 2}');
|
||||||
|
|
||||||
|
Assert.AreEqual<TAstNodeKind>(TAstNodeKind.akRecordLiteral, node.Kind);
|
||||||
|
rec := node.AsRecordLiteral;
|
||||||
|
|
||||||
|
Assert.AreEqual<Integer>(2, Length(rec.Fields));
|
||||||
|
Assert.AreEqual<string>('a', rec.Fields[0].Key.Value.Name);
|
||||||
|
Assert.AreEqual<Int64>(1, rec.Fields[0].Value.AsConstant.Value.AsScalar.Value.AsInt64);
|
||||||
|
|
||||||
|
Assert.AreEqual<string>('b', rec.Fields[1].Key.Value.Name);
|
||||||
|
Assert.AreEqual<Int64>(2, rec.Fields[1].Value.AsConstant.Value.AsScalar.Value.AsInt64);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TTestMycAstScript.Parser_RecordLiteral_Empty;
|
||||||
|
var
|
||||||
|
node: IAstNode;
|
||||||
|
begin
|
||||||
|
node := Parse('{}');
|
||||||
|
Assert.AreEqual<TAstNodeKind>(TAstNodeKind.akRecordLiteral, node.Kind);
|
||||||
|
Assert.AreEqual<Integer>(0, Length(node.AsRecordLiteral.Fields));
|
||||||
|
end;
|
||||||
|
|
||||||
|
// --- Special Forms ---
|
||||||
|
|
||||||
|
procedure TTestMycAstScript.Parser_If_CreatesIfNode;
|
||||||
|
var
|
||||||
|
node: IAstNode;
|
||||||
|
ifNode: IIfExpressionNode;
|
||||||
|
begin
|
||||||
|
node := Parse('(if 1 2 3)');
|
||||||
|
Assert.AreEqual<TAstNodeKind>(TAstNodeKind.akIfExpression, node.Kind);
|
||||||
|
|
||||||
|
ifNode := node.AsIfExpression;
|
||||||
|
Assert.IsNotNull(ifNode.Condition);
|
||||||
|
Assert.IsNotNull(ifNode.ThenBranch);
|
||||||
|
Assert.IsNotNull(ifNode.ElseBranch);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TTestMycAstScript.Parser_Fn_CreatesLambdaNode;
|
||||||
|
var
|
||||||
|
node: IAstNode;
|
||||||
|
lam: ILambdaExpressionNode;
|
||||||
|
begin
|
||||||
|
node := Parse('(fn [a b] (add a b))');
|
||||||
|
|
||||||
|
Assert.AreEqual<TAstNodeKind>(TAstNodeKind.akLambdaExpression, node.Kind);
|
||||||
|
lam := node.AsLambdaExpression;
|
||||||
|
|
||||||
|
Assert.AreEqual<Integer>(2, Length(lam.Parameters));
|
||||||
|
Assert.AreEqual<string>('a', lam.Parameters[0].Name);
|
||||||
|
Assert.AreEqual<string>('b', lam.Parameters[1].Name);
|
||||||
|
|
||||||
|
Assert.AreEqual<TAstNodeKind>(TAstNodeKind.akFunctionCall, lam.Body.Kind);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TTestMycAstScript.Parser_Def_CreatesVarDeclNode;
|
||||||
|
var
|
||||||
|
node: IAstNode;
|
||||||
|
decl: IVariableDeclarationNode;
|
||||||
|
begin
|
||||||
|
node := Parse('(def x 10)');
|
||||||
|
|
||||||
|
Assert.AreEqual<TAstNodeKind>(TAstNodeKind.akVariableDeclaration, node.Kind);
|
||||||
|
decl := node.AsVariableDeclaration;
|
||||||
|
|
||||||
|
Assert.AreEqual<string>('x', decl.Target.AsIdentifier.Name);
|
||||||
|
Assert.IsNotNull(decl.Initializer);
|
||||||
|
Assert.AreEqual<Int64>(10, decl.Initializer.AsConstant.Value.AsScalar.Value.AsInt64);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TTestMycAstScript.Parser_MemberAccess_DotNotation;
|
||||||
|
var
|
||||||
|
node: IAstNode;
|
||||||
|
mem: IMemberAccessNode;
|
||||||
|
begin
|
||||||
|
node := Parse('(.Name obj)');
|
||||||
|
|
||||||
|
Assert.AreEqual<TAstNodeKind>(TAstNodeKind.akMemberAccess, node.Kind);
|
||||||
|
mem := node.AsMemberAccess;
|
||||||
|
|
||||||
|
Assert.AreEqual<string>('Name', mem.Member.Value.Name);
|
||||||
|
Assert.AreEqual<string>('obj', mem.Base.AsIdentifier.Name);
|
||||||
|
end;
|
||||||
|
|
||||||
|
// --- Reader Macros ---
|
||||||
|
|
||||||
|
procedure TTestMycAstScript.Parser_Quote_ExpandsToCall;
|
||||||
|
var
|
||||||
|
node: IAstNode;
|
||||||
|
call: IFunctionCallNode;
|
||||||
|
begin
|
||||||
|
node := Parse('''foo');
|
||||||
|
|
||||||
|
Assert.AreEqual<TAstNodeKind>(TAstNodeKind.akFunctionCall, node.Kind);
|
||||||
|
call := node.AsFunctionCall;
|
||||||
|
|
||||||
|
Assert.AreEqual<string>('quote', call.Callee.AsIdentifier.Name);
|
||||||
|
Assert.AreEqual<Integer>(1, Length(call.Arguments));
|
||||||
|
Assert.AreEqual<string>('foo', call.Arguments[0].AsIdentifier.Name);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TTestMycAstScript.Parser_Quasiquote_CreatesNode;
|
||||||
|
var
|
||||||
|
node: IAstNode;
|
||||||
|
begin
|
||||||
|
node := Parse('`(a)');
|
||||||
|
Assert.AreEqual<TAstNodeKind>(TAstNodeKind.akQuasiquote, node.Kind);
|
||||||
|
Assert.AreEqual<TAstNodeKind>(TAstNodeKind.akFunctionCall, node.AsQuasiquote.Expression.Kind);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TTestMycAstScript.Parser_Unquote_CreatesNode;
|
||||||
|
var
|
||||||
|
node: IAstNode;
|
||||||
|
begin
|
||||||
|
node := Parse('~x');
|
||||||
|
Assert.AreEqual<TAstNodeKind>(TAstNodeKind.akUnquote, node.Kind);
|
||||||
|
Assert.AreEqual<string>('x', node.AsUnquote.Expression.AsIdentifier.Name);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TTestMycAstScript.Parser_UnquoteSplicing_CreatesNode;
|
||||||
|
var
|
||||||
|
node: IAstNode;
|
||||||
|
begin
|
||||||
|
// ~@x
|
||||||
|
// There's something off here - we'll fix this later
|
||||||
|
node := Parse('~@`x');
|
||||||
|
Assert.AreEqual<TAstNodeKind>(TAstNodeKind.akUnquoteSplicing, node.Kind);
|
||||||
|
|
||||||
|
Assert.AreEqual<string>('x', node.AsUnquoteSplicing.Expression.AsQuasiquote.Expression.AsIdentifier.Name);
|
||||||
|
end;
|
||||||
|
|
||||||
|
// --- Error Handling ---
|
||||||
|
|
||||||
|
procedure TTestMycAstScript.Parser_Error_UnbalancedParens_MissingRight;
|
||||||
|
begin
|
||||||
|
// FIX 2: Pass explicit Exception type to WillRaise
|
||||||
|
Assert.WillRaise(procedure begin Parse('(a b'); end, Exception);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TTestMycAstScript.Parser_Error_UnbalancedParens_ExtraRight;
|
||||||
|
begin
|
||||||
|
Assert.WillRaise(procedure begin Parse('a )'); end, Exception);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TTestMycAstScript.Parser_Error_UnterminatedString;
|
||||||
|
begin
|
||||||
|
Assert.WillRaise(procedure begin Parse('"hello'); end, Exception);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TTestMycAstScript.Parser_Error_EmptyList;
|
||||||
|
begin
|
||||||
|
Assert.WillRaise(procedure begin Parse('()'); end, Exception);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TTestMycAstScript.Parser_Error_Record_MissingValue;
|
||||||
|
begin
|
||||||
|
Assert.WillRaise(procedure begin Parse('{:a 1 :b}'); end, Exception);
|
||||||
|
end;
|
||||||
|
|
||||||
|
end.
|
||||||
+3
-7
@@ -25,16 +25,12 @@ uses
|
|||||||
Myc.Test.Signals.Dirty in '..\Src\Myc.Test.Signals.Dirty.pas',
|
Myc.Test.Signals.Dirty in '..\Src\Myc.Test.Signals.Dirty.pas',
|
||||||
Test.Core.Mutable in 'Test.Core.Mutable.pas',
|
Test.Core.Mutable in 'Test.Core.Mutable.pas',
|
||||||
TestDataArray in 'TestDataArray.pas',
|
TestDataArray in 'TestDataArray.pas',
|
||||||
TestDataTypes in '..\Src\Data\TestDataTypes.pas',
|
|
||||||
Myc.Ast in '..\Src\AST\Myc.Ast.pas',
|
|
||||||
TestDataTypes.JSON in '..\Src\Data\TestDataTypes.JSON.pas',
|
|
||||||
Myc.Data.POD in '..\Src\Data\Myc.Data.Scalar.pas',
|
|
||||||
Myc.Data.Decimal in '..\Src\Data\Myc.Data.Decimal.pas',
|
|
||||||
TestDataDecimal in 'TestDataDecimal.pas',
|
TestDataDecimal in 'TestDataDecimal.pas',
|
||||||
TestDataPOD in 'TestDataPOD.pas',
|
TestDataPOD in 'TestDataPOD.pas',
|
||||||
Myc.Data.Chunks in '..\Src\Data\Myc.Data.Chunks.pas',
|
|
||||||
Test.Myc.Ast.Scope in 'AST\Test.Myc.Ast.Scope.pas',
|
Test.Myc.Ast.Scope in 'AST\Test.Myc.Ast.Scope.pas',
|
||||||
Test.Myc.Ast.RTL in 'AST\Test.Myc.Ast.RTL.pas';
|
Test.Myc.Ast.RTL in 'AST\Test.Myc.Ast.RTL.pas',
|
||||||
|
Test.Myc.Ast.Script in 'AST\Test.Myc.Ast.Script.pas',
|
||||||
|
Test.Myc.Ast.Compiler.Macros in 'AST\Test.Myc.Ast.Compiler.Macros.pas';
|
||||||
|
|
||||||
{ keep comment here to protect the following conditional from being removed by the IDE when adding a unit }
|
{ keep comment here to protect the following conditional from being removed by the IDE when adding a unit }
|
||||||
{$IFNDEF TESTINSIGHT}
|
{$IFNDEF TESTINSIGHT}
|
||||||
|
|||||||
+2
-8
@@ -126,18 +126,12 @@ $(PreBuildEvent)]]></PreBuildEvent>
|
|||||||
<DCCReference Include="..\Src\Myc.Test.Signals.Dirty.pas"/>
|
<DCCReference Include="..\Src\Myc.Test.Signals.Dirty.pas"/>
|
||||||
<DCCReference Include="Test.Core.Mutable.pas"/>
|
<DCCReference Include="Test.Core.Mutable.pas"/>
|
||||||
<DCCReference Include="TestDataArray.pas"/>
|
<DCCReference Include="TestDataArray.pas"/>
|
||||||
<DCCReference Include="..\Src\Data\TestDataTypes.pas"/>
|
|
||||||
<DCCReference Include="..\Src\AST\Myc.Ast.pas"/>
|
|
||||||
<DCCReference Include="..\Src\Data\TestDataTypes.JSON.pas"/>
|
|
||||||
<DCCReference Include="..\Src\Data\Myc.Data.Scalar.pas">
|
|
||||||
<ModuleName>Myc.Data.POD</ModuleName>
|
|
||||||
</DCCReference>
|
|
||||||
<DCCReference Include="..\Src\Data\Myc.Data.Decimal.pas"/>
|
|
||||||
<DCCReference Include="TestDataDecimal.pas"/>
|
<DCCReference Include="TestDataDecimal.pas"/>
|
||||||
<DCCReference Include="TestDataPOD.pas"/>
|
<DCCReference Include="TestDataPOD.pas"/>
|
||||||
<DCCReference Include="..\Src\Data\Myc.Data.Chunks.pas"/>
|
|
||||||
<DCCReference Include="AST\Test.Myc.Ast.Scope.pas"/>
|
<DCCReference Include="AST\Test.Myc.Ast.Scope.pas"/>
|
||||||
<DCCReference Include="AST\Test.Myc.Ast.RTL.pas"/>
|
<DCCReference Include="AST\Test.Myc.Ast.RTL.pas"/>
|
||||||
|
<DCCReference Include="AST\Test.Myc.Ast.Script.pas"/>
|
||||||
|
<DCCReference Include="AST\Test.Myc.Ast.Compiler.Macros.pas"/>
|
||||||
<BuildConfiguration Include="Base">
|
<BuildConfiguration Include="Base">
|
||||||
<Key>Base</Key>
|
<Key>Base</Key>
|
||||||
</BuildConfiguration>
|
</BuildConfiguration>
|
||||||
|
|||||||
Reference in New Issue
Block a user