Json Schema for LLMs

This commit is contained in:
Michael Schimmel
2026-01-06 20:55:11 +01:00
parent 4618573d6c
commit 8a29cf7f74
8 changed files with 263 additions and 179 deletions
+108 -20
View File
@@ -59,6 +59,7 @@ type
{$endregion}
function FindSlot(const Name: string): Integer;
function GetSymbolDoc(const Name: string): string;
// Returns all defined symbols in this layout (for debugging/reflection)
function GetSymbols: TArray<string>;
@@ -69,7 +70,7 @@ type
// 2. The Builder (Mutable).
IScopeBuilder = interface(IScopeLayout)
function Define(const Name: string): Integer;
function Define(const Name: string; const Doc: string = ''): Integer;
function Build: IScopeLayout;
end;
@@ -95,7 +96,13 @@ type
procedure SetValues(const Address: TResolvedAddress; const Value: TDataValue);
{$endregion}
function Define(const Name: string; const Value: TDataValue; const AStaticType: IStaticType = nil): TResolvedAddress;
function Define(
const Name: string;
const Value: TDataValue;
const AStaticType: IStaticType = nil;
const ADoc: string = ''
): TResolvedAddress;
procedure DefineBoxed(SlotIndex: Integer; const Value: TDataValue);
function Capture(const Address: TResolvedAddress): IValueCell;
@@ -129,18 +136,25 @@ uses
System.SyncObjs;
type
TSymbolMeta = record
Slot: Integer;
Doc: string;
constructor Create(ASlot: Integer; const ADoc: string);
end;
// --- Concrete Layout (Immutable) ---
TScopeLayout = class(TInterfacedObject, IScopeLayout)
private
FParent: IScopeLayout;
FMap: TDictionary<string, Integer>;
FSlotCount: Integer; // Explicit slot count
FMap: TDictionary<string, TSymbolMeta>;
FSlotCount: Integer;
function GetParent: IScopeLayout;
function GetSlotCount: Integer;
public
constructor Create(const AParent: IScopeLayout; AMap: TDictionary<string, Integer>; ASlotCount: Integer);
constructor Create(const AParent: IScopeLayout; AMap: TDictionary<string, TSymbolMeta>; ASlotCount: Integer);
destructor Destroy; override;
function FindSlot(const Name: string): Integer;
function GetSymbolDoc(const Name: string): string;
function GetSymbols: TArray<string>;
end;
@@ -148,16 +162,17 @@ type
TScopeBuilder = class(TInterfacedObject, IScopeBuilder, IScopeLayout)
private
FParentLayout: IScopeLayout;
FMap: TDictionary<string, Integer>;
FNextSlot: Integer; // Counter for sequential slot allocation
FMap: TDictionary<string, TSymbolMeta>;
FNextSlot: Integer;
function GetParent: IScopeLayout;
function GetSlotCount: Integer;
public
constructor Create(const AParentLayout: IScopeLayout);
destructor Destroy; override;
function Define(const Name: string): Integer;
function Define(const Name: string; const Doc: string): Integer;
function FindSlot(const Name: string): Integer;
function GetSymbolDoc(const Name: string): string;
function GetSymbols: TArray<string>;
function Build: IScopeLayout;
end;
@@ -202,6 +217,7 @@ type
public
constructor Create(AOwner: TExecutionScope);
function FindSlot(const Name: string): Integer;
function GetSymbolDoc(const Name: string): string;
function GetSymbols: TArray<string>;
end;
@@ -224,9 +240,12 @@ type
FSlotTypes: TArray<IStaticType>;
FValues: TArray<TScopeItem>;
FCapturedUpvalues: TArray<IValueCell>;
// Dynamic Name Mapping
FNames: TDictionary<string, Integer>;
FNameStrings: TList<string>;
FNameToIndex: TDictionary<Integer, Integer>;
FDocs: TDictionary<string, string>; // Stores docs for dynamic symbols
procedure DumpScope(const ABuilder: TStringBuilder; AIndent: Integer);
procedure NeedNameToIndex;
@@ -242,7 +261,12 @@ type
function GetNameID(const Name: String): Integer;
function Resolve(const Name: string): TResolvedAddress;
function Dump: string;
function Define(const Name: string; const Value: TDataValue; const AStaticType: IStaticType = nil): TResolvedAddress;
function Define(
const Name: string;
const Value: TDataValue;
const AStaticType: IStaticType = nil;
const ADoc: string = ''
): TResolvedAddress;
procedure DefineBoxed(SlotIndex: Integer; const Value: TDataValue);
function Capture(const Address: TResolvedAddress): IValueCell;
@@ -300,9 +324,17 @@ begin
Dest.StaticType := TTypes.Unknown;
end;
{ TSymbolMeta }
constructor TSymbolMeta.Create(ASlot: Integer; const ADoc: string);
begin
Slot := ASlot;
Doc := ADoc;
end;
{ TScopeLayout }
constructor TScopeLayout.Create(const AParent: IScopeLayout; AMap: TDictionary<string, Integer>; ASlotCount: Integer);
constructor TScopeLayout.Create(const AParent: IScopeLayout; AMap: TDictionary<string, TSymbolMeta>; ASlotCount: Integer);
begin
inherited Create;
FParent := AParent;
@@ -317,11 +349,25 @@ begin
end;
function TScopeLayout.FindSlot(const Name: string): Integer;
var
meta: TSymbolMeta;
begin
if not FMap.TryGetValue(Name, Result) then
if FMap.TryGetValue(Name, meta) then
Result := meta.Slot
else
Result := -1;
end;
function TScopeLayout.GetSymbolDoc(const Name: string): string;
var
meta: TSymbolMeta;
begin
if FMap.TryGetValue(Name, meta) then
Result := meta.Doc
else
Result := '';
end;
function TScopeLayout.GetSymbols: TArray<string>;
begin
Result := FMap.Keys.ToArray;
@@ -343,7 +389,7 @@ constructor TScopeBuilder.Create(const AParentLayout: IScopeLayout);
begin
inherited Create;
FParentLayout := AParentLayout;
FMap := TDictionary<string, Integer>.Create;
FMap := TDictionary<string, TSymbolMeta>.Create;
FNextSlot := 0;
end;
@@ -353,25 +399,38 @@ begin
inherited;
end;
function TScopeBuilder.Define(const Name: string): Integer;
function TScopeBuilder.Define(const Name: string; const Doc: string): Integer;
begin
// Rule: Shadowing / Redefinition in the same scope is forbidden.
// The Client (Binder) must ensure this name is not already taken before calling Define.
Assert(not FMap.ContainsKey(Name), 'Scope Error: Variable "' + Name + '" is already defined in this scope builder.');
// Allocate a new slot
Result := FNextSlot;
Inc(FNextSlot);
FMap.Add(Name, Result);
FMap.Add(Name, TSymbolMeta.Create(Result, Doc));
end;
function TScopeBuilder.FindSlot(const Name: string): Integer;
var
meta: TSymbolMeta;
begin
if not FMap.TryGetValue(Name, Result) then
if FMap.TryGetValue(Name, meta) then
Result := meta.Slot
else
Result := -1;
end;
function TScopeBuilder.GetSymbolDoc(const Name: string): string;
var
meta: TSymbolMeta;
begin
if FMap.TryGetValue(Name, meta) then
Result := meta.Doc
else
Result := '';
end;
function TScopeBuilder.GetSymbols: TArray<string>;
begin
Result := FMap.Keys.ToArray;
@@ -380,7 +439,7 @@ end;
function TScopeBuilder.Build: IScopeLayout;
begin
// Pass FNextSlot as the total slot count
Result := TScopeLayout.Create(FParentLayout, TDictionary<string, Integer>.Create(FMap), FNextSlot);
Result := TScopeLayout.Create(FParentLayout, TDictionary<string, TSymbolMeta>.Create(FMap), FNextSlot);
end;
function TScopeBuilder.GetParent: IScopeLayout;
@@ -436,6 +495,12 @@ begin
Result := -1;
end;
function TExecutionScope.TDynamicLayout.GetSymbolDoc(const Name: string): string;
begin
if (FOwner.FDocs = nil) or (not FOwner.FDocs.TryGetValue(Name, Result)) then
Result := '';
end;
function TExecutionScope.TDynamicLayout.GetSymbols: TArray<string>;
begin
Result := FOwner.FNames.Keys.ToArray;
@@ -532,6 +597,8 @@ begin
FNameStrings := TList<string>.Create;
end;
FDocs := nil; // Initialized on demand in Define
if Assigned(FLayoutIntf) then
SetLength(FValues, FLayoutIntf.SlotCount);
end;
@@ -544,6 +611,7 @@ begin
FNameStrings.Free;
FNames.Free;
end;
FDocs.Free;
inherited Destroy;
end;
@@ -564,6 +632,13 @@ procedure TExecutionScope.Clear;
begin
FValues := [];
FreeAndNil(FNameToIndex);
// Docs are tied to Names which are persistent/shared, so FDocs should clear if this is the owner?
// Actually FDocs is local to this scope instance if we define dynamic vars.
// If we clear values, we should probably clear doc mappings too if they are tied to those values.
// But Resolve uses FNames/FNameToIndex. If we clear FNameToIndex, existing name lookups fail.
// FDocs is separate. Let's clear it too.
if FDocs <> nil then
FDocs.Clear;
end;
function TExecutionScope.Capture(const Address: TResolvedAddress): IValueCell;
@@ -602,7 +677,12 @@ begin
end;
end;
function TExecutionScope.Define(const Name: string; const Value: TDataValue; const AStaticType: IStaticType): TResolvedAddress;
function TExecutionScope.Define(
const Name: string;
const Value: TDataValue;
const AStaticType: IStaticType;
const ADoc: string
): TResolvedAddress;
var
id: Integer;
index: Integer;
@@ -630,6 +710,14 @@ begin
end;
FNameToIndex.Add(id, index);
if ADoc <> '' then
begin
if FDocs = nil then
FDocs := TDictionary<string, string>.Create;
FDocs.AddOrSetValue(Name, ADoc);
end;
Result.Kind := akLocalOrParent;
Result.ScopeDepth := 0;
Result.SlotIndex := index;
@@ -755,7 +843,7 @@ begin
begin
var map := (FStaticDescriptor.Layout as TScopeLayout).FMap;
for var item in map do
FNameToIndex.AddOrSetValue(GetNameId(item.Key), item.Value);
FNameToIndex.AddOrSetValue(GetNameId(item.Key), item.Value.Slot);
end;
end;
end;
@@ -843,7 +931,7 @@ end;
class function TScope.CreateRootLayout: IScopeLayout;
begin
Result := TScopeLayout.Create(nil, TDictionary<string, Integer>.Create, 0);
Result := TScopeLayout.Create(nil, TDictionary<string, TSymbolMeta>.Create, 0);
end;
end.