Keywords as basic scalar type

Script enhancements
This commit is contained in:
Michael Schimmel
2025-11-01 00:56:59 +01:00
parent 689dede600
commit 957171f089
7 changed files with 498 additions and 78 deletions
+91 -26
View File
@@ -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;