Integrated Boolean and DateTime as core types

This commit is contained in:
Michael Schimmel
2025-11-23 22:34:10 +01:00
parent bcd20df29e
commit 2f87444827
9 changed files with 105 additions and 58 deletions
+13 -4
View File
@@ -505,8 +505,11 @@ begin
newElse := Accept(Node.ElseBranch);
conditionType := newCond.AsTypedNode.StaticType;
if (conditionType.Kind <> stUnknown) and not TTypeRules.CanAssign(TTypes.Ordinal, conditionType) then
raise ETypeException.CreateFmt('If condition must be Ordinal, but got %s', [conditionType.ToString]);
// Ordinal and Boolean are valid for if-conditions
if (conditionType.Kind <> stUnknown)
and not TTypeRules.CanAssign(TTypes.Ordinal, conditionType)
and not TTypeRules.CanAssign(TTypes.Boolean, conditionType) then
raise ETypeException.CreateFmt('If condition must be Boolean or Ordinal, but got %s', [conditionType.ToString]);
thenType := newThen.AsTypedNode.StaticType;
elseType :=
@@ -528,8 +531,10 @@ begin
newElse := Accept(Node.ElseBranch);
conditionType := newCond.AsTypedNode.StaticType;
if (conditionType.Kind <> stUnknown) and not TTypeRules.CanAssign(TTypes.Ordinal, conditionType) then
raise ETypeException.CreateFmt('Ternary condition must be Ordinal, but got %s', [conditionType.ToString]);
if (conditionType.Kind <> stUnknown)
and not TTypeRules.CanAssign(TTypes.Ordinal, conditionType)
and not TTypeRules.CanAssign(TTypes.Boolean, conditionType) then
raise ETypeException.CreateFmt('Ternary condition must be Boolean or Ordinal, but got %s', [conditionType.ToString]);
thenType := newThen.AsTypedNode.StaticType;
elseType := newElse.AsTypedNode.StaticType;
@@ -641,6 +646,10 @@ begin
scalarKind := TScalar.TKind.Float
else if (valType.Kind = stKeyword) then
scalarKind := TScalar.TKind.Keyword
else if (valType.Kind = stBoolean) then
scalarKind := TScalar.TKind.Boolean
else if (valType.Kind = stDateTime) then
scalarKind := TScalar.TKind.DateTime
else
begin
allScalar := False;
+1
View File
@@ -169,6 +169,7 @@ begin
TScalar.TKind.Float: Result := AValue.AsScalar.Value.AsDouble <> 0.0;
TScalar.TKind.Keyword: Result := AValue.AsScalar.Value.AsInt64 <> 0;
TScalar.TKind.Boolean: Result := AValue.AsScalar.Value.AsInt64 <> 0;
TScalar.TKind.DateTime: Result := AValue.AsScalar.Value.AsDouble <> 0.0;
else
Result := false;
end;
+5 -3
View File
@@ -93,7 +93,6 @@ uses
constructor TJsonAstConverter.Create;
begin
inherited;
// FJsonObjectStack removed
end;
function TJsonAstConverter.Deserialize(const AJson: TJSONObject): IAstNode;
@@ -119,6 +118,8 @@ begin
case AValue.AsScalar.Kind of
TScalar.TKind.Ordinal: scalarObj.AddPair('Value', TJSONNumber.Create(AValue.AsScalar.Value.AsInt64));
TScalar.TKind.Float: scalarObj.AddPair('Value', TJSONNumber.Create(AValue.AsScalar.Value.AsDouble));
TScalar.TKind.Boolean: scalarObj.AddPair('Value', TJSONBool.Create(AValue.AsScalar.Value.AsInt64 <> 0));
TScalar.TKind.DateTime: scalarObj.AddPair('Value', TJSONNumber.Create(AValue.AsScalar.Value.AsDouble));
TScalar.TKind.Keyword:
begin
// Use reverse map to get name from index (AsInt64)
@@ -433,7 +434,6 @@ function TJsonAstConverter.VisitNop(const Node: INopNode): TJSONObject;
begin
Result := TJSONObject.Create;
Result.AddPair('NodeType', TJSONString.Create('Nop'));
// No other properties needed, as it is stateless
end;
{ TJsonAstConverter - Deserialization }
@@ -455,6 +455,8 @@ begin
case scalarKind of
TScalar.TKind.Ordinal: Result := TScalar.FromInt64(scalarObj.GetValue<TJSONNumber>('Value').AsInt64);
TScalar.TKind.Float: Result := TScalar.FromDouble(scalarObj.GetValue<TJSONNumber>('Value').AsDouble);
TScalar.TKind.Boolean: Result := TScalar.FromBoolean(scalarObj.GetValue<TJSONBool>('Value').AsBoolean);
TScalar.TKind.DateTime: Result := TScalar.FromDateTime(scalarObj.GetValue<TJSONNumber>('Value').AsDouble);
TScalar.TKind.Keyword:
// Keywords are serialized by name, intern them back
Result := TScalar.FromKeyword(TKeywordRegistry.Intern(scalarObj.GetValue<string>('Value')));
@@ -765,7 +767,7 @@ begin
Result := JsonToAddSeriesItemNode(obj)
else if nodeType = 'SeriesLength' then
Result := JsonToSeriesLengthNode(obj)
else if nodeType = 'Nop' then // Added Nop Deserialization logic
else if nodeType = 'Nop' then
Result := JsonToNopNode(obj)
else
raise ENotSupportedException.CreateFmt('Unsupported NodeType "%s" for JSON deserialization.', [nodeType]);
+3 -4
View File
@@ -109,7 +109,7 @@ type
function VisitNop(const Node: INopNode): TDataValue;
end;
IAstNode = interface(IInterface)
IAstNode = interface
{$region 'private'}
function GetKind: TAstNodeKind;
function GetIsTyped: Boolean;
@@ -162,7 +162,7 @@ type
{$endregion}
property Body: IAstNode read GetBody;
property Parameters: TArray<IIdentifierNode> read GetParameters;
property IsPure: Boolean read GetIsPure; // (* ADDED *)
property IsPure: Boolean read GetIsPure;
end;
INopNode = interface(IAstTypedNode)
@@ -170,7 +170,6 @@ type
end;
IConstantNode = interface(IAstTypedNode)
// Represents a constant value in the AST (Scalar, Text, or Void).
{$region 'private'}
function GetValue: TDataValue;
{$endregion}
@@ -256,7 +255,7 @@ type
property Arguments: TArray<IAstNode> read GetArguments;
property IsTailCall: Boolean read GetIsTailCall;
property StaticTarget: TDataValue.TFunc read GetStaticTarget;
property IsTargetPure: Boolean read GetIsTargetPure; // (* ADDED *)
property IsTargetPure: Boolean read GetIsTargetPure;
end;
// A node representing a macro call.
+25 -9
View File
@@ -62,7 +62,7 @@ type
[TRtlExport('Abs')]
class function Abs(const Arg: TScalar): TScalar; static;
[TRtlExport('Round')]
class function Round(const Arg: TScalar): TScalar; static; // <-- Added
class function Round(const Arg: TScalar): TScalar; static;
[TRtlExport('Trunc')]
class function Trunc(const Arg: TScalar): TScalar; static;
[TRtlExport('Ceil')]
@@ -225,9 +225,9 @@ type
class function Abs_Float_Float(A: Double): Double; static;
[TRtlExport('Round', True)]
class function Round_Ordinal_Ordinal(A: Int64): Int64; static; // <-- Added
class function Round_Ordinal_Ordinal(A: Int64): Int64; static;
[TRtlExport('Round', True)]
class function Round_Float_Ordinal(A: Double): Int64; static; // <-- Added
class function Round_Float_Ordinal(A: Double): Int64; static;
[TRtlExport('Trunc', True)]
class function Trunc_Ordinal_Ordinal(A: Int64): Int64; static;
@@ -383,7 +383,7 @@ class function TRtlFunctions.Abs(const Arg: TScalar): TScalar;
begin
case Arg.Kind of
TScalar.TKind.Ordinal: Result := TScalar.FromInt64(System.Abs(Arg.Value.AsInt64));
TScalar.TKind.Float: Result := TScalar.FromDouble(System.Abs(Arg.Value.AsDouble));
TScalar.TKind.Float, TScalar.TKind.DateTime: Result := TScalar.FromDouble(System.Abs(Arg.Value.AsDouble));
else
raise EArgumentException.Create('Abs requires a numeric argument.');
end;
@@ -391,21 +391,37 @@ end;
class function TRtlFunctions.Round(const Arg: TScalar): TScalar;
begin
var val: Double := Arg;
case Arg.Kind of
TScalar.TKind.Ordinal: Result := Arg;
TScalar.TKind.Float, TScalar.TKind.DateTime:
begin
var val: Double := Arg.Value.AsDouble;
Result := TScalar.FromInt64(System.Round(val));
end;
else
raise EArgumentException.Create('Round requires a numeric argument.');
end;
end;
class function TRtlFunctions.Trunc(const Arg: TScalar): TScalar;
begin
var val: Double := Arg;
case Arg.Kind of
TScalar.TKind.Ordinal: Result := Arg;
TScalar.TKind.Float, TScalar.TKind.DateTime:
begin
var val: Double := Arg.Value.AsDouble;
Result := TScalar.FromInt64(System.Trunc(val));
end;
else
raise EArgumentException.Create('Trunc requires a numeric argument.');
end;
end;
class function TRtlFunctions.Ceil(const Arg: TScalar): TScalar;
begin
case Arg.Kind of
TScalar.TKind.Ordinal: Result := Arg;
TScalar.TKind.Float: Result := TScalar.FromInt64(System.Math.Ceil(Arg.Value.AsDouble));
TScalar.TKind.Float, TScalar.TKind.DateTime: Result := TScalar.FromInt64(System.Math.Ceil(Arg.Value.AsDouble));
else
raise EArgumentException.Create('Ceil requires a numeric argument.');
end;
@@ -415,7 +431,7 @@ class function TRtlFunctions.Floor(const Arg: TScalar): TScalar;
begin
case Arg.Kind of
TScalar.TKind.Ordinal: Result := Arg;
TScalar.TKind.Float: Result := TScalar.FromInt64(System.Math.Floor(Arg.Value.AsDouble));
TScalar.TKind.Float, TScalar.TKind.DateTime: Result := TScalar.FromInt64(System.Math.Floor(Arg.Value.AsDouble));
else
raise EArgumentException.Create('Floor requires a numeric argument.');
end;
@@ -425,7 +441,7 @@ class function TRtlFunctions.Sign(const Arg: TScalar): TScalar;
begin
case Arg.Kind of
TScalar.TKind.Ordinal: Result := TScalar.FromInt64(System.Math.Sign(Arg.Value.AsInt64));
TScalar.TKind.Float: Result := TScalar.FromInt64(System.Math.Sign(Arg.Value.AsDouble));
TScalar.TKind.Float, TScalar.TKind.DateTime: Result := TScalar.FromInt64(System.Math.Sign(Arg.Value.AsDouble));
else
raise EArgumentException.Create('Sign requires a numeric argument.');
end;
+8 -2
View File
@@ -47,7 +47,7 @@ type
public
Target: TDataValue.TFunc;
ReturnType: IStaticType;
IsPure: Boolean; // (* ADDED *)
IsPure: Boolean;
constructor Create(const ATarget: TDataValue.TFunc; const AReturnType: IStaticType; AIsPure: Boolean);
end;
@@ -344,7 +344,6 @@ begin
else if (not Assigned(funcInfo.DynamicWrapper)) and (Length(argTypes) = 2) and (argTypes[0].Kind = stUnknown) then
begin
// Potentially handle 2-arg scalar wrappers here too if needed
// For now, Abs/Trunc are 1-arg TScalar->TScalar, which matches stUnknown check above.
end;
end
else
@@ -424,6 +423,10 @@ var
Result := TTypes.Float
else if SameText(S, 'Keyword') then
Result := TTypes.Keyword
else if SameText(S, 'Boolean') then
Result := TTypes.Boolean
else if SameText(S, 'DateTime') then
Result := TTypes.DateTime
else
Result := TTypes.Unknown;
end;
@@ -739,6 +742,9 @@ end;
procedure RegisterRtlFunctions(const AScope: IExecutionScope);
begin
AScope.Define('true', TDataValue(TScalar.FromBoolean(True)), TTypes.Boolean);
AScope.Define('false', TDataValue(TScalar.FromBoolean(False)), TTypes.Boolean);
TRtlRegistry.RegisterAll(AScope);
end;
+46 -29
View File
@@ -17,6 +17,8 @@ type
stVoid,
stOrdinal, // Int64
stFloat, // Double
stBoolean,
stDateTime,
stText,
stKeyword,
stMethod,
@@ -51,8 +53,7 @@ type
{$region 'private'}
function GetKind: TStaticTypeKind;
function GetElementType: IStaticType;
// function GetSignature: IMethodSignature; // (* REMOVED *)
function GetSignatures: TArray<IMethodSignature>; // (* ADDED *)
function GetSignatures: TArray<IMethodSignature>;
function GetDefinition: IScalarRecordDefinition;
function GetGenericDefinition: IGenericRecordDefinition;
{$endregion}
@@ -92,6 +93,10 @@ type
FOrdinal: IStaticType;
class var
FFloat: IStaticType;
class var
FBoolean: IStaticType;
class var
FDateTime: IStaticType;
class var
FText: IStaticType;
class var
@@ -103,6 +108,8 @@ type
class property Void: IStaticType read FVoid;
class property Ordinal: IStaticType read FOrdinal;
class property Float: IStaticType read FFloat;
class property Boolean: IStaticType read FBoolean;
class property DateTime: IStaticType read FDateTime;
class property Text: IStaticType read FText;
class property Keyword: IStaticType read FKeyword;
@@ -146,7 +153,7 @@ implementation
uses
System.Generics.Defaults,
System.Hash; // Added for TStringBuilder in TMethodType.ToString
System.Hash;
{ TStaticTypeKindHelper }
@@ -157,6 +164,8 @@ begin
stVoid: Result := 'Void';
stOrdinal: Result := 'Ordinal';
stFloat: Result := 'Float';
stBoolean: Result := 'Boolean';
stDateTime: Result := 'DateTime';
stText: Result := 'Text';
stKeyword: Result := 'Keyword';
stMethod: Result := 'Method';
@@ -177,7 +186,7 @@ type
// IStaticType (default implementations for non-applicable properties)
function GetKind: TStaticTypeKind; virtual; abstract;
function GetElementType: IStaticType; virtual;
function GetSignatures: TArray<IMethodSignature>; virtual; // (* CHANGED *)
function GetSignatures: TArray<IMethodSignature>; virtual;
function GetDefinition: IScalarRecordDefinition; virtual;
function GetGenericDefinition: IGenericRecordDefinition; virtual;
function IsEqual(const Other: IStaticType): Boolean; virtual;
@@ -190,9 +199,9 @@ begin
Result := nil;
end;
function TAbstractStaticType.GetSignatures: TArray<IMethodSignature>; // (* CHANGED *)
function TAbstractStaticType.GetSignatures: TArray<IMethodSignature>;
begin
Result := nil; // (* CHANGED *)
Result := nil;
end;
function TAbstractStaticType.GetDefinition: IScalarRecordDefinition;
@@ -342,7 +351,6 @@ end;
// ---
type
// (* ENTIRE TMethodType IMPLEMENTATION REPLACED *)
TMethodType = class(TAbstractStaticType)
private
FSignatures: TArray<IMethodSignature>;
@@ -482,7 +490,7 @@ type
function GetKind: TStaticTypeKind; override;
function GetDefinition: IScalarRecordDefinition; override;
function IsEqual(const Other: IStaticType): Boolean; override;
function GetHashCode: Integer; override; // Added
function GetHashCode: Integer; override;
function ToString: string; override;
end;
@@ -575,7 +583,7 @@ type
function GetKind: TStaticTypeKind; override;
function GetGenericDefinition: IGenericRecordDefinition; override;
function IsEqual(const Other: IStaticType): Boolean; override;
function GetHashCode: Integer; override; // Added
function GetHashCode: Integer; override;
function ToString: string; override;
end;
@@ -669,6 +677,8 @@ begin
FVoid := TSimpleStaticType.Create(stVoid);
FOrdinal := TSimpleStaticType.Create(stOrdinal);
FFloat := TSimpleStaticType.Create(stFloat);
FBoolean := TSimpleStaticType.Create(stBoolean);
FDateTime := TSimpleStaticType.Create(stDateTime);
FText := TSimpleStaticType.Create(stText);
FKeyword := TSimpleStaticType.Create(stKeyword);
end;
@@ -717,6 +727,8 @@ begin
TScalar.TKind.Ordinal: Result := FOrdinal;
TScalar.TKind.Float: Result := FFloat;
TScalar.TKind.Keyword: Result := FKeyword;
TScalar.TKind.Boolean: Result := FBoolean;
TScalar.TKind.DateTime: Result := FDateTime;
else
raise ETypeException.Create('Cannot convert invalid TScalar.TKind to TStaticType.');
end;
@@ -742,6 +754,14 @@ begin
if (Target.Kind = stFloat) and (Source.Kind = stOrdinal) then
exit(True);
// Allow assigning Boolean to Ordinal (0/1)
if (Target.Kind = stOrdinal) and (Source.Kind = stBoolean) then
exit(True);
// Allow assigning DateTime to Float (TDateTime is Double)
if (Target.Kind = stFloat) and (Source.Kind = stDateTime) then
exit(True);
// Allow discarding the return value of a method (e.g., in a 'do' block).
if (Target.Kind = stVoid) and (Source.Kind = stMethod) then
exit(True);
@@ -750,9 +770,6 @@ begin
if (Target.Kind = stOrdinal) and (Source.Kind = stKeyword) then
exit(True);
// Allow assigning a scalar record to a generic record? (Maybe later)
// Allow assigning a generic record to a scalar record? (If fields match)
// Default: Assignment is not allowed if no specific rule matches.
Result := False;
end;
@@ -779,7 +796,8 @@ begin
if (A.Kind = stOrdinal) and (B.Kind = stOrdinal) then
exit(TTypes.Ordinal);
// If types are identical (incl. Keyword, Text, etc.), return that type.
// Implicit DateTime/Boolean logic via Ordinal/Float mapping?
// If types are identical (incl. Keyword, Text, Boolean, DateTime), return that type.
if A.IsEqual(B) then
exit(A);
@@ -787,7 +805,7 @@ begin
if (A.Kind in [stRecord, stGenericRecord]) or (B.Kind in [stRecord, stGenericRecord]) then
raise ETypeException.CreateFmt('Cannot promote types %s and %s', [A.ToString, B.ToString]);
// Cannot promote other combinations (e.g., Ordinal and Keyword)
// Cannot promote other combinations
raise ETypeException.CreateFmt('Cannot promote types %s and %s', [A.ToString, B.ToString]);
end;
@@ -817,8 +835,8 @@ begin
case Op of
TScalar.TBinaryOp.Add, TScalar.TBinaryOp.Subtract, TScalar.TBinaryOp.Multiply:
begin
// Numeric operations require Ordinal or Float
if not (promotedKind in [stOrdinal, stFloat]) then
// Numeric operations require Ordinal, Float, or DateTime(as Float)
if not (promotedKind in [stOrdinal, stFloat, stDateTime]) then
raise ETypeException
.CreateFmt('Operator %s requires Ordinal or Float, but got %s after promotion', [Op.ToString, promotedType.ToString]);
Result := promotedType; // Result has the promoted type
@@ -827,7 +845,7 @@ begin
TScalar.TBinaryOp.Divide:
begin
// Division requires Ordinal or Float, but *always* results in Float
if not (promotedKind in [stOrdinal, stFloat]) then
if not (promotedKind in [stOrdinal, stFloat, stDateTime]) then
raise ETypeException
.CreateFmt('Operator %s requires Ordinal or Float, but got %s after promotion', [Op.ToString, promotedType.ToString]);
Result := TTypes.Float;
@@ -835,24 +853,23 @@ begin
TScalar.TBinaryOp.Equal, TScalar.TBinaryOp.NotEqual:
begin
// Allow equality checks for Ordinal, Float, or Keyword
// (Records are not yet supported for equality)
if not (promotedKind in [stOrdinal, stFloat, stKeyword]) then
// Allow equality checks for Ordinal, Float, Keyword, Boolean, DateTime
if not (promotedKind in [stOrdinal, stFloat, stKeyword, stBoolean, stDateTime]) then
raise ETypeException.CreateFmt(
'Comparison operator %s requires Ordinal, Float, or Keyword, but got %s after promotion',
'Comparison operator %s requires scalar type, but got %s after promotion',
[Op.ToString, promotedType.ToString]);
// Result is always Ordinal (boolean)
Result := TTypes.Ordinal;
// Result is always Boolean
Result := TTypes.Boolean;
end;
TScalar.TBinaryOp.Less, TScalar.TBinaryOp.Greater, TScalar.TBinaryOp.LessOrEqual, TScalar.TBinaryOp.GreaterOrEqual:
begin
// Comparison requires Ordinal or Float (Keywords are not ordered)
if not (promotedKind in [stOrdinal, stFloat]) then
if not (promotedKind in [stOrdinal, stFloat, stDateTime]) then
raise ETypeException.CreateFmt(
'Comparison operator %s requires Ordinal or Float, but got %s after promotion',
'Comparison operator %s requires ordered type (Ordinal, Float, DateTime), but got %s after promotion',
[Op.ToString, promotedType.ToString]);
Result := TTypes.Ordinal;
Result := TTypes.Boolean;
end;
else
raise ETypeException.Create('Unknown binary operator for type resolution.');
@@ -880,10 +897,10 @@ begin
TScalar.TUnaryOp.Not:
begin
// Logical not only applies to Ordinal (booleans)
if not (rightKind = stOrdinal) then
// Logical not only applies to Boolean (or Ordinal if treated as bool)
if not (rightKind in [stOrdinal, stBoolean]) then
raise ETypeException.CreateFmt('Logical not cannot be applied to %s', [Right.ToString]);
Result := TTypes.Ordinal;
Result := TTypes.Boolean;
end;
else
raise ETypeException.Create('Unknown unary operator for type resolution.');
+2 -2
View File
@@ -38,6 +38,7 @@ type
// --- Factory functions ---
class function Constant(const AValue: TDataValue; const AStaticType: IStaticType = nil): IConstantNode; overload; static;
class function Constant(const AValue: String): IConstantNode; overload; static;
class function Keyword(const AName: string): IKeywordNode; static;
class function Identifier(AName: string; const AStaticType: IStaticType = nil): IIdentifierNode; overload; static;
class function Identifier(
@@ -56,7 +57,6 @@ type
const AStaticType: IStaticType = nil
): ITernaryExpressionNode; static;
// (* UPDATED Factory: Added AIsPure *)
class function LambdaExpr(
const AParameters: TArray<IIdentifierNode>;
const ABody: IAstNode;
@@ -64,7 +64,7 @@ type
const ADescriptor: IScopeDescriptor = nil;
const AUpvalues: TArray<TResolvedAddress> = nil;
const AHasNestedLambdas: Boolean = False;
const AIsPure: Boolean = False; // (* ADDED *)
const AIsPure: Boolean = False;
const AStaticType: IStaticType = nil
): ILambdaExpressionNode; static;
-3
View File
@@ -119,9 +119,6 @@ begin
FLock := TSpinLock.Create(false);
FRegistry := TDictionary<string, Integer>.Create;
FReverseMap := TList<IKeyword>.Create;
Intern('false');
Intern('true');
end;
class destructor TKeywordRegistry.Destroy;