Keywords as basic scalar type
Script enhancements
This commit is contained in:
@@ -18,6 +18,7 @@ type
|
||||
TEvaluatorVisitor = class(TAstVisitor, IEvaluatorVisitor)
|
||||
private
|
||||
FScope: IExecutionScope;
|
||||
class var
|
||||
procedure HandleTCO(var ResultValue: TDataValue);
|
||||
|
||||
protected
|
||||
@@ -77,13 +78,15 @@ type
|
||||
TThunk = record
|
||||
Callee: TDataValue;
|
||||
Args: TArray<TDataValue>;
|
||||
constructor Create(const ACallee: TDataValue; const AArgs: TArray<TDataValue>);
|
||||
Recur: Boolean;
|
||||
constructor Create(const ACallee: TDataValue; const AArgs: TArray<TDataValue>; ARecur: Boolean);
|
||||
end;
|
||||
|
||||
constructor TThunk.Create(const ACallee: TDataValue; const AArgs: TArray<TDataValue>);
|
||||
constructor TThunk.Create(const ACallee: TDataValue; const AArgs: TArray<TDataValue>; ARecur: Boolean);
|
||||
begin
|
||||
Callee := ACallee;
|
||||
Args := AArgs;
|
||||
Recur := ARecur;
|
||||
end;
|
||||
|
||||
// --- Native Functions Implementation ---
|
||||
@@ -114,6 +117,41 @@ begin
|
||||
AScope.Define('CreateRecordSeries', TDataValue(NativeCreateRecordSeries));
|
||||
end;
|
||||
|
||||
{ TDynamicRecord }
|
||||
|
||||
type
|
||||
// Runtime implementation for generic records using linear search
|
||||
TDynamicRecord = class(TInterfacedObject, IKeywordMapping<TDataValue>)
|
||||
private
|
||||
FFields: TArray<TPair<IKeyword, TDataValue>>;
|
||||
function GetFields: TArray<TPair<IKeyword, TDataValue>>;
|
||||
public
|
||||
constructor Create(const AFields: TArray<TPair<IKeyword, TDataValue>>);
|
||||
function IndexOf(const Key: IKeyword): Integer;
|
||||
end;
|
||||
|
||||
constructor TDynamicRecord.Create(const AFields: TArray<TPair<IKeyword, TDataValue>>);
|
||||
begin
|
||||
inherited Create;
|
||||
FFields := AFields;
|
||||
end;
|
||||
|
||||
function TDynamicRecord.GetFields: TArray<TPair<IKeyword, TDataValue>>;
|
||||
begin
|
||||
Result := FFields;
|
||||
end;
|
||||
|
||||
function TDynamicRecord.IndexOf(const Key: IKeyword): Integer;
|
||||
begin
|
||||
// Linear search (O(n)) as requested
|
||||
for Result := 0 to High(FFields) do
|
||||
begin
|
||||
if FFields[Result].Key.Idx = Key.Idx then
|
||||
exit;
|
||||
end;
|
||||
Result := -1;
|
||||
end;
|
||||
|
||||
{ TEvaluatorVisitor }
|
||||
|
||||
constructor TEvaluatorVisitor.Create(const AScope: IExecutionScope);
|
||||
@@ -279,12 +317,13 @@ begin
|
||||
if boundNode.IsTailCall then
|
||||
begin
|
||||
// This is a tail call. Return a thunk to be processed by the trampoline.
|
||||
Result := TDataValue.FromGeneric<TThunk>(TThunk.Create(calleeValue, argValues));
|
||||
Result := TDataValue.FromGeneric<TThunk>(TThunk.Create(calleeValue, argValues, false));
|
||||
end
|
||||
else
|
||||
begin
|
||||
// This is a non-tail call. It must execute the call and act as the trampoline.
|
||||
Result := (calleeValue.AsMethod)(argValues);
|
||||
|
||||
HandleTCO(Result);
|
||||
end;
|
||||
end;
|
||||
@@ -315,7 +354,7 @@ begin
|
||||
calleeValue := FScope[calleeAddress];
|
||||
|
||||
// Recur always returns a thunk for the trampoline.
|
||||
Result := TDataValue.FromGeneric<TThunk>(TThunk.Create(calleeValue, argValues));
|
||||
Result := TDataValue.FromGeneric<TThunk>(TThunk.Create(calleeValue, argValues, true));
|
||||
end;
|
||||
|
||||
function TEvaluatorVisitor.VisitAddSeriesItem(const Node: IAddSeriesItemNode): TDataValue;
|
||||
@@ -446,7 +485,19 @@ begin
|
||||
|
||||
case baseValue.Kind of
|
||||
vkRecordSeries: Result := TDataValue.FromSeries(baseValue.AsRecordSeries[Node.Member.Value]);
|
||||
|
||||
vkRecord: Result := baseValue.AsRecord[Node.Member.Value];
|
||||
|
||||
// --- NEW GENERIC PATH ---
|
||||
vkGenericRecord:
|
||||
begin
|
||||
var rec := baseValue.AsGenericRecord;
|
||||
var fieldIndex := rec.IndexOf(Node.Member.Value);
|
||||
if fieldIndex < 0 then
|
||||
raise EArgumentException.CreateFmt('Member ":%s" not found in record.', [Node.Member.Value.Name]);
|
||||
|
||||
Result := rec.Fields[fieldIndex].Value;
|
||||
end;
|
||||
else
|
||||
raise EArgumentException.Create('Member access operator `.` is not supported for this value type.');
|
||||
end;
|
||||
@@ -454,35 +505,49 @@ end;
|
||||
|
||||
function TEvaluatorVisitor.VisitRecordLiteral(const Node: IRecordLiteralNode): TDataValue;
|
||||
var
|
||||
boundNode: TBoundRecordLiteralNode;
|
||||
values: TArray<TScalar.TValue>;
|
||||
i: Integer;
|
||||
valData: TDataValue;
|
||||
begin
|
||||
// The node must be a bound node from the binder
|
||||
boundNode := Node as TBoundRecordLiteralNode;
|
||||
SetLength(values, Length(boundNode.Fields));
|
||||
|
||||
for i := 0 to High(boundNode.Fields) do
|
||||
// Check which type the binder created
|
||||
if Node is TBoundGenericRecordLiteralNode then
|
||||
begin
|
||||
// Evaluate the value expression for this field
|
||||
valData := boundNode.Fields[i].Value.Accept(Self);
|
||||
// --- NEW GENERIC PATH ---
|
||||
var boundNode := Node as TBoundGenericRecordLiteralNode;
|
||||
var genFields: TArray<TPair<IKeyword, TDataValue>>;
|
||||
SetLength(genFields, Length(boundNode.Fields));
|
||||
|
||||
if valData.Kind = vkScalar then
|
||||
// Evaluate all field values
|
||||
for i := 0 to High(boundNode.Fields) do
|
||||
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.');
|
||||
genFields[i] :=
|
||||
TPair<IKeyword, TDataValue>.Create(
|
||||
boundNode.Fields[i].Key.Value,
|
||||
boundNode.Fields[i].Value.Accept(Self) // Evaluate expression
|
||||
);
|
||||
end;
|
||||
end;
|
||||
|
||||
// Create the TScalarRecord using the definition from the binder
|
||||
var rec := TScalarRecord.Create(boundNode.Definition, values);
|
||||
Result := TDataValue.FromRecord(rec);
|
||||
// Create the new runtime object and wrap it
|
||||
var dynRec := TDynamicRecord.Create(genFields);
|
||||
Result := TDataValue.FromGenericRecord(dynRec);
|
||||
end
|
||||
else if Node is TBoundRecordLiteralNode then
|
||||
begin
|
||||
// --- EXISTING SCALAR PATH ---
|
||||
var boundNode := Node as TBoundRecordLiteralNode;
|
||||
var values: TArray<TScalar.TValue>;
|
||||
SetLength(values, Length(boundNode.Fields));
|
||||
|
||||
for i := 0 to High(boundNode.Fields) do
|
||||
begin
|
||||
var valData := boundNode.Fields[i].Value.Accept(Self);
|
||||
// Binder guarantees these are vkScalar
|
||||
values[i] := valData.AsScalar.Value;
|
||||
end;
|
||||
|
||||
var rec := TScalarRecord.Create(boundNode.Definition, values);
|
||||
Result := TDataValue.FromRecord(rec);
|
||||
end
|
||||
else
|
||||
raise EInvalidOpException.Create('Unknown record literal node type during evaluation.');
|
||||
end;
|
||||
|
||||
function TEvaluatorVisitor.VisitVariableDeclaration(const Node: IVariableDeclarationNode): TDataValue;
|
||||
|
||||
Reference in New Issue
Block a user