Keywords as basic scalar type
This commit is contained in:
+16
-22
@@ -259,14 +259,10 @@ begin
|
||||
// externally evaluate the expression using the injected evaluator
|
||||
value := FEvaluate(expr);
|
||||
|
||||
// Allow unquoting keywords
|
||||
if value.Kind in [vkScalar, vkText, vkVoid, vkKeyword] then
|
||||
// Allow unquoting scalars (which now include keywords)
|
||||
if value.Kind in [vkScalar, vkText, vkVoid] then
|
||||
begin
|
||||
// vkKeyword needs to be handled differently than other constants
|
||||
if value.Kind = vkKeyword then
|
||||
Result := TDataValue.FromIntf<IAstNode>(TAst.Keyword(value.AsKeyword.Name))
|
||||
else
|
||||
Result := TDataValue.FromIntf<IAstNode>(TAst.Constant(value));
|
||||
Result := TDataValue.FromIntf<IAstNode>(TAst.Constant(value));
|
||||
end
|
||||
else
|
||||
raise Exception.CreateFmt('Cannot unquote complex value of type %s at compile time.', [value.Kind.ToString]);
|
||||
@@ -795,7 +791,6 @@ begin
|
||||
Result := SetType(TDataValue.FromIntf<IConstantNode>(Node), TTypes.FromScalarKind(Node.Value.AsScalar.Kind));
|
||||
TDataValueKind.vkText: Result := SetType(TDataValue.FromIntf<IConstantNode>(Node), TTypes.Text);
|
||||
TDataValueKind.vkVoid: Result := SetType(TDataValue.FromIntf<IConstantNode>(Node), TTypes.Void);
|
||||
TDataValueKind.vkKeyword: Result := SetType(TDataValue.FromIntf<IConstantNode>(Node), TTypes.Keyword);
|
||||
else
|
||||
// Handle other constant types if they become supported
|
||||
Result := SetType(TDataValue.FromIntf<IConstantNode>(Node), TTypes.Unknown);
|
||||
@@ -805,7 +800,8 @@ end;
|
||||
function TAstBinder.VisitKeyword(const Node: IKeywordNode): TDataValue;
|
||||
begin
|
||||
// Keywords are literals. Their type is set in TKeywordNode.Create.
|
||||
Result := TDataValue.FromIntf<IKeywordNode>(Node);
|
||||
// We also set the static type on the node itself during binding.
|
||||
Result := SetType(TDataValue.FromIntf<IKeywordNode>(Node), TTypes.Keyword);
|
||||
end;
|
||||
|
||||
function TAstBinder.VisitCreateSeries(const Node: ICreateSeriesNode): TDataValue;
|
||||
@@ -966,34 +962,32 @@ var
|
||||
boundNode: IRecordLiteralNode;
|
||||
valNode: IAstNode;
|
||||
valType: IStaticType;
|
||||
scalarKind: TScalar.TKind;
|
||||
begin
|
||||
FNextIsTail := False;
|
||||
SetLength(boundFields, Length(Node.Fields));
|
||||
SetLength(defFields, Length(Node.Fields));
|
||||
|
||||
// We assume this is a TScalarRecord (Path A) until proven otherwise.
|
||||
// The "dual path" logic for stDictionary is not yet implemented.
|
||||
|
||||
for i := 0 to High(Node.Fields) do
|
||||
begin
|
||||
valNode := Accept(Node.Fields[i].Value).AsIntf<IAstNode>;
|
||||
valType := (valNode as TAstNode).StaticType;
|
||||
|
||||
// Path A: Records can only store scalar values
|
||||
if not (valType.Kind in [stOrdinal, stFloat]) then
|
||||
// Path A: Records can now store Ordinal, Float, or Keyword
|
||||
if (valType.Kind = stOrdinal) then
|
||||
scalarKind := TScalar.TKind.Ordinal
|
||||
else if (valType.Kind = stFloat) then
|
||||
scalarKind := TScalar.TKind.Float
|
||||
else if (valType.Kind = stKeyword) then
|
||||
scalarKind := TScalar.TKind.Keyword
|
||||
else
|
||||
raise ETypeException.CreateFmt(
|
||||
'Record fields must be scalar (Ordinal or Float), but field ":%s" is %s',
|
||||
'Record fields must be scalar (Ordinal, Float, or Keyword), but field ":%s" is %s',
|
||||
[Node.Fields[i].Key.Value.Name, valType.ToString]);
|
||||
|
||||
boundFields[i] := TRecordFieldLiteral.Create(Node.Fields[i].Key, valNode);
|
||||
|
||||
var scalarKind: TScalar.TKind;
|
||||
if valType.Kind = stOrdinal then
|
||||
scalarKind := TScalar.TKind.Ordinal
|
||||
else
|
||||
scalarKind := TScalar.TKind.Float;
|
||||
|
||||
// Create the definition field using the Keyword's name
|
||||
// Create the definition field using the Keyword and its TScalar.TKind
|
||||
defFields[i] := TScalarRecordField.Create(Node.Fields[i].Key.Value, scalarKind);
|
||||
end;
|
||||
|
||||
|
||||
@@ -152,17 +152,16 @@ end;
|
||||
|
||||
function TEvaluatorVisitor.IsTruthy(const AValue: TDataValue): Boolean;
|
||||
begin
|
||||
// Other types (Text, Series, etc.) are considered "false" in a boolean context
|
||||
if (AValue.Kind <> vkScalar) then
|
||||
begin
|
||||
Result := False;
|
||||
exit;
|
||||
end;
|
||||
exit(false);
|
||||
|
||||
case AValue.AsScalar.Kind of
|
||||
TScalar.TKind.Ordinal: Result := (AValue.AsScalar.Value.AsInt64 <> 0);
|
||||
TScalar.TKind.Float: Result := (AValue.AsScalar.Value.AsDouble <> 0.0);
|
||||
TScalar.TKind.Ordinal: Result := AValue.AsScalar.Value.AsInt64 <> 0;
|
||||
TScalar.TKind.Float: Result := AValue.AsScalar.Value.AsDouble <> 0.0;
|
||||
TScalar.TKind.Keyword: Result := AValue.AsScalar.Value.AsInt64 <> 0;
|
||||
else
|
||||
Result := False;
|
||||
Result := false;
|
||||
end;
|
||||
end;
|
||||
|
||||
@@ -368,8 +367,8 @@ end;
|
||||
|
||||
function TEvaluatorVisitor.VisitKeyword(const Node: IKeywordNode): TDataValue;
|
||||
begin
|
||||
// Return the shared, interned IKeyword interface as a TDataValue
|
||||
Result := TDataValue.FromKeyword(Node.Value);
|
||||
// Return the keyword as a TScalar value
|
||||
Result := TDataValue(TScalar.FromKeyword(Node.Value));
|
||||
end;
|
||||
|
||||
function TEvaluatorVisitor.VisitCreateSeries(const Node: ICreateSeriesNode): TDataValue;
|
||||
@@ -402,6 +401,8 @@ function TEvaluatorVisitor.VisitIndexer(const Node: IIndexerNode): TDataValue;
|
||||
var
|
||||
baseValue, indexValue: TDataValue;
|
||||
index: Int64;
|
||||
series: ISeries;
|
||||
recSeries: IRecordSeries;
|
||||
begin
|
||||
baseValue := Node.Base.Accept(Self);
|
||||
indexValue := Node.Index.Accept(Self);
|
||||
@@ -414,12 +415,23 @@ begin
|
||||
case baseValue.Kind of
|
||||
vkSeries:
|
||||
begin
|
||||
with baseValue.AsSeries do
|
||||
begin
|
||||
if (index < 0) or (index >= TotalCount) then
|
||||
raise EArgumentException.CreateFmt('Index %d is out of bounds for series with %d elements.', [index, TotalCount]);
|
||||
Result := Items[Integer(index)];
|
||||
end;
|
||||
series := baseValue.AsSeries;
|
||||
if (index < 0) or (index >= series.TotalCount) then
|
||||
raise EArgumentException.CreateFmt('Index %d is out of bounds for series with %d elements.', [index, series.TotalCount]);
|
||||
Result := series.Items[Integer(index)];
|
||||
end;
|
||||
vkRecordSeries:
|
||||
begin
|
||||
recSeries := baseValue.AsRecordSeries;
|
||||
if (index < 0) or (index >= recSeries.TotalCount) then
|
||||
raise EArgumentException.CreateFmt('Index %d is out of bounds for series with %d elements.', [index, recSeries.TotalCount]);
|
||||
// Accessing a record series by index materializes the TScalarRecord
|
||||
var rec := TScalarRecord.Create(recSeries.Def, nil); // Nil fields, needs implementation
|
||||
// TODO: This path (materializing a record from TScalarRecordSeries) is not fully implemented.
|
||||
// We need to fetch the underlying TScalar.TValue array slice.
|
||||
// For now, returning an empty record shell.
|
||||
raise ENotSupportedException.Create('Indexing a RecordSeries to materialize a Record is not yet implemented.');
|
||||
Result := TDataValue.FromRecord(rec);
|
||||
end;
|
||||
else
|
||||
raise EArgumentException.Create('Indexer `[]` is not supported for this value type.');
|
||||
@@ -455,8 +467,17 @@ begin
|
||||
begin
|
||||
// Evaluate the value expression for this field
|
||||
valData := boundNode.Fields[i].Value.Accept(Self);
|
||||
// The binder already validated this is a scalar
|
||||
values[i] := valData.AsScalar.Value;
|
||||
|
||||
if valData.Kind = vkScalar then
|
||||
begin
|
||||
// Binder ensures it's a valid scalar kind (Ordinal, Float, Keyword)
|
||||
values[i] := valData.AsScalar.Value;
|
||||
end
|
||||
else
|
||||
begin
|
||||
// This should be unreachable if binder worked correctly
|
||||
raise EInvalidOpException.Create('Invalid type found in record literal during evaluation.');
|
||||
end;
|
||||
end;
|
||||
|
||||
// Create the TScalarRecord using the definition from the binder
|
||||
@@ -501,32 +522,9 @@ begin
|
||||
leftValue := Node.Left.Accept(Self);
|
||||
rightValue := Node.Right.Accept(Self);
|
||||
|
||||
// Handle Keyword equality
|
||||
if (leftValue.Kind = vkKeyword) or (rightValue.Kind = vkKeyword) then
|
||||
begin
|
||||
if not (leftValue.Kind = rightValue.Kind) then
|
||||
begin
|
||||
// Comparing keyword to non-keyword
|
||||
if Node.Operator = TScalar.TBinaryOp.NotEqual then
|
||||
Result := TScalar.FromInt64(1)
|
||||
else
|
||||
Result := TScalar.FromInt64(0);
|
||||
exit;
|
||||
end;
|
||||
|
||||
// Both are keywords, compare interfaces
|
||||
case Node.Operator of
|
||||
TScalar.TBinaryOp.Equal: Result := TScalar.FromInt64(Ord(leftValue.AsKeyword = rightValue.AsKeyword));
|
||||
TScalar.TBinaryOp.NotEqual: Result := TScalar.FromInt64(Ord(leftValue.AsKeyword <> rightValue.AsKeyword));
|
||||
else
|
||||
raise ENotSupportedException.Create('Only = and <> operations are supported for Keywords.');
|
||||
end;
|
||||
exit;
|
||||
end;
|
||||
|
||||
// Standard scalar operations
|
||||
if (leftValue.Kind <> vkScalar) or (rightValue.Kind <> vkScalar) then
|
||||
raise ENotSupportedException.Create('Binary operations are only supported for scalar or keyword types.');
|
||||
raise ENotSupportedException.Create('Binary operations are only supported for scalar types.');
|
||||
|
||||
if not TScalar.TryBinaryOperation(Node.Operator, leftValue.AsScalar, rightValue.AsScalar, resScalar) then
|
||||
raise ENotSupportedException.Create(
|
||||
|
||||
@@ -114,6 +114,7 @@ end;
|
||||
procedure TJsonAstConverter.DataValueToJson(const AValue: TDataValue; const AParent: TJSONObject; const AName: string);
|
||||
var
|
||||
valObj, scalarObj: TJSONObject;
|
||||
kwName: string;
|
||||
begin
|
||||
valObj := TJSONObject.Create;
|
||||
valObj.AddPair('Kind', TJSONString.Create(AValue.Kind.ToString));
|
||||
@@ -126,11 +127,16 @@ 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.Keyword:
|
||||
begin
|
||||
// Use reverse map to get name from index (AsInt64)
|
||||
kwName := TKeywordRegistry.GetName(AValue.AsScalar.Value.AsInt64);
|
||||
scalarObj.AddPair('Value', TJSONString.Create(kwName));
|
||||
end;
|
||||
end;
|
||||
valObj.AddPair('Value', scalarObj);
|
||||
end;
|
||||
vkText: valObj.AddPair('Value', TJSONString.Create(AValue.AsText));
|
||||
vkKeyword: valObj.AddPair('Value', TJSONString.Create(AValue.AsKeyword.Name));
|
||||
vkVoid:; // No value to add
|
||||
else
|
||||
raise ENotSupportedException.Create('Unsupported TDataValue kind for constant serialization.');
|
||||
@@ -660,16 +666,15 @@ 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.Keyword:
|
||||
// Keywords are serialized by name, intern them back
|
||||
Result := TScalar.FromKeyword(TKeywordRegistry.Intern(scalarObj.GetValue<string>('Value')));
|
||||
end;
|
||||
end
|
||||
else if SameText(kindStr, 'Text') then
|
||||
begin
|
||||
Result := valObj.GetValue<string>('Value');
|
||||
end
|
||||
else if SameText(kindStr, 'Keyword') then
|
||||
begin
|
||||
Result := TDataValue.FromKeyword(TKeywordRegistry.Intern(valObj.GetValue<string>('Value')));
|
||||
end
|
||||
else if SameText(kindStr, 'Void') then
|
||||
begin
|
||||
Result := TDataValue.Void;
|
||||
|
||||
+22
-16
@@ -381,6 +381,9 @@ begin
|
||||
exit(False);
|
||||
|
||||
var otherDef := Other.Definition;
|
||||
if (not Assigned(Self.FDefinition)) or (not Assigned(otherDef)) then
|
||||
exit(False); // Should not happen if Kind is equal, but defensive check
|
||||
|
||||
if Length(Self.FDefinition.Fields) <> Length(otherDef.Fields) then
|
||||
exit(False);
|
||||
|
||||
@@ -399,11 +402,14 @@ var
|
||||
i: Integer;
|
||||
begin
|
||||
Result := GetKind.ToString + '{';
|
||||
for i := 0 to High(FDefinition.Fields) do
|
||||
if Assigned(FDefinition) then
|
||||
begin
|
||||
Result := Result + FDefinition.Fields[i].Key.Name + ': ' + FDefinition.Fields[i].Value.ToString;
|
||||
if i < High(FDefinition.Fields) then
|
||||
Result := Result + ', ';
|
||||
for i := 0 to High(FDefinition.Fields) do
|
||||
begin
|
||||
Result := Result + FDefinition.Fields[i].Key.Name + ': ' + FDefinition.Fields[i].Value.ToString;
|
||||
if i < High(FDefinition.Fields) then
|
||||
Result := Result + ', ';
|
||||
end;
|
||||
end;
|
||||
Result := Result + '}';
|
||||
end;
|
||||
@@ -446,6 +452,7 @@ begin
|
||||
case AKind of
|
||||
TScalar.TKind.Ordinal: Result := FOrdinal;
|
||||
TScalar.TKind.Float: Result := FFloat;
|
||||
TScalar.TKind.Keyword: Result := FKeyword;
|
||||
else
|
||||
raise ETypeException.Create('Cannot convert invalid TScalar.TKind to TStaticType.');
|
||||
end;
|
||||
@@ -472,6 +479,9 @@ begin
|
||||
if (Target.Kind = stVoid) and (Source.Kind = stMethod) then
|
||||
exit(True);
|
||||
|
||||
if (Target.Kind = stOrdinal) and (Source.Kind = stKeyword) then
|
||||
exit(True);
|
||||
|
||||
// TODO: Implement full assignment compatibility rules (e.g., for records/series)
|
||||
|
||||
Result := False;
|
||||
@@ -499,11 +509,11 @@ begin
|
||||
if (A.Kind = stOrdinal) and (B.Kind = stOrdinal) then
|
||||
exit(TTypes.Ordinal);
|
||||
|
||||
// If types are identical and not numeric, return that type (e.g. Text + Text might be valid later)
|
||||
// If types are identical (incl. Keyword, Text, etc.), return that type.
|
||||
if A.IsEqual(B) then
|
||||
exit(A);
|
||||
|
||||
// Cannot promote other combinations during basic inference
|
||||
// Cannot promote other combinations (e.g., Ordinal and Keyword)
|
||||
raise ETypeException.CreateFmt('Cannot promote types %s and %s', [A.ToString, B.ToString]);
|
||||
end;
|
||||
|
||||
@@ -551,24 +561,18 @@ begin
|
||||
|
||||
TScalar.TBinaryOp.Equal, TScalar.TBinaryOp.NotEqual:
|
||||
begin
|
||||
// Allow equality checks for Keywords
|
||||
if (promotedKind = stKeyword) then
|
||||
begin
|
||||
Result := TTypes.Ordinal;
|
||||
exit;
|
||||
end;
|
||||
|
||||
// Comparison requires Ordinal or Float, but *always* results in Ordinal (boolean)
|
||||
if not (promotedKind in [stOrdinal, stFloat]) then
|
||||
// Allow equality checks for Ordinal, Float, or Keyword
|
||||
if not (promotedKind in [stOrdinal, stFloat, stKeyword]) then
|
||||
raise ETypeException.CreateFmt(
|
||||
'Comparison operator %s requires Ordinal, Float, or Keyword, but got %s after promotion',
|
||||
[Op.ToString, promotedType.ToString]);
|
||||
// Result is always Ordinal (boolean)
|
||||
Result := TTypes.Ordinal;
|
||||
end;
|
||||
|
||||
TScalar.TBinaryOp.Less, TScalar.TBinaryOp.Greater, TScalar.TBinaryOp.LessOrEqual, TScalar.TBinaryOp.GreaterOrEqual:
|
||||
begin
|
||||
// Comparison requires Ordinal or Float, but *always* results in Ordinal (boolean)
|
||||
// Comparison requires Ordinal or Float (Keywords are not ordered)
|
||||
if not (promotedKind in [stOrdinal, stFloat]) then
|
||||
raise ETypeException.CreateFmt(
|
||||
'Comparison operator %s requires Ordinal or Float, but got %s after promotion',
|
||||
@@ -593,6 +597,7 @@ begin
|
||||
case Op of
|
||||
TScalar.TUnaryOp.Negate:
|
||||
begin
|
||||
// Negation requires Ordinal or Float
|
||||
if not (rightKind in [stOrdinal, stFloat]) then
|
||||
raise ETypeException.CreateFmt('Unary negation cannot be applied to %s', [Right.ToString]);
|
||||
Result := Right; // Negation preserves type
|
||||
@@ -600,6 +605,7 @@ begin
|
||||
|
||||
TScalar.TUnaryOp.Not:
|
||||
begin
|
||||
// Logical not only applies to Ordinal (booleans)
|
||||
if not (rightKind = stOrdinal) then
|
||||
raise ETypeException.CreateFmt('Logical not cannot be applied to %s', [Right.ToString]);
|
||||
Result := TTypes.Ordinal;
|
||||
|
||||
+4
-4
@@ -349,17 +349,17 @@ constructor TConstantNode.Create(const AValue: TDataValue);
|
||||
begin
|
||||
inherited Create;
|
||||
// Validate that only allowed constant kinds are used.
|
||||
if not (AValue.Kind in [vkScalar, vkText, vkVoid, vkKeyword]) then
|
||||
raise EArgumentException.Create('IConstantNode only supports Scalar, Text, Void and Keyword values.');
|
||||
// vkKeyword is no longer a valid TDataValue kind.
|
||||
if not (AValue.Kind in [vkScalar, vkText, vkVoid]) then
|
||||
raise EArgumentException.Create('IConstantNode only supports Scalar, Text, and Void values.');
|
||||
FValue := AValue;
|
||||
|
||||
case AValue.Kind of
|
||||
vkScalar: StaticType := TTypes.FromScalarKind(AValue.AsScalar.Kind);
|
||||
vkText: StaticType := TTypes.Text;
|
||||
vkVoid: StaticType := TTypes.Void;
|
||||
vkKeyword: StaticType := TTypes.Keyword;
|
||||
else
|
||||
StaticType := TTypes.Unknown; // Should not happen due to validation above
|
||||
StaticType := TTypes.Unknown; // Should not happen
|
||||
end;
|
||||
end;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user