RTL custom interface types as records
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -36,7 +36,8 @@ uses
|
|||||||
Myc.Fmx.AstEditor.Handlers.Lists in '..\Src\AST\Myc.Fmx.AstEditor.Handlers.Lists.pas',
|
Myc.Fmx.AstEditor.Handlers.Lists in '..\Src\AST\Myc.Fmx.AstEditor.Handlers.Lists.pas',
|
||||||
Myc.Fmx.AstEditor.Handlers.Primitives in '..\Src\AST\Myc.Fmx.AstEditor.Handlers.Primitives.pas',
|
Myc.Fmx.AstEditor.Handlers.Primitives in '..\Src\AST\Myc.Fmx.AstEditor.Handlers.Primitives.pas',
|
||||||
Myc.Fmx.AstEditor.Handlers.Control in '..\Src\AST\Myc.Fmx.AstEditor.Handlers.Control.pas',
|
Myc.Fmx.AstEditor.Handlers.Control in '..\Src\AST\Myc.Fmx.AstEditor.Handlers.Control.pas',
|
||||||
Myc.Fmx.AstEditor.Handlers.Data in '..\Src\AST\Myc.Fmx.AstEditor.Handlers.Data.pas';
|
Myc.Fmx.AstEditor.Handlers.Data in '..\Src\AST\Myc.Fmx.AstEditor.Handlers.Data.pas',
|
||||||
|
Myc.Ast.RTL.TypeRegistry in '..\Src\AST\Myc.Ast.RTL.TypeRegistry.pas';
|
||||||
|
|
||||||
{$R *.res}
|
{$R *.res}
|
||||||
|
|
||||||
|
|||||||
@@ -167,6 +167,7 @@
|
|||||||
<DCCReference Include="..\Src\AST\Myc.Fmx.AstEditor.Handlers.Primitives.pas"/>
|
<DCCReference Include="..\Src\AST\Myc.Fmx.AstEditor.Handlers.Primitives.pas"/>
|
||||||
<DCCReference Include="..\Src\AST\Myc.Fmx.AstEditor.Handlers.Control.pas"/>
|
<DCCReference Include="..\Src\AST\Myc.Fmx.AstEditor.Handlers.Control.pas"/>
|
||||||
<DCCReference Include="..\Src\AST\Myc.Fmx.AstEditor.Handlers.Data.pas"/>
|
<DCCReference Include="..\Src\AST\Myc.Fmx.AstEditor.Handlers.Data.pas"/>
|
||||||
|
<DCCReference Include="..\Src\AST\Myc.Ast.RTL.TypeRegistry.pas"/>
|
||||||
<BuildConfiguration Include="Base">
|
<BuildConfiguration Include="Base">
|
||||||
<Key>Base</Key>
|
<Key>Base</Key>
|
||||||
</BuildConfiguration>
|
</BuildConfiguration>
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ uses
|
|||||||
System.Generics.Collections,
|
System.Generics.Collections,
|
||||||
Myc.Data.Scalar,
|
Myc.Data.Scalar,
|
||||||
Myc.Data.Value,
|
Myc.Data.Value,
|
||||||
Myc.Ast.Nodes, // Provides EAstException
|
Myc.Data.Keyword,
|
||||||
Myc.Ast.Scope,
|
Myc.Ast.Nodes,
|
||||||
Myc.Data.Keyword;
|
Myc.Ast.Scope;
|
||||||
|
|
||||||
type
|
type
|
||||||
// Exception for runtime errors during script evaluation
|
// Exception for runtime errors during script evaluation
|
||||||
|
|||||||
@@ -101,6 +101,8 @@ type
|
|||||||
class function Add_Ordinal_Float_Float(A: Int64; B: Double): Double; static;
|
class function Add_Ordinal_Float_Float(A: Int64; B: Double): Double; static;
|
||||||
[TRtlExport('+', True)]
|
[TRtlExport('+', True)]
|
||||||
class function Add_Float_Ordinal_Float(A: Double; B: Int64): Double; static;
|
class function Add_Float_Ordinal_Float(A: Double; B: Int64): Double; static;
|
||||||
|
[TRtlExport('+', True)]
|
||||||
|
class function Add_Text_Text_Text(const A: String; const B: String): String; static;
|
||||||
|
|
||||||
// Subtract
|
// Subtract
|
||||||
[TRtlExport('-', True)]
|
[TRtlExport('-', True)]
|
||||||
@@ -249,6 +251,9 @@ class function TRtlFunctions.Add(const Args: TArray<TDataValue>): TDataValue;
|
|||||||
begin
|
begin
|
||||||
if Length(Args) <> 2 then
|
if Length(Args) <> 2 then
|
||||||
raise EArgumentException.Create('Operator + requires 2 arguments.');
|
raise EArgumentException.Create('Operator + requires 2 arguments.');
|
||||||
|
if (Args[0].Kind = vkText) and (Args[1].Kind = vkText) then
|
||||||
|
Result := Args[0].AsText + Args[1].AsText
|
||||||
|
else
|
||||||
Result := Args[0].AsScalar + Args[1].AsScalar;
|
Result := Args[0].AsScalar + Args[1].AsScalar;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -915,6 +920,11 @@ begin
|
|||||||
Result := System.Abs(A);
|
Result := System.Abs(A);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
class function TRtlFunctions.Add_Text_Text_Text(const A: String; const B: String): String;
|
||||||
|
begin
|
||||||
|
Result := A + B;
|
||||||
|
end;
|
||||||
|
|
||||||
class function TRtlFunctions.Round_Ordinal_Ordinal(A: Int64): Int64;
|
class function TRtlFunctions.Round_Ordinal_Ordinal(A: Int64): Int64;
|
||||||
begin
|
begin
|
||||||
Result := A;
|
Result := A;
|
||||||
|
|||||||
@@ -0,0 +1,385 @@
|
|||||||
|
unit Myc.Ast.RTL.TypeRegistry;
|
||||||
|
|
||||||
|
interface
|
||||||
|
|
||||||
|
uses
|
||||||
|
System.SysUtils,
|
||||||
|
System.Classes,
|
||||||
|
System.Rtti,
|
||||||
|
System.TypInfo,
|
||||||
|
System.Generics.Collections,
|
||||||
|
Myc.Data.Value,
|
||||||
|
Myc.Data.Scalar,
|
||||||
|
Myc.Data.Keyword,
|
||||||
|
Myc.Ast.Types,
|
||||||
|
Myc.Ast.Scope,
|
||||||
|
Myc.Ast.RTL;
|
||||||
|
|
||||||
|
type
|
||||||
|
EInteropException = class(Exception);
|
||||||
|
ERecursionException = class(EInteropException);
|
||||||
|
|
||||||
|
// Factory delegate for Interfaces
|
||||||
|
TInterfaceFactoryDelegate<T: IInterface> = reference to function(const Args: TArray<TDataValue>): T;
|
||||||
|
|
||||||
|
// Central registry mapping Delphi Interfaces to AST static types and handling
|
||||||
|
// runtime wrapping (Shadow Records).
|
||||||
|
TRtlTypeRegistry = class
|
||||||
|
private
|
||||||
|
class var
|
||||||
|
FCtx: TRttiContext;
|
||||||
|
FKnownTypes: TDictionary<PTypeInfo, IStaticType>;
|
||||||
|
FAnalysisStack: TList<PTypeInfo>;
|
||||||
|
|
||||||
|
// --- Analysis ---
|
||||||
|
class function AnalyzeType(ATypeInfo: PTypeInfo): IStaticType; static;
|
||||||
|
class function GenerateInterfaceDefinition(RType: TRttiInterfaceType): IStaticType; static;
|
||||||
|
class procedure EnterAnalysis(Info: PTypeInfo); static;
|
||||||
|
class procedure ExitAnalysis(Info: PTypeInfo); static;
|
||||||
|
|
||||||
|
// --- Marshalling ---
|
||||||
|
class function ToTValue(const AData: TDataValue; ATargetType: TRttiType): TValue; static;
|
||||||
|
class function FromTValue(const AValue: TValue): TDataValue; static;
|
||||||
|
|
||||||
|
// --- Wrapping ---
|
||||||
|
class function WrapInstance(const Instance: IInterface; TypeInfo: PTypeInfo): TDataValue; static;
|
||||||
|
|
||||||
|
public
|
||||||
|
class constructor Create;
|
||||||
|
class destructor Destroy;
|
||||||
|
|
||||||
|
// --- Registration ---
|
||||||
|
// Registers a Delphi Interface type. Raises ERecursionException on cycles.
|
||||||
|
class procedure RegisterType<T: IInterface>; overload;
|
||||||
|
class procedure RegisterType(Info: PTypeInfo); overload;
|
||||||
|
|
||||||
|
// --- Factories ---
|
||||||
|
// Registers a factory function. T must be registered via RegisterType<T> first.
|
||||||
|
class procedure RegisterFactory<T: IInterface>(
|
||||||
|
const Scope: IExecutionScope;
|
||||||
|
const FactoryName: string;
|
||||||
|
const FactoryArgs: TArray<IStaticType>;
|
||||||
|
const FactoryDelegate: TInterfaceFactoryDelegate<T>
|
||||||
|
);
|
||||||
|
|
||||||
|
// --- Resolution ---
|
||||||
|
class function GetStaticType(Info: PTypeInfo): IStaticType;
|
||||||
|
class function ResolveType(RType: TRttiType): IStaticType;
|
||||||
|
|
||||||
|
class property Context: TRttiContext read FCtx;
|
||||||
|
end;
|
||||||
|
|
||||||
|
implementation
|
||||||
|
|
||||||
|
uses
|
||||||
|
Myc.Ast.RTL.Core;
|
||||||
|
|
||||||
|
{ TRtlTypeRegistry }
|
||||||
|
|
||||||
|
class constructor TRtlTypeRegistry.Create;
|
||||||
|
begin
|
||||||
|
FCtx := TRttiContext.Create;
|
||||||
|
FKnownTypes := TDictionary<PTypeInfo, IStaticType>.Create;
|
||||||
|
FAnalysisStack := TList<PTypeInfo>.Create;
|
||||||
|
end;
|
||||||
|
|
||||||
|
class destructor TRtlTypeRegistry.Destroy;
|
||||||
|
begin
|
||||||
|
FAnalysisStack.Free;
|
||||||
|
FKnownTypes.Free;
|
||||||
|
FCtx.Free;
|
||||||
|
end;
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// Type Analysis
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
class procedure TRtlTypeRegistry.EnterAnalysis(Info: PTypeInfo);
|
||||||
|
begin
|
||||||
|
if FAnalysisStack.Contains(Info) then
|
||||||
|
raise ERecursionException.CreateFmt('Recursive type definition detected for "%s".', [string(Info.Name)]);
|
||||||
|
FAnalysisStack.Add(Info);
|
||||||
|
end;
|
||||||
|
|
||||||
|
class procedure TRtlTypeRegistry.ExitAnalysis(Info: PTypeInfo);
|
||||||
|
begin
|
||||||
|
FAnalysisStack.Remove(Info);
|
||||||
|
end;
|
||||||
|
|
||||||
|
class procedure TRtlTypeRegistry.RegisterType<T>;
|
||||||
|
begin
|
||||||
|
RegisterType(TypeInfo(T));
|
||||||
|
end;
|
||||||
|
|
||||||
|
class procedure TRtlTypeRegistry.RegisterType(Info: PTypeInfo);
|
||||||
|
begin
|
||||||
|
if FKnownTypes.ContainsKey(Info) then
|
||||||
|
exit;
|
||||||
|
|
||||||
|
EnterAnalysis(Info);
|
||||||
|
try
|
||||||
|
var staticType := AnalyzeType(Info);
|
||||||
|
FKnownTypes.Add(Info, staticType);
|
||||||
|
finally
|
||||||
|
ExitAnalysis(Info);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
class function TRtlTypeRegistry.GetStaticType(Info: PTypeInfo): IStaticType;
|
||||||
|
begin
|
||||||
|
if not FKnownTypes.TryGetValue(Info, Result) then
|
||||||
|
Result := TTypes.Unknown;
|
||||||
|
end;
|
||||||
|
|
||||||
|
class function TRtlTypeRegistry.ResolveType(RType: TRttiType): IStaticType;
|
||||||
|
begin
|
||||||
|
if not Assigned(RType) then
|
||||||
|
exit(TTypes.Void);
|
||||||
|
|
||||||
|
if FKnownTypes.TryGetValue(RType.Handle, Result) then
|
||||||
|
exit;
|
||||||
|
|
||||||
|
if FAnalysisStack.Contains(RType.Handle) then
|
||||||
|
raise ERecursionException.CreateFmt('Recursive type reference detected during resolution of "%s".', [RType.Name]);
|
||||||
|
|
||||||
|
case RType.TypeKind of
|
||||||
|
tkInteger, tkInt64: Result := TTypes.Ordinal;
|
||||||
|
tkFloat: Result := TTypes.Float;
|
||||||
|
tkString, tkUString, tkWString, tkLString, tkChar, tkWChar: Result := TTypes.Text;
|
||||||
|
tkEnumeration:
|
||||||
|
if RType.Handle = TypeInfo(Boolean) then
|
||||||
|
Result := TTypes.Boolean
|
||||||
|
else
|
||||||
|
Result := TTypes.Ordinal;
|
||||||
|
tkInterface: raise EInteropException.CreateFmt('Interface "%s" is not registered. Call RegisterType first.', [RType.Name]);
|
||||||
|
else
|
||||||
|
Result := TTypes.Unknown;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
class function TRtlTypeRegistry.AnalyzeType(ATypeInfo: PTypeInfo): IStaticType;
|
||||||
|
var
|
||||||
|
rType: TRttiType;
|
||||||
|
begin
|
||||||
|
rType := FCtx.GetType(ATypeInfo);
|
||||||
|
case rType.TypeKind of
|
||||||
|
tkInterface: Result := GenerateInterfaceDefinition(rType as TRttiInterfaceType);
|
||||||
|
else
|
||||||
|
Result := ResolveType(rType);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
class function TRtlTypeRegistry.GenerateInterfaceDefinition(RType: TRttiInterfaceType): IStaticType;
|
||||||
|
var
|
||||||
|
methods: TArray<TRttiMethod>;
|
||||||
|
props: TArray<TRttiProperty>;
|
||||||
|
fields: TList<TPair<IKeyword, IStaticType>>;
|
||||||
|
|
||||||
|
function MakeSig(Method: TRttiMethod): IStaticType;
|
||||||
|
var
|
||||||
|
ps: TArray<TRttiParameter>;
|
||||||
|
pTypes: TArray<IStaticType>;
|
||||||
|
returnType: IStaticType;
|
||||||
|
k: Integer;
|
||||||
|
begin
|
||||||
|
ps := Method.GetParameters;
|
||||||
|
SetLength(pTypes, Length(ps));
|
||||||
|
for k := 0 to High(ps) do
|
||||||
|
pTypes[k] := ResolveType(ps[k].ParamType);
|
||||||
|
|
||||||
|
if Assigned(Method.ReturnType) then
|
||||||
|
returnType := ResolveType(Method.ReturnType)
|
||||||
|
else
|
||||||
|
returnType := TTypes.Void;
|
||||||
|
|
||||||
|
Result := TTypes.CreateMethod(pTypes, returnType);
|
||||||
|
end;
|
||||||
|
|
||||||
|
begin
|
||||||
|
fields := TList<TPair<IKeyword, IStaticType>>.Create;
|
||||||
|
try
|
||||||
|
methods := RType.GetMethods;
|
||||||
|
for var m in methods do
|
||||||
|
begin
|
||||||
|
// Only map standard procedures/functions
|
||||||
|
if m.MethodKind in [mkProcedure, mkFunction] then
|
||||||
|
fields.Add(TPair<IKeyword, IStaticType>.Create(TKeywordRegistry.Intern(m.Name), MakeSig(m)));
|
||||||
|
end;
|
||||||
|
|
||||||
|
props := RType.GetProperties;
|
||||||
|
for var p in props do
|
||||||
|
begin
|
||||||
|
if p.IsReadable then
|
||||||
|
begin
|
||||||
|
var propType := ResolveType(p.PropertyType);
|
||||||
|
var getterSig := TTypes.CreateMethod([], propType);
|
||||||
|
fields.Add(TPair<IKeyword, IStaticType>.Create(TKeywordRegistry.Intern(p.Name), getterSig));
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
var def := TGenericRecordRegistry.Intern(fields.ToArray);
|
||||||
|
Result := TTypes.CreateGenericRecord(def);
|
||||||
|
finally
|
||||||
|
fields.Free;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// Marshalling
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
class function TRtlTypeRegistry.ToTValue(const AData: TDataValue; ATargetType: TRttiType): TValue;
|
||||||
|
begin
|
||||||
|
case AData.Kind of
|
||||||
|
vkScalar:
|
||||||
|
begin
|
||||||
|
var sc := AData.AsScalar;
|
||||||
|
case sc.Kind of
|
||||||
|
TScalar.TKind.Ordinal:
|
||||||
|
begin
|
||||||
|
if ATargetType.Handle = TypeInfo(Integer) then
|
||||||
|
Result := TValue.From<Integer>(Integer(sc.Value.AsInt64))
|
||||||
|
else if ATargetType.Handle = TypeInfo(Byte) then
|
||||||
|
Result := TValue.From<Byte>(Byte(sc.Value.AsInt64))
|
||||||
|
else if ATargetType.Handle = TypeInfo(Word) then
|
||||||
|
Result := TValue.From<Word>(Word(sc.Value.AsInt64))
|
||||||
|
else
|
||||||
|
Result := TValue.From<Int64>(sc.Value.AsInt64);
|
||||||
|
end;
|
||||||
|
TScalar.TKind.Float, TScalar.TKind.DateTime: Result := TValue.From<Double>(sc.Value.AsDouble);
|
||||||
|
TScalar.TKind.Boolean: Result := TValue.From<Boolean>(sc.Value.AsInt64 <> 0);
|
||||||
|
TScalar.TKind.Keyword:
|
||||||
|
if ATargetType.TypeKind in [tkString, tkUString, tkWString, tkLString] then
|
||||||
|
Result := TValue.From<String>(TKeywordRegistry.GetName(sc.Value.AsInt64))
|
||||||
|
else
|
||||||
|
Result := TValue.From<Int64>(sc.Value.AsInt64);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
vkText: Result := TValue.From<string>(AData.AsText);
|
||||||
|
vkVoid: Result := TValue.Empty;
|
||||||
|
else
|
||||||
|
raise EInteropException.CreateFmt('Marshalling for DataKind %s not implemented.', [TRttiEnumerationType.GetName(AData.Kind)]);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
class function TRtlTypeRegistry.FromTValue(const AValue: TValue): TDataValue;
|
||||||
|
begin
|
||||||
|
if AValue.IsEmpty then
|
||||||
|
exit(TDataValue.Void);
|
||||||
|
|
||||||
|
case AValue.Kind of
|
||||||
|
tkInteger, tkInt64: Result := TScalar.FromInt64(AValue.AsInt64);
|
||||||
|
tkFloat: Result := TScalar.FromDouble(AValue.AsExtended);
|
||||||
|
tkString, tkUString, tkWString, tkLString: Result := AValue.AsString;
|
||||||
|
tkEnumeration:
|
||||||
|
if AValue.TypeInfo = TypeInfo(Boolean) then
|
||||||
|
Result := TScalar.FromBoolean(AValue.AsBoolean)
|
||||||
|
else
|
||||||
|
Result := TScalar.FromInt64(AValue.AsOrdinal);
|
||||||
|
|
||||||
|
// Recursively wrap returned interfaces
|
||||||
|
tkInterface: Result := WrapInstance(AValue.AsInterface, AValue.TypeInfo);
|
||||||
|
else
|
||||||
|
Result := TDataValue.Void;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// Runtime Wrapping (Shadow Record Creation)
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
class function TRtlTypeRegistry.WrapInstance(const Instance: IInterface; TypeInfo: PTypeInfo): TDataValue;
|
||||||
|
var
|
||||||
|
rType: TRttiType;
|
||||||
|
rIntf: TRttiInterfaceType;
|
||||||
|
recFields: TDictionary<IKeyword, TDataValue>;
|
||||||
|
begin
|
||||||
|
if Instance = nil then
|
||||||
|
exit(TDataValue.Void);
|
||||||
|
|
||||||
|
rType := FCtx.GetType(TypeInfo);
|
||||||
|
if not (rType is TRttiInterfaceType) then
|
||||||
|
exit(TDataValue.Void);
|
||||||
|
|
||||||
|
rIntf := rType as TRttiInterfaceType;
|
||||||
|
recFields := TDictionary<IKeyword, TDataValue>.Create;
|
||||||
|
try
|
||||||
|
// Wrap Methods
|
||||||
|
for var m in rIntf.GetMethods do
|
||||||
|
begin
|
||||||
|
if (m.MethodKind in [mkProcedure, mkFunction]) then
|
||||||
|
begin
|
||||||
|
var key := TKeywordRegistry.Intern(m.Name);
|
||||||
|
|
||||||
|
if recFields.ContainsKey(key) then
|
||||||
|
raise EInteropException.Create('Overloading not supported');
|
||||||
|
|
||||||
|
var methodFactory :=
|
||||||
|
function(const Instance: IInterface; Meth: TRttiMethod): TDataValue.TFunc
|
||||||
|
begin
|
||||||
|
var inst := TValue.From(Instance);
|
||||||
|
var params := Meth.GetParameters;
|
||||||
|
Result :=
|
||||||
|
function(const Args: TArray<TDataValue>): TDataValue
|
||||||
|
var
|
||||||
|
rttiArgs: TArray<TValue>;
|
||||||
|
res: TValue;
|
||||||
|
i: Integer;
|
||||||
|
begin
|
||||||
|
if Length(Args) <> Length(params) then
|
||||||
|
raise Exception.CreateFmt('Method %s expects %d args', [m.Name, Length(params)]);
|
||||||
|
|
||||||
|
SetLength(rttiArgs, Length(params));
|
||||||
|
for i := 0 to High(params) do
|
||||||
|
rttiArgs[i] := ToTValue(Args[i], params[i].ParamType);
|
||||||
|
|
||||||
|
res := Meth.Invoke(inst, rttiArgs);
|
||||||
|
|
||||||
|
Result := FromTValue(res);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
recFields.Add(key, methodFactory(Instance, m));
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
Result := TDataValue.FromGenericRecord(TKeywordMapping<TDataValue>.Create(recFields.ToArray));
|
||||||
|
finally
|
||||||
|
recFields.Free;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// Factories
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
class procedure TRtlTypeRegistry.RegisterFactory<T>(
|
||||||
|
const Scope: IExecutionScope;
|
||||||
|
const FactoryName: string;
|
||||||
|
const FactoryArgs: TArray<IStaticType>;
|
||||||
|
const FactoryDelegate: TInterfaceFactoryDelegate<T>
|
||||||
|
);
|
||||||
|
var
|
||||||
|
staticType: IStaticType;
|
||||||
|
ti: PTypeInfo;
|
||||||
|
begin
|
||||||
|
ti := TypeInfo(T);
|
||||||
|
staticType := GetStaticType(ti);
|
||||||
|
if staticType.Kind = stUnknown then
|
||||||
|
raise EInteropException.CreateFmt('Interface "%s" must be registered via RegisterType<T> first.', [string(ti.Name)]);
|
||||||
|
|
||||||
|
var factoryWrapper: TDataValue.TFunc :=
|
||||||
|
function(const Args: TArray<TDataValue>): TDataValue
|
||||||
|
var
|
||||||
|
instance: T;
|
||||||
|
begin
|
||||||
|
instance := FactoryDelegate(Args);
|
||||||
|
// Result wrapping will handle reference counting via closures
|
||||||
|
Result := WrapInstance(instance, ti);
|
||||||
|
end;
|
||||||
|
|
||||||
|
var factorySig := TTypes.CreateMethod(FactoryArgs, staticType);
|
||||||
|
Scope.Define(FactoryName, TDataValue(factoryWrapper), factorySig);
|
||||||
|
end;
|
||||||
|
|
||||||
|
end.
|
||||||
+21
-1
@@ -84,6 +84,7 @@ type
|
|||||||
TNativeFunc_OF_O = function(A: Int64; B: Double): Int64;
|
TNativeFunc_OF_O = function(A: Int64; B: Double): Int64;
|
||||||
TNativeFunc_FO_F = function(A: Double; B: Int64): Double;
|
TNativeFunc_FO_F = function(A: Double; B: Int64): Double;
|
||||||
TNativeFunc_FO_O = function(A: Double; B: Int64): Int64;
|
TNativeFunc_FO_O = function(A: Double; B: Int64): Int64;
|
||||||
|
TNativeFunc_TT_T = function(const A: String; const B: String): String;
|
||||||
|
|
||||||
private
|
private
|
||||||
class var
|
class var
|
||||||
@@ -105,6 +106,7 @@ type
|
|||||||
class function CreateWrapper_OF_O(CodeAddress: Pointer): TDataValue.TFunc; static;
|
class function CreateWrapper_OF_O(CodeAddress: Pointer): TDataValue.TFunc; static;
|
||||||
class function CreateWrapper_FO_F(CodeAddress: Pointer): TDataValue.TFunc; static;
|
class function CreateWrapper_FO_F(CodeAddress: Pointer): TDataValue.TFunc; static;
|
||||||
class function CreateWrapper_FO_O(CodeAddress: Pointer): TDataValue.TFunc; static;
|
class function CreateWrapper_FO_O(CodeAddress: Pointer): TDataValue.TFunc; static;
|
||||||
|
class function CreateWrapper_TT_T(CodeAddress: Pointer): TDataValue.TFunc; static;
|
||||||
class function CreateStaticWrapper(
|
class function CreateStaticWrapper(
|
||||||
method: TRttiMethod;
|
method: TRttiMethod;
|
||||||
retType: IStaticType;
|
retType: IStaticType;
|
||||||
@@ -656,6 +658,20 @@ begin
|
|||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
class function TRtlRegistry.CreateWrapper_TT_T(CodeAddress: Pointer): TDataValue.TFunc;
|
||||||
|
begin
|
||||||
|
Result :=
|
||||||
|
function(const Args: TArray<TDataValue>): TDataValue
|
||||||
|
var
|
||||||
|
A: String;
|
||||||
|
B: String;
|
||||||
|
begin
|
||||||
|
A := Args[0].AsText;
|
||||||
|
B := Args[1].AsText;
|
||||||
|
Result := TNativeFunc_TT_T(CodeAddress)(A, B);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
class function TRtlRegistry.CreateStaticWrapper(
|
class function TRtlRegistry.CreateStaticWrapper(
|
||||||
method: TRttiMethod;
|
method: TRttiMethod;
|
||||||
retType: IStaticType;
|
retType: IStaticType;
|
||||||
@@ -736,7 +752,11 @@ begin
|
|||||||
begin
|
begin
|
||||||
// Keywords are passed as Int64 (index)
|
// Keywords are passed as Int64 (index)
|
||||||
Result := CreateWrapper_OO_O(ptr); // e.g. Equal_Keyword_Keyword
|
Result := CreateWrapper_OO_O(ptr); // e.g. Equal_Keyword_Keyword
|
||||||
end;
|
end
|
||||||
|
else if (k1 = stText) and (k2 = stText) then
|
||||||
|
begin
|
||||||
|
Result := CreateWrapper_TT_T(ptr); // e.g. String concat
|
||||||
|
end
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
|||||||
@@ -227,10 +227,6 @@ type
|
|||||||
function VisitNop(const Node: INopNode): TVoid; override;
|
function VisitNop(const Node: INopNode): TVoid; override;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
|
||||||
// IMPLEMENTATION: Helpers & Basic Types
|
|
||||||
// -----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
function TTokenKindHelper.ToString: String;
|
function TTokenKindHelper.ToString: String;
|
||||||
begin
|
begin
|
||||||
case Self of
|
case Self of
|
||||||
@@ -259,9 +255,7 @@ begin
|
|||||||
Result := TIdentities.Location(Line, Col);
|
Result := TIdentities.Location(Line, Col);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
{ EParserException }
|
||||||
// IMPLEMENTATION: Lexer
|
|
||||||
// -----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
constructor EParserException.Create(const AMessage: string; ALine, ACol: Integer);
|
constructor EParserException.Create(const AMessage: string; ALine, ACol: Integer);
|
||||||
begin
|
begin
|
||||||
@@ -270,6 +264,8 @@ begin
|
|||||||
FCol := ACol;
|
FCol := ACol;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
{ TLexer }
|
||||||
|
|
||||||
constructor TLexer.Create(const ASource: string);
|
constructor TLexer.Create(const ASource: string);
|
||||||
begin
|
begin
|
||||||
inherited Create;
|
inherited Create;
|
||||||
@@ -811,9 +807,7 @@ begin
|
|||||||
Result := expr.Node;
|
Result := expr.Node;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
{ TPrettyPrintVisitor }
|
||||||
// IMPLEMENTATION: PrettyPrintVisitor
|
|
||||||
// -----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
constructor TPrettyPrintVisitor.Create;
|
constructor TPrettyPrintVisitor.Create;
|
||||||
begin
|
begin
|
||||||
@@ -1136,9 +1130,7 @@ begin
|
|||||||
Append(')');
|
Append(')');
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
{ TAstScript }
|
||||||
// IMPLEMENTATION: TAstScript Facade
|
|
||||||
// -----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
class function TAstScript.Parse(const ASource: string): IAstNode;
|
class function TAstScript.Parse(const ASource: string): IAstNode;
|
||||||
var
|
var
|
||||||
|
|||||||
@@ -28,15 +28,11 @@ type
|
|||||||
RecCount: Integer;
|
RecCount: Integer;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// Converts a TScalarBatch into a stream of IScalarRecord events using the cursor pattern.
|
IBroker = interface
|
||||||
// NOTE: This uses a zero-copy approach. Consumers must process data synchronously or copy it.
|
function GetEquity: Double;
|
||||||
TScalarBatchUnpacker = class(TMycConverter<TScalarBatch, IScalarRecord>)
|
procedure Buy(Count: Integer);
|
||||||
private
|
procedure Sell(Count: Integer);
|
||||||
FDef: IScalarRecordDefinition;
|
property Equity: Double read GetEquity;
|
||||||
protected
|
|
||||||
function Consume(const Value: TScalarBatch): TState; override;
|
|
||||||
public
|
|
||||||
constructor Create(const ADef: IScalarRecordDefinition);
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
IScalarBatchLoader = interface
|
IScalarBatchLoader = interface
|
||||||
@@ -220,39 +216,6 @@ begin
|
|||||||
Result := FDef.IndexOf(Key);
|
Result := FDef.IndexOf(Key);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
{ TScalarBatchUnpacker }
|
|
||||||
|
|
||||||
constructor TScalarBatchUnpacker.Create(const ADef: IScalarRecordDefinition);
|
|
||||||
begin
|
|
||||||
inherited Create;
|
|
||||||
FDef := ADef;
|
|
||||||
end;
|
|
||||||
|
|
||||||
function TScalarBatchUnpacker.Consume(const Value: TScalarBatch): TState;
|
|
||||||
begin
|
|
||||||
if (Length(Value.Data) > 0) and (Value.RecCount > 0) then
|
|
||||||
begin
|
|
||||||
var Stride := Length(Value.Data) div Value.RecCount;
|
|
||||||
var pCursor: TScalar.PValue := @Value.Data[0];
|
|
||||||
|
|
||||||
// Initialize reused View object (Cursor Pattern)
|
|
||||||
// This object implements IScalarRecord and is updated for each tick.
|
|
||||||
var ViewObj := TScalarRecordView.Create(FDef);
|
|
||||||
var View := ViewObj as IScalarRecord;
|
|
||||||
|
|
||||||
for var i := 0 to Value.RecCount - 1 do
|
|
||||||
begin
|
|
||||||
ViewObj.SetData(pCursor);
|
|
||||||
|
|
||||||
// Broadcast to all listeners
|
|
||||||
Broadcast(View);
|
|
||||||
|
|
||||||
Inc(pCursor, Stride);
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
Result := TState.Null;
|
|
||||||
end;
|
|
||||||
|
|
||||||
{ TScalarBatchLoader.TDataFile }
|
{ TScalarBatchLoader.TDataFile }
|
||||||
|
|
||||||
constructor TScalarBatchLoader.TDataFile.Create(const APath, ASymbol, AExtension: String; AYear, AMonth: Integer; const ABasename: String);
|
constructor TScalarBatchLoader.TDataFile.Create(const APath, ASymbol, AExtension: String; AYear, AMonth: Integer; const ABasename: String);
|
||||||
|
|||||||
@@ -0,0 +1,481 @@
|
|||||||
|
unit Test.Myc.Ast.RTL.TypeRegistry;
|
||||||
|
|
||||||
|
interface
|
||||||
|
|
||||||
|
uses
|
||||||
|
System.SysUtils,
|
||||||
|
System.Classes,
|
||||||
|
System.Rtti,
|
||||||
|
DUnitX.TestFramework,
|
||||||
|
Myc.Data.Value,
|
||||||
|
Myc.Data.Scalar,
|
||||||
|
Myc.Ast.Types,
|
||||||
|
Myc.Ast.Scope,
|
||||||
|
Myc.Ast.Script,
|
||||||
|
Myc.Ast.Environment,
|
||||||
|
Myc.Ast.Evaluator,
|
||||||
|
Myc.Ast.RTL.TypeRegistry,
|
||||||
|
Myc.Ast; // Facade for parsing
|
||||||
|
|
||||||
|
type
|
||||||
|
// --- Mocks & Interfaces for Testing ---
|
||||||
|
|
||||||
|
// Ein untergeordnetes Interface für rekursive Tests
|
||||||
|
ISubService = interface(IInvokable)
|
||||||
|
['{B1E8A9C4-1234-4321-ABCD-1234567890AB}']
|
||||||
|
function GetName: string;
|
||||||
|
function Multiply(A, B: Integer): Int64;
|
||||||
|
end;
|
||||||
|
|
||||||
|
IUnknownService = interface(IInvokable)
|
||||||
|
['{DEADBEEF-0000-0000-0000-000000000000}']
|
||||||
|
procedure DoNothing;
|
||||||
|
end;
|
||||||
|
|
||||||
|
// Das Haupt-Interface
|
||||||
|
IMainService = interface(IInvokable)
|
||||||
|
['{A1E8A9C4-1111-2222-3333-444455556666}']
|
||||||
|
// Basic Methods
|
||||||
|
function Add(A, B: Integer): Integer;
|
||||||
|
procedure SetStatus(const Msg: string);
|
||||||
|
|
||||||
|
// Properties
|
||||||
|
function GetCounter: Integer;
|
||||||
|
property Counter: Integer read GetCounter;
|
||||||
|
|
||||||
|
// Recursion / Complex Return Types
|
||||||
|
function CreateSub(const Name: string): ISubService;
|
||||||
|
|
||||||
|
// Edge Cases
|
||||||
|
function EchoFloat(val: Double): Double;
|
||||||
|
end;
|
||||||
|
|
||||||
|
// --- New Interfaces for Corner Cases ---
|
||||||
|
|
||||||
|
// 1. Inheritance Hierarchy
|
||||||
|
IAncestorService = interface(IInvokable)
|
||||||
|
['{11111111-0000-0000-0000-111111111111}']
|
||||||
|
function AncestorMethod: string;
|
||||||
|
end;
|
||||||
|
|
||||||
|
IDescendantService = interface(IAncestorService)
|
||||||
|
['{22222222-0000-0000-0000-222222222222}']
|
||||||
|
function DescendantMethod: string;
|
||||||
|
end;
|
||||||
|
|
||||||
|
// 2. Complex Edge Cases (Void, Errors, Nil, Overloads)
|
||||||
|
IEdgeCaseService = interface(IInvokable)
|
||||||
|
['{EEEEEEEE-0000-0000-0000-EEEEEEEEEEEE}']
|
||||||
|
// Void return (Procedure)
|
||||||
|
procedure DoSomethingVoid;
|
||||||
|
|
||||||
|
// Native Exception
|
||||||
|
procedure TriggerError(const Msg: string);
|
||||||
|
|
||||||
|
// Nil Interface return
|
||||||
|
function GetNullService: IAncestorService;
|
||||||
|
end;
|
||||||
|
|
||||||
|
[TestFixture]
|
||||||
|
[IgnoreMemoryLeaks]
|
||||||
|
TTestRtlTypeRegistry = class
|
||||||
|
private
|
||||||
|
FScope: IExecutionScope;
|
||||||
|
FEnv: TAstEnvironment;
|
||||||
|
public
|
||||||
|
[Setup]
|
||||||
|
procedure Setup;
|
||||||
|
[TearDown]
|
||||||
|
procedure TearDown;
|
||||||
|
|
||||||
|
// --- Original Tests ---
|
||||||
|
[Test]
|
||||||
|
procedure Test_TypeRegistration_Analysis;
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
procedure Test_Factory_And_MethodCall;
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
procedure Test_PropertyAccess;
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
procedure Test_Recursive_Interface_Wrapping;
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
procedure Test_Marshalling_EdgeCases;
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
procedure Test_Error_MissingRegistration;
|
||||||
|
|
||||||
|
// --- New Corner Case Tests ---
|
||||||
|
[Test]
|
||||||
|
procedure Test_Inheritance_CallAncestorMethod;
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
procedure Test_VoidProcedure;
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
procedure Test_NativeException_Propagates;
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
procedure Test_ReturnNilInterface;
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
procedure Test_UnsupportedType_Marshalling;
|
||||||
|
end;
|
||||||
|
|
||||||
|
implementation
|
||||||
|
|
||||||
|
uses
|
||||||
|
System.TypInfo,
|
||||||
|
Myc.Ast.Nodes;
|
||||||
|
|
||||||
|
// --- Mock Implementations ---
|
||||||
|
|
||||||
|
type
|
||||||
|
TSubService = class(TInterfacedObject, ISubService)
|
||||||
|
private
|
||||||
|
FName: string;
|
||||||
|
public
|
||||||
|
constructor Create(const AName: string);
|
||||||
|
function GetName: string;
|
||||||
|
function Multiply(A, B: Integer): Int64;
|
||||||
|
end;
|
||||||
|
|
||||||
|
TMainService = class(TInterfacedObject, IMainService)
|
||||||
|
private
|
||||||
|
FCount: Integer;
|
||||||
|
FLastStatus: string;
|
||||||
|
public
|
||||||
|
constructor Create;
|
||||||
|
function Add(A, B: Integer): Integer;
|
||||||
|
procedure SetStatus(const Msg: string);
|
||||||
|
function GetCounter: Integer;
|
||||||
|
function CreateSub(const Name: string): ISubService;
|
||||||
|
function EchoFloat(val: Double): Double;
|
||||||
|
property LastStatus: string read FLastStatus;
|
||||||
|
end;
|
||||||
|
|
||||||
|
TInheritanceService = class(TInterfacedObject, IDescendantService)
|
||||||
|
public
|
||||||
|
function AncestorMethod: string;
|
||||||
|
function DescendantMethod: string;
|
||||||
|
end;
|
||||||
|
|
||||||
|
TEdgeCaseService = class(TInterfacedObject, IEdgeCaseService)
|
||||||
|
private
|
||||||
|
FVoidCalled: Boolean;
|
||||||
|
public
|
||||||
|
procedure DoSomethingVoid;
|
||||||
|
procedure TriggerError(const Msg: string);
|
||||||
|
function GetNullService: IAncestorService;
|
||||||
|
property VoidCalled: Boolean read FVoidCalled;
|
||||||
|
end;
|
||||||
|
|
||||||
|
{ TSubService }
|
||||||
|
|
||||||
|
constructor TSubService.Create(const AName: string);
|
||||||
|
begin
|
||||||
|
FName := AName;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TSubService.GetName: string;
|
||||||
|
begin
|
||||||
|
Result := FName;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TSubService.Multiply(A, B: Integer): Int64;
|
||||||
|
begin
|
||||||
|
Result := Int64(A) * Int64(B);
|
||||||
|
end;
|
||||||
|
|
||||||
|
{ TMainService }
|
||||||
|
|
||||||
|
constructor TMainService.Create;
|
||||||
|
begin
|
||||||
|
FCount := 0;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TMainService.Add(A, B: Integer): Integer;
|
||||||
|
begin
|
||||||
|
Result := A + B;
|
||||||
|
Inc(FCount);
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TMainService.CreateSub(const Name: string): ISubService;
|
||||||
|
begin
|
||||||
|
Result := TSubService.Create(Name);
|
||||||
|
Inc(FCount);
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TMainService.EchoFloat(val: Double): Double;
|
||||||
|
begin
|
||||||
|
Result := val;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TMainService.GetCounter: Integer;
|
||||||
|
begin
|
||||||
|
Result := FCount;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TMainService.SetStatus(const Msg: string);
|
||||||
|
begin
|
||||||
|
FLastStatus := Msg;
|
||||||
|
end;
|
||||||
|
|
||||||
|
{ TInheritanceService }
|
||||||
|
|
||||||
|
function TInheritanceService.AncestorMethod: string;
|
||||||
|
begin
|
||||||
|
Result := 'Ancestor';
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TInheritanceService.DescendantMethod: string;
|
||||||
|
begin
|
||||||
|
Result := 'Descendant';
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TEdgeCaseService.DoSomethingVoid;
|
||||||
|
begin
|
||||||
|
FVoidCalled := True;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TEdgeCaseService.GetNullService: IAncestorService;
|
||||||
|
begin
|
||||||
|
Result := nil;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TEdgeCaseService.TriggerError(const Msg: string);
|
||||||
|
begin
|
||||||
|
raise Exception.Create(Msg);
|
||||||
|
end;
|
||||||
|
|
||||||
|
// --- Tests ---
|
||||||
|
|
||||||
|
procedure TTestRtlTypeRegistry.Setup;
|
||||||
|
begin
|
||||||
|
// 1. Create Clean Environment
|
||||||
|
FEnv := TAstEnvironment.Construct(nil);
|
||||||
|
FEnv.SetStandardMode;
|
||||||
|
FScope := FEnv.RootScope;
|
||||||
|
|
||||||
|
// 2. Register Standard Mocks
|
||||||
|
TRtlTypeRegistry.RegisterType<ISubService>;
|
||||||
|
TRtlTypeRegistry.RegisterType<IMainService>;
|
||||||
|
|
||||||
|
TRtlTypeRegistry.RegisterFactory<IMainService>(
|
||||||
|
FScope,
|
||||||
|
'create-service',
|
||||||
|
[],
|
||||||
|
function(const Args: TArray<TDataValue>): IMainService begin Result := TMainService.Create; end
|
||||||
|
);
|
||||||
|
|
||||||
|
// 3. Register Corner Case Mocks
|
||||||
|
TRtlTypeRegistry.RegisterType<IAncestorService>;
|
||||||
|
TRtlTypeRegistry.RegisterType<IDescendantService>;
|
||||||
|
TRtlTypeRegistry.RegisterType<IEdgeCaseService>;
|
||||||
|
|
||||||
|
TRtlTypeRegistry.RegisterFactory<IDescendantService>(
|
||||||
|
FScope,
|
||||||
|
'create-descendant',
|
||||||
|
[],
|
||||||
|
function(const Args: TArray<TDataValue>): IDescendantService begin Result := TInheritanceService.Create; end
|
||||||
|
);
|
||||||
|
|
||||||
|
TRtlTypeRegistry.RegisterFactory<IEdgeCaseService>(
|
||||||
|
FScope,
|
||||||
|
'create-edge',
|
||||||
|
[],
|
||||||
|
function(const Args: TArray<TDataValue>): IEdgeCaseService begin Result := TEdgeCaseService.Create; end
|
||||||
|
);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TTestRtlTypeRegistry.TearDown;
|
||||||
|
begin
|
||||||
|
// Interfaces are ref-counted, scope cleanup handles values.
|
||||||
|
FScope := nil;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TTestRtlTypeRegistry.Test_TypeRegistration_Analysis;
|
||||||
|
var
|
||||||
|
staticType: IStaticType;
|
||||||
|
genRec: IGenericRecordDefinition;
|
||||||
|
idx: Integer;
|
||||||
|
methodType: IStaticType;
|
||||||
|
begin
|
||||||
|
// Verify that IMainService was analyzed correctly into an AST definition
|
||||||
|
staticType := TRtlTypeRegistry.GetStaticType(TypeInfo(IMainService));
|
||||||
|
|
||||||
|
Assert.AreNotEqual(TStaticTypeKind.stUnknown, staticType.Kind, 'Type should be known');
|
||||||
|
Assert.AreEqual(TStaticTypeKind.stGenericRecord, staticType.Kind, 'Interface should map to GenericRecord');
|
||||||
|
|
||||||
|
genRec := staticType.GenericDefinition;
|
||||||
|
|
||||||
|
// Check 'Add' method existence
|
||||||
|
idx := genRec.IndexOf(Myc.Data.Keyword.TKeywordRegistry.Intern('Add'));
|
||||||
|
Assert.IsTrue(idx >= 0, 'Method Add should exist in type definition');
|
||||||
|
|
||||||
|
methodType := genRec.Items[idx].Value;
|
||||||
|
Assert.AreEqual(TStaticTypeKind.stMethod, methodType.Kind);
|
||||||
|
// Add has 2 args (Ordinal, Ordinal) -> Ordinal
|
||||||
|
Assert.AreEqual(Int64(2), Length(methodType.Signatures[0].ParamTypes));
|
||||||
|
Assert.AreEqual(TStaticTypeKind.stOrdinal, methodType.Signatures[0].ReturnType.Kind);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TTestRtlTypeRegistry.Test_Factory_And_MethodCall;
|
||||||
|
var
|
||||||
|
script: string;
|
||||||
|
res: TDataValue;
|
||||||
|
begin
|
||||||
|
// Script: Create service, call Add(10, 20)
|
||||||
|
script := '(do ' + ' (def svc (create-service)) ' + ' ((.Add svc) 10 20) ' + ')';
|
||||||
|
|
||||||
|
var root := TAstScript.Parse(script);
|
||||||
|
|
||||||
|
res := FEnv.Run(root);
|
||||||
|
|
||||||
|
Assert.AreEqual(TDataValueKind.vkScalar, res.Kind);
|
||||||
|
Assert.AreEqual(Int64(30), res.AsScalar.Value.AsInt64);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TTestRtlTypeRegistry.Test_PropertyAccess;
|
||||||
|
var
|
||||||
|
script: string;
|
||||||
|
res: TDataValue;
|
||||||
|
begin
|
||||||
|
// Script: Call Add twice (increments counter), then read Counter property
|
||||||
|
script :=
|
||||||
|
'(do '
|
||||||
|
+ ' (def svc (create-service)) '
|
||||||
|
+ ' ((.Add svc) 1 1) '
|
||||||
|
+ ' ((.Add svc) 2 2) '
|
||||||
|
+ ' ((.GetCounter svc)) '
|
||||||
|
+ // Property is a getter-function in script!
|
||||||
|
')';
|
||||||
|
|
||||||
|
var root := TAstScript.Parse(script);
|
||||||
|
res := FEnv.Run(root);
|
||||||
|
|
||||||
|
Assert.AreEqual(vkScalar, res.Kind);
|
||||||
|
Assert.AreEqual(Int64(2), res.AsScalar.Value.AsInt64);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TTestRtlTypeRegistry.Test_Recursive_Interface_Wrapping;
|
||||||
|
var
|
||||||
|
script: string;
|
||||||
|
res: TDataValue;
|
||||||
|
begin
|
||||||
|
// Script:
|
||||||
|
// 1. Create MainService
|
||||||
|
// 2. Call CreateSub("TestSub") -> Returns ISubService (Wrapped automatically!)
|
||||||
|
// 3. Call Multiply on that SubService
|
||||||
|
|
||||||
|
script := '(do ' + ' (def svc (create-service)) ' + ' (def sub ((.CreateSub svc) "TestSub")) ' + ' ((.Multiply sub) 6 7) ' + ')';
|
||||||
|
|
||||||
|
var root := TAstScript.Parse(script);
|
||||||
|
res := FEnv.Run(root);
|
||||||
|
|
||||||
|
Assert.AreEqual(Int64(42), res.AsScalar.Value.AsInt64);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TTestRtlTypeRegistry.Test_Marshalling_EdgeCases;
|
||||||
|
var
|
||||||
|
script: string;
|
||||||
|
res: TDataValue;
|
||||||
|
begin
|
||||||
|
// Test Float Marshalling and String
|
||||||
|
// Script: Pass a float, get it back.
|
||||||
|
|
||||||
|
script := '(do ' + ' (def svc (create-service)) ' + ' ((.EchoFloat svc) 3.1415) ' + ')';
|
||||||
|
|
||||||
|
var root := TAstScript.Parse(script);
|
||||||
|
res := FEnv.Run(root);
|
||||||
|
|
||||||
|
Assert.AreEqual(3.1415, res.AsScalar.Value.AsDouble, 0.0001);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TTestRtlTypeRegistry.Test_Error_MissingRegistration;
|
||||||
|
begin
|
||||||
|
// Try to create factory without registering type
|
||||||
|
Assert.WillRaise(procedure begin TRtlTypeRegistry.RegisterFactory<IUnknownService>(FScope, 'fail', [], nil); end, EInteropException);
|
||||||
|
end;
|
||||||
|
|
||||||
|
// --- New Corner Cases ---
|
||||||
|
|
||||||
|
procedure TTestRtlTypeRegistry.Test_Inheritance_CallAncestorMethod;
|
||||||
|
var
|
||||||
|
script: string;
|
||||||
|
res: TDataValue;
|
||||||
|
begin
|
||||||
|
// Test if we can call a method defined in the parent interface
|
||||||
|
// on an instance of the child interface.
|
||||||
|
script :=
|
||||||
|
'(do '
|
||||||
|
+ ' (def svc (create-descendant)) '
|
||||||
|
+ ' (def val1 ((.DescendantMethod svc))) '
|
||||||
|
+ ' (def val2 ((.AncestorMethod svc))) '
|
||||||
|
+ ' (+ val1 val2) '
|
||||||
|
+ ')';
|
||||||
|
|
||||||
|
var root := TAstScript.Parse(script);
|
||||||
|
res := FEnv.Run(root);
|
||||||
|
|
||||||
|
Assert.AreEqual(TDataValueKind.vkText, res.Kind);
|
||||||
|
Assert.AreEqual('DescendantAncestor', res.AsText);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TTestRtlTypeRegistry.Test_VoidProcedure;
|
||||||
|
var
|
||||||
|
script: string;
|
||||||
|
res: TDataValue;
|
||||||
|
begin
|
||||||
|
// Procedures (void return) should return vkVoid in AST
|
||||||
|
script := '(do ' + ' (def svc (create-edge)) ' + ' ((.DoSomethingVoid svc)) ' + ')';
|
||||||
|
|
||||||
|
var root := TAstScript.Parse(script);
|
||||||
|
res := FEnv.Run(root);
|
||||||
|
|
||||||
|
Assert.IsTrue(res.IsVoid, 'Procedures should return Void TDataValue');
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TTestRtlTypeRegistry.Test_NativeException_Propagates;
|
||||||
|
var
|
||||||
|
script: string;
|
||||||
|
begin
|
||||||
|
// Verify that a Delphi exception crashes the script execution properly
|
||||||
|
script := '(do ' + ' (def svc (create-edge)) ' + ' ((.TriggerError svc) "Boom!") ' + ')';
|
||||||
|
|
||||||
|
var root := TAstScript.Parse(script);
|
||||||
|
|
||||||
|
Assert.WillRaise(procedure begin FEnv.Run(root); end, EEvaluatorException, 'Boom!');
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TTestRtlTypeRegistry.Test_ReturnNilInterface;
|
||||||
|
var
|
||||||
|
script: string;
|
||||||
|
res: TDataValue;
|
||||||
|
begin
|
||||||
|
// If a Delphi function returns nil, the AST value should be Void or handle it gracefully
|
||||||
|
script := '(do ' + ' (def svc (create-edge)) ' + ' ((.GetNullService svc)) ' + ')';
|
||||||
|
|
||||||
|
var root := TAstScript.Parse(script);
|
||||||
|
res := FEnv.Run(root);
|
||||||
|
|
||||||
|
if res.Kind = vkInterface then
|
||||||
|
Assert.IsNull(res.AsIntf<IInterface>, 'Returned interface should be nil')
|
||||||
|
else
|
||||||
|
Assert.IsTrue(res.IsVoid, 'Nil result should be mapped to Void');
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TTestRtlTypeRegistry.Test_UnsupportedType_Marshalling;
|
||||||
|
begin
|
||||||
|
// This expects the runtime to throw an error when converting TValue or invoking
|
||||||
|
// due to invalid argument types (passing string where int is expected).
|
||||||
|
var script := '(do ' + ' (def svc (create-edge)) ' + ' ((.Calc svc) "Not a number") ' + ')';
|
||||||
|
|
||||||
|
var root := TAstScript.Parse(script);
|
||||||
|
|
||||||
|
Assert.WillRaise(procedure begin FEnv.Run(root); end);
|
||||||
|
end;
|
||||||
|
|
||||||
|
end.
|
||||||
+2
-1
@@ -32,7 +32,8 @@ uses
|
|||||||
Test.Myc.Ast.Script in 'AST\Test.Myc.Ast.Script.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',
|
Test.Myc.Ast.Compiler.Macros in 'AST\Test.Myc.Ast.Compiler.Macros.pas',
|
||||||
Test.Myc.Ast.RTL.DateTime in 'AST\Test.Myc.Ast.RTL.DateTime.pas',
|
Test.Myc.Ast.RTL.DateTime in 'AST\Test.Myc.Ast.RTL.DateTime.pas',
|
||||||
Test.Myc.Ast.Compiler.Binder in '..\Src\AST\Test.Myc.Ast.Compiler.Binder.pas';
|
Test.Myc.Ast.Compiler.Binder in '..\Src\AST\Test.Myc.Ast.Compiler.Binder.pas',
|
||||||
|
Test.Myc.Ast.RTL.TypeRegistry in 'AST\Test.Myc.Ast.RTL.TypeRegistry.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}
|
||||||
|
|||||||
@@ -134,6 +134,7 @@ $(PreBuildEvent)]]></PreBuildEvent>
|
|||||||
<DCCReference Include="AST\Test.Myc.Ast.Compiler.Macros.pas"/>
|
<DCCReference Include="AST\Test.Myc.Ast.Compiler.Macros.pas"/>
|
||||||
<DCCReference Include="AST\Test.Myc.Ast.RTL.DateTime.pas"/>
|
<DCCReference Include="AST\Test.Myc.Ast.RTL.DateTime.pas"/>
|
||||||
<DCCReference Include="..\Src\AST\Test.Myc.Ast.Compiler.Binder.pas"/>
|
<DCCReference Include="..\Src\AST\Test.Myc.Ast.Compiler.Binder.pas"/>
|
||||||
|
<DCCReference Include="AST\Test.Myc.Ast.RTL.TypeRegistry.pas"/>
|
||||||
<BuildConfiguration Include="Base">
|
<BuildConfiguration Include="Base">
|
||||||
<Key>Base</Key>
|
<Key>Base</Key>
|
||||||
</BuildConfiguration>
|
</BuildConfiguration>
|
||||||
|
|||||||
@@ -23,8 +23,6 @@ type
|
|||||||
[TestCase('Float_Negative', '-3.1415926535,Float')]
|
[TestCase('Float_Negative', '-3.1415926535,Float')]
|
||||||
procedure TestScalarCreation(const AValueStr: string; AExpectedKind: TScalar.TKind);
|
procedure TestScalarCreation(const AValueStr: string; AExpectedKind: TScalar.TKind);
|
||||||
|
|
||||||
[Test]
|
|
||||||
procedure TestScalarTuple;
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
implementation
|
implementation
|
||||||
@@ -56,25 +54,6 @@ begin
|
|||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TTestPOD.TestScalarTuple;
|
|
||||||
var
|
|
||||||
tuple: TScalarTuple;
|
|
||||||
begin
|
|
||||||
// A tuple is a heterogeneous array of TScalar
|
|
||||||
SetLength(tuple, 3);
|
|
||||||
tuple[0] := TScalar.FromInt64(123);
|
|
||||||
tuple[1] := TScalar.FromDouble(3.14);
|
|
||||||
tuple[2] := TScalar.FromInt64(-456);
|
|
||||||
|
|
||||||
Assert.AreEqual(Int64(3), Length(tuple), 'Tuple length mismatch');
|
|
||||||
Assert.AreEqual(TScalar.TKind.Ordinal, tuple[0].Kind, 'Tuple item 0 kind mismatch');
|
|
||||||
Assert.AreEqual(Int64(123), tuple[0].Value.AsInt64, 'Tuple item 0 value mismatch');
|
|
||||||
Assert.AreEqual(TScalar.TKind.Float, tuple[1].Kind, 'Tuple item 1 kind mismatch');
|
|
||||||
Assert.AreEqual(3.14, tuple[1].Value.AsDouble, 1E-12, 'Tuple item 1 value mismatch');
|
|
||||||
Assert.AreEqual(TScalar.TKind.Ordinal, tuple[2].Kind, 'Tuple item 2 kind mismatch');
|
|
||||||
Assert.AreEqual(Int64(-456), tuple[2].Value.AsInt64, 'Tuple item 2 value mismatch');
|
|
||||||
end;
|
|
||||||
|
|
||||||
initialization
|
initialization
|
||||||
FormatSettings.DecimalSeparator := '.';
|
FormatSettings.DecimalSeparator := '.';
|
||||||
TDUnitX.RegisterTestFixture(TTestPOD);
|
TDUnitX.RegisterTestFixture(TTestPOD);
|
||||||
|
|||||||
Reference in New Issue
Block a user