Files
MycLib/Src/AST/Myc.Ast.RTL.pas
T
2025-09-18 14:23:19 +02:00

204 lines
7.2 KiB
ObjectPascal

unit Myc.Ast.RTL;
interface
// This unit is intended to be included in a 'uses' clause.
// It self-registers its functions via its initialization section.
uses
System.SysUtils,
System.Math,
Myc.Data.Scalar,
Myc.Data.Value,
Myc.Data.Decimal,
Myc.Ast,
Myc.Ast.Nodes,
Myc.Ast.Scope;
implementation
type
// Implements a lazily evaluated, read-only series based on a source series and a mapper function.
TMapSeries = class(TInterfacedObject, ISeries)
private
FSourceSeries: ISeries;
FMapperFunc: TDataValue.TFunc;
// ISeries
function GetCount: Int64;
function GetTotalCount: Int64;
function GetItems(Idx: Integer): TScalar;
public
constructor Create(const ASourceSeries: ISeries; const AMapperFunc: TDataValue.TFunc);
end;
{ TMapSeries }
constructor TMapSeries.Create(const ASourceSeries: ISeries; const AMapperFunc: TDataValue.TFunc);
begin
inherited Create;
FSourceSeries := ASourceSeries;
FMapperFunc := AMapperFunc;
end;
function TMapSeries.GetCount: Int64;
begin
Result := FSourceSeries.Count;
end;
function TMapSeries.GetTotalCount: Int64;
begin
Result := FSourceSeries.TotalCount;
end;
function TMapSeries.GetItems(Idx: Integer): TScalar;
var
sourceValue: TScalar;
argArray: TArray<TDataValue>;
mappedValue: TDataValue;
begin
// Get the original item, apply the mapper function, and return the result.
sourceValue := FSourceSeries.Items[Idx];
argArray := [TDataValue(sourceValue)];
mappedValue := FMapperFunc(argArray);
if mappedValue.Kind <> vkScalar then
raise EInvalidCast.Create('Map function did not return a scalar value.');
Result := mappedValue.AsScalar;
end;
//--------------------------------------------------------------------------------------------------
//== Native Function Implementations
//--------------------------------------------------------------------------------------------------
// A helper to simplify argument validation for unary numeric functions.
function GetSingleNumericArg(const AName: string; const AArgs: TArray<TDataValue>; out AArg: TScalar): Boolean;
begin
if Length(AArgs) <> 1 then
raise EArgumentException.CreateFmt('%s requires exactly one argument.', [AName]);
if AArgs[0].Kind <> vkScalar then
raise EArgumentException.CreateFmt('%s requires a scalar argument.', [AName]);
AArg := AArgs[0].AsScalar;
if not (AArg.Kind in [skInteger, skInt64, skSingle, skDouble, skDecimal]) then
raise EArgumentException.CreateFmt('%s requires a numeric argument, but got %s.', [AName, AArg.Kind.ToString]);
Result := True;
end;
function NativeAbs(const Args: TArray<TDataValue>): TDataValue;
var
arg: TScalar;
begin
GetSingleNumericArg('Abs', Args, arg);
case arg.Kind of
skInteger: Result := TScalar.FromInteger(Abs(arg.Value.AsInteger));
skInt64: Result := TScalar.FromInt64(Abs(arg.Value.AsInt64));
skSingle: Result := TScalar.FromSingle(Abs(arg.Value.AsSingle));
skDouble: Result := TScalar.FromDouble(Abs(arg.Value.AsDouble));
skDecimal: Result := TScalar.FromDecimal(arg.Value.AsDecimal.Abs);
else
// Should be caught by GetSingleNumericArg, but as a safeguard:
raise EArgumentException.Create('Abs requires a numeric argument.');
end;
end;
function NativeTrunc(const Args: TArray<TDataValue>): TDataValue;
var
arg: TScalar;
begin
GetSingleNumericArg('Trunc', Args, arg);
case arg.Kind of
skInteger, skInt64: Result := arg; // Trunc on an integer is a no-op
skSingle: Result := TScalar.FromInt64(Trunc(arg.Value.AsSingle));
skDouble: Result := TScalar.FromInt64(Trunc(arg.Value.AsDouble));
skDecimal: Result := TScalar.FromInt64(Trunc(arg.Value.AsDecimal));
else
raise EArgumentException.Create('Trunc requires a numeric argument.');
end;
end;
function NativeCeil(const Args: TArray<TDataValue>): TDataValue;
var
arg: TScalar;
begin
GetSingleNumericArg('Ceil', Args, arg);
case arg.Kind of
skInteger, skInt64: Result := arg; // Ceil on an integer is a no-op
skSingle: Result := TScalar.FromInt64(Ceil(arg.Value.AsSingle));
skDouble: Result := TScalar.FromInt64(Ceil(arg.Value.AsDouble));
skDecimal: Result := TScalar.FromInt64(Ceil(Double(arg.Value.AsDecimal)));
else
raise EArgumentException.Create('Ceil requires a numeric argument.');
end;
end;
function NativeFloor(const Args: TArray<TDataValue>): TDataValue;
var
arg: TScalar;
begin
GetSingleNumericArg('Floor', Args, arg);
case arg.Kind of
skInteger, skInt64: Result := arg; // Floor on an integer is a no-op
skSingle: Result := TScalar.FromInt64(Floor(arg.Value.AsSingle));
skDouble: Result := TScalar.FromInt64(Floor(arg.Value.AsDouble));
skDecimal: Result := TScalar.FromInt64(Floor(Double(arg.Value.AsDecimal)));
else
raise EArgumentException.Create('Floor requires a numeric argument.');
end;
end;
function NativeSign(const Args: TArray<TDataValue>): TDataValue;
var
arg: TScalar;
begin
GetSingleNumericArg('Sign', Args, arg);
case arg.Kind of
skInteger: Result := TScalar.FromInteger(Sign(arg.Value.AsInteger));
skInt64: Result := TScalar.FromInteger(Sign(arg.Value.AsInt64));
skSingle: Result := TScalar.FromInteger(Sign(arg.Value.AsSingle));
skDouble: Result := TScalar.FromInteger(Sign(arg.Value.AsDouble));
skDecimal: Result := TScalar.FromInteger(arg.Value.AsDecimal.Sign);
else
raise EArgumentException.Create('Sign requires a numeric argument.');
end;
end;
// Maps a series to a new series by applying a function to each element.
function NativeMap(const Args: TArray<TDataValue>): TDataValue;
var
sourceArg: TDataValue;
begin
if Length(Args) <> 2 then
raise EArgumentException.Create('Map requires exactly two arguments: a series and a function.');
sourceArg := Args[0];
if not (sourceArg.Kind in [vkSeries]) then
raise EArgumentException.Create('The first argument to Map must be a series.');
if Args[1].Kind <> vkMethod then
raise EArgumentException.Create('The second argument to Map must be a function.');
Result := TDataValue.FromSeries(TMapSeries.Create(sourceArg.AsSeries, Args[1].AsMethod()));
end;
//--------------------------------------------------------------------------------------------------
//== Library Registration
//--------------------------------------------------------------------------------------------------
procedure RegisterRtlFunctions(const AScope: IExecutionScope);
begin
AScope.Define('Abs', TDataValue(NativeAbs));
AScope.Define('Trunc', TDataValue(NativeTrunc));
AScope.Define('Ceil', TDataValue(NativeCeil));
AScope.Define('Floor', TDataValue(NativeFloor));
AScope.Define('Sign', TDataValue(NativeSign));
AScope.Define('Map', TDataValue(NativeMap));
end;
initialization
// Register this library's functions with the central AST factory.
TAst.RegisterLibrary(RegisterRtlFunctions);
end.