Files
MycLib/Src/AST/Myc.Ast.RTL.pas
T
2025-09-18 16:09:51 +02:00

294 lines
10 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
uses
System.Generics.Collections;
//--------------------------------------------------------------------------------------------------
//== 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(TDataValue.Map(sourceArg.AsSeries, Args[1].AsMethod()));
end;
// Reduces a series into a single value.
function NativeReduce(const Args: TArray<TDataValue>): TDataValue;
var
sourceArg: TDataValue;
sourceSeries: ISeries;
accumulator: TDataValue;
reducerFunc: TDataValue.TFunc;
i: Integer;
currentItem: TScalar;
reducerArgs: TArray<TDataValue>;
begin
if Length(Args) <> 3 then
raise EArgumentException.Create('Reduce requires exactly three arguments: a series, an initial value, and a reducer function.');
sourceArg := Args[0];
if sourceArg.Kind <> vkSeries then
raise EArgumentException.Create('The first argument to Reduce must be a series.');
if Args[2].Kind <> vkMethod then
raise EArgumentException.Create('The third argument to Reduce must be a function.');
// Extract arguments
sourceSeries := sourceArg.AsSeries;
accumulator := Args[1];
reducerFunc := Args[2].AsMethod();
// Iterate from oldest to newest element (index Count-1 to 0)
for i := sourceSeries.Count - 1 downto 0 do
begin
currentItem := sourceSeries.Items[i];
reducerArgs := [accumulator, TDataValue(currentItem)];
accumulator := reducerFunc(reducerArgs);
end;
Result := accumulator;
end;
// Filters a series based on a predicate.
function NativeWhere(const Args: TArray<TDataValue>): TDataValue;
var
sourceArg: TDataValue;
sourceSeries: ISeries;
predicateFunc: TDataValue.TFunc;
matchingIndices: TList<Integer>;
i: Integer;
item: TScalar;
predicateResult: TDataValue;
indexSeries: ISeries;
mapperFunc: TDataValue.TFunc;
finalSeries: ISeries;
begin
if Length(Args) <> 2 then
raise EArgumentException.Create('Where requires exactly two arguments: a series and a predicate function.');
sourceArg := Args[0];
if sourceArg.Kind <> vkSeries then
raise EArgumentException.Create('The first argument to Where must be a series.');
if Args[1].Kind <> vkMethod then
raise EArgumentException.Create('The second argument to Where must be a function.');
sourceSeries := sourceArg.AsSeries;
predicateFunc := Args[1].AsMethod();
// 1. Eagerly build the index of matching items
matchingIndices := TList<Integer>.Create;
try
for i := sourceSeries.Count - 1 downto 0 do
begin
item := sourceSeries.Items[i];
predicateResult := predicateFunc([TDataValue(item)]);
if (predicateResult.Kind = vkScalar) and (predicateResult.AsScalar.Value.AsBoolean) then
matchingIndices.Add(i);
end;
// 2. Create a helper series from the index
indexSeries := TIndexSeries.Create(matchingIndices.ToArray);
finally
matchingIndices.Free;
end;
// 3. Define a mapper to transform an index back to a value from the original series
mapperFunc :=
function(const AArgs: TArray<TDataValue>): TDataValue
var
idx: Integer;
begin
idx := AArgs[0].AsScalar.Value.AsInteger;
Result := TDataValue(sourceSeries.Items[idx]);
end;
// 4. Use TMapSeries to create the final, filtered series
finalSeries := TDataValue.Map(indexSeries, mapperFunc);
Result := TDataValue.FromSeries(finalSeries);
end;
// Checks if any element in a series satisfies a predicate.
function NativeAny(const Args: TArray<TDataValue>): TDataValue;
var
sourceArg: TDataValue;
sourceSeries: ISeries;
predicateFunc: TDataValue.TFunc;
i: Integer;
item: TScalar;
predicateResult: TDataValue;
begin
if Length(Args) <> 2 then
raise EArgumentException.Create('Any requires exactly two arguments: a series and a predicate function.');
sourceArg := Args[0];
if sourceArg.Kind <> vkSeries then
raise EArgumentException.Create('The first argument to Any must be a series.');
if Args[1].Kind <> vkMethod then
raise EArgumentException.Create('The second argument to Any must be a function.');
sourceSeries := sourceArg.AsSeries;
predicateFunc := Args[1].AsMethod();
for i := 0 to sourceSeries.Count - 1 do
begin
item := sourceSeries.Items[i];
predicateResult := predicateFunc([TDataValue(item)]);
if (predicateResult.Kind = vkScalar) and (predicateResult.AsScalar.Value.AsBoolean) then
begin
Result := TScalar.FromBoolean(True);
exit;
end;
end;
Result := TScalar.FromBoolean(False);
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));
AScope.Define('Reduce', TDataValue(NativeReduce));
AScope.Define('Where', TDataValue(NativeWhere));
AScope.Define('Any', TDataValue(NativeAny));
end;
initialization
// Register this library's functions with the central AST factory.
TAst.RegisterLibrary(RegisterRtlFunctions);
end.