Json Schema for LLMs
This commit is contained in:
@@ -98,7 +98,8 @@ type
|
||||
|
||||
function Run(const ANode: IAstNode; const Params: TArray<IIdentifierNode> = []; const Args: TArray<TDataValue> = []): TDataValue;
|
||||
|
||||
procedure Define(const Name: String; const AScript: IAstNode);
|
||||
// UPDATED: Now supports Documentation
|
||||
procedure Define(const Name: String; const AScript: IAstNode; const Doc: string = '');
|
||||
|
||||
property Environment: IEnvironment read FEnvironment;
|
||||
property RootScope: IExecutionScope read GetRootScope;
|
||||
@@ -261,11 +262,12 @@ begin
|
||||
Result := FEnvironment.CreateEnvironment;
|
||||
end;
|
||||
|
||||
procedure TAstEnvironment.Define(const Name: String; const AScript: IAstNode);
|
||||
procedure TAstEnvironment.Define(const Name: String; const AScript: IAstNode; const Doc: string);
|
||||
begin
|
||||
var compiled := Compile(AScript);
|
||||
var linked := Link(compiled);
|
||||
RootScope.Define(Name, linked.Func([]), compiled.StaticType);
|
||||
// Pass the documentation to the scope
|
||||
RootScope.Define(Name, linked.Func([]), compiled.StaticType, Doc);
|
||||
end;
|
||||
|
||||
function TAstEnvironment.ExpandMacros(const Node: IAstNode): IAstNode;
|
||||
@@ -425,9 +427,6 @@ begin
|
||||
if lg.HasErrors then
|
||||
raise ECompilationFailed.Create(lg.GetEntries);
|
||||
|
||||
// Note: If types are Unknown but no errors were logged (edge case), we proceed.
|
||||
// But Binder/TypeChecker logic ensures errors are logged for Unknowns that matter.
|
||||
|
||||
var specialized := Specialize(typedNode);
|
||||
|
||||
Result := specialized.AsLambdaExpression;
|
||||
@@ -471,7 +470,6 @@ begin
|
||||
var cExecutionStrategy := FExecutionStrategy;
|
||||
|
||||
// The Glue: This callback performs the full compilation pipeline for a macro argument expression.
|
||||
// It binds the node to the layout of the temporary macro scope and then executes it.
|
||||
var macroEvaluator: TMacroEvaluatorProc :=
|
||||
function(const Scope: IExecutionScope; const ANode: IAstNode): TDataValue
|
||||
var
|
||||
@@ -485,15 +483,12 @@ begin
|
||||
tempLog := TCompilerLog.Create;
|
||||
|
||||
// 1. Binding
|
||||
// Note: Scope is dynamic (from TMacroExpander), so Descriptor.Layout describes current variables.
|
||||
boundSubAst := TAstBinder.Bind(Scope.Descriptor.Layout, ANode, tmpLayout, tempLog);
|
||||
|
||||
// Macro execution is strictly fail-fast. If we can't bind arguments, we can't run the macro.
|
||||
if tempLog.HasErrors then
|
||||
raise EMacroException.Create('Macro Argument Error: ' + tempLog.GetEntries[0].Message);
|
||||
|
||||
// 2. Scope Matching
|
||||
// Create a child scope to ensure correct depth resolution (Binder sees Layout at depth 0 relative to itself)
|
||||
scratchScope := TScope.CreateScope(Scope, nil, nil);
|
||||
|
||||
// 3. Execution
|
||||
|
||||
+109
-116
@@ -14,23 +14,17 @@ uses
|
||||
Myc.Data.Value,
|
||||
Myc.Ast.Nodes,
|
||||
Myc.Ast.Attributes,
|
||||
Myc.Ast.RTL,
|
||||
Myc.Ast.RTL.Core,
|
||||
Myc.Ast.Scope,
|
||||
Myc.Ast.Types,
|
||||
Myc.Ast.Script,
|
||||
Myc.Ast.Json;
|
||||
Myc.Ast.Json,
|
||||
Myc.Ast;
|
||||
|
||||
type
|
||||
{ Scans AST, RTL and Scope metadata to generate a context-aware LLM system prompt. }
|
||||
TAstSchema = class
|
||||
strict private
|
||||
type
|
||||
TRtlMeta = record
|
||||
Doc: string;
|
||||
Signatures: TArray<string>;
|
||||
end;
|
||||
|
||||
TFieldInfo = record
|
||||
Idx: Integer;
|
||||
Name: string;
|
||||
@@ -39,18 +33,17 @@ type
|
||||
|
||||
class function GetTsType(Kind: TFieldKind): string; static;
|
||||
class function TypeToTs(const AType: IStaticType): string; static;
|
||||
class function GetRtlMetadata: TDictionary<string, TRtlMeta>; static;
|
||||
public
|
||||
// Generates the TypeScript definition for AST nodes
|
||||
// Generates the TypeScript definition for AST nodes using RTTI on the Node Interfaces
|
||||
class function GenerateTypeScriptDefinition: string; static;
|
||||
|
||||
// Unified section: Lists only symbols available in the current scope with their docs/types
|
||||
// Unified section: Lists symbols available in the given scope with their docs and types.
|
||||
class function GenerateAvailableSymbols(const AScope: IExecutionScope): string; static;
|
||||
|
||||
// Documentation for all RTL functions (used as fallback or for general prompts)
|
||||
// Generates documentation for the standard library by creating a temporary scope.
|
||||
class function GenerateAllRtlDocumentation: string; static;
|
||||
|
||||
// Detailed generation rules
|
||||
// Detailed generation rules for the LLM
|
||||
class function GenerateGenerationRules: string; static;
|
||||
|
||||
// The final consolidated system prompt for the AI
|
||||
@@ -81,37 +74,106 @@ begin
|
||||
exit('any');
|
||||
|
||||
case AType.Kind of
|
||||
stUnknown: Result := 'any';
|
||||
stVoid: Result := 'void';
|
||||
stOrdinal, stFloat: Result := 'number';
|
||||
stBoolean: Result := 'boolean';
|
||||
stDateTime: Result := 'datetime';
|
||||
stText: Result := 'string';
|
||||
stKeyword: Result := 'keyword';
|
||||
|
||||
stSeries: Result := Format('Series<%s>', [TypeToTs(AType.AsSeries.ElementType)]);
|
||||
|
||||
stRecord:
|
||||
begin
|
||||
var def := AType.AsRecord.Definition;
|
||||
var fields := TList<string>.Create;
|
||||
try
|
||||
for var i := 0 to def.Count - 1 do
|
||||
fields.Add(Format('%s: %s', [def.Keywords[i].Name, def[i].ToString.ToLower]));
|
||||
begin
|
||||
var k := def.Keywords[i].Name;
|
||||
var tStr: string;
|
||||
case def[i] of
|
||||
TScalar.TKind.Ordinal, TScalar.TKind.Float: tStr := 'number';
|
||||
TScalar.TKind.Boolean: tStr := 'boolean';
|
||||
TScalar.TKind.DateTime: tStr := 'datetime';
|
||||
TScalar.TKind.Keyword: tStr := 'keyword';
|
||||
else
|
||||
tStr := 'any';
|
||||
end;
|
||||
fields.Add(Format('%s: %s', [k, tStr]));
|
||||
end;
|
||||
Result := '{ ' + string.Join(', ', fields.ToArray) + ' }';
|
||||
finally
|
||||
fields.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
stGenericRecord:
|
||||
begin
|
||||
var def := AType.AsGenericRecord.GenericDefinition;
|
||||
var fields := TList<string>.Create;
|
||||
try
|
||||
for var i := 0 to def.Count - 1 do
|
||||
fields.Add(Format('%s: %s', [def.Keywords[i].Name, TypeToTs(def[i])]));
|
||||
Result := '{ ' + string.Join(', ', fields.ToArray) + ' }';
|
||||
finally
|
||||
fields.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
stRecordSeries: Result := 'Stream<' + TypeToTs(TTypes.CreateRecord(AType.AsRecord.Definition)) + '>';
|
||||
|
||||
stTuple:
|
||||
begin
|
||||
var elems := AType.AsTuple.Elements;
|
||||
var elemStrings := TList<string>.Create;
|
||||
try
|
||||
for var t in elems do
|
||||
elemStrings.Add(TypeToTs(t));
|
||||
Result := '[' + string.Join(', ', elemStrings.ToArray) + ']';
|
||||
finally
|
||||
elemStrings.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
stVector: Result := TypeToTs(AType.AsVector.ElementType) + '[]';
|
||||
|
||||
stMatrix:
|
||||
begin
|
||||
var dims := AType.AsMatrix.Dimensions;
|
||||
Result := TypeToTs(AType.AsMatrix.ElementType);
|
||||
for var k := 0 to High(dims) do
|
||||
Result := Result + '[]';
|
||||
end;
|
||||
|
||||
stMethod:
|
||||
begin
|
||||
if Length(AType.AsMethod.Signatures) = 0 then
|
||||
var signatures := AType.AsMethod.Signatures;
|
||||
if Length(signatures) = 0 then
|
||||
exit('function');
|
||||
var sig := AType.AsMethod.Signatures[0];
|
||||
var args := TList<string>.Create;
|
||||
|
||||
var sigStrings := TList<string>.Create;
|
||||
try
|
||||
for var p in sig.ParamTypes do
|
||||
args.Add(TypeToTs(p));
|
||||
Result := Format('(%s) => %s', [string.Join(', ', args.ToArray), TypeToTs(sig.ReturnType)]);
|
||||
for var sig in signatures do
|
||||
begin
|
||||
var args := TList<string>.Create;
|
||||
try
|
||||
for var p in sig.ParamTypes do
|
||||
args.Add(TypeToTs(p));
|
||||
|
||||
var s := Format('(%s) => %s', [string.Join(', ', args.ToArray), TypeToTs(sig.ReturnType)]);
|
||||
|
||||
// Deduplicate: If multiple RTL overloads map to the same TS signature, only show it once.
|
||||
if not sigStrings.Contains(s) then
|
||||
sigStrings.Add(s);
|
||||
finally
|
||||
args.Free;
|
||||
end;
|
||||
end;
|
||||
Result := string.Join(' | ', sigStrings.ToArray);
|
||||
finally
|
||||
args.Free;
|
||||
sigStrings.Free;
|
||||
end;
|
||||
end;
|
||||
else
|
||||
@@ -122,56 +184,6 @@ begin
|
||||
Result := Result + ' | null';
|
||||
end;
|
||||
|
||||
class function TAstSchema.GetRtlMetadata: TDictionary<string, TRtlMeta>;
|
||||
var
|
||||
ctx: TRttiContext;
|
||||
typ: TRttiType;
|
||||
meth: TRttiMethod;
|
||||
attr: TCustomAttribute;
|
||||
meta: TRtlMeta;
|
||||
rtlName: string;
|
||||
sigs: TList<string>;
|
||||
begin
|
||||
Result := TDictionary<string, TRtlMeta>.Create;
|
||||
sigs := TList<string>.Create;
|
||||
try
|
||||
typ := ctx.GetType(TypeInfo(TRtlFunctions));
|
||||
for meth in typ.GetMethods do
|
||||
begin
|
||||
rtlName := '';
|
||||
meta.Doc := '';
|
||||
sigs.Clear;
|
||||
for attr in meth.GetAttributes do
|
||||
begin
|
||||
if attr is AstDocAttribute then
|
||||
meta.Doc := AstDocAttribute(attr).Description;
|
||||
if attr is TRtlExportAttribute then
|
||||
rtlName := TRtlExportAttribute(attr).Name;
|
||||
if attr is AstSignatureAttribute then
|
||||
sigs.Add(AstSignatureAttribute(attr).Signature);
|
||||
end;
|
||||
if rtlName <> '' then
|
||||
begin
|
||||
meta.Signatures := sigs.ToArray;
|
||||
if Result.ContainsKey(rtlName) then
|
||||
begin
|
||||
var existing := Result[rtlName];
|
||||
var combinedSigs := TList<string>.Create;
|
||||
combinedSigs.AddRange(existing.Signatures);
|
||||
combinedSigs.AddRange(meta.Signatures);
|
||||
existing.Signatures := combinedSigs.ToArray;
|
||||
combinedSigs.Free;
|
||||
Result[rtlName] := existing;
|
||||
end
|
||||
else
|
||||
Result.Add(rtlName, meta);
|
||||
end;
|
||||
end;
|
||||
finally
|
||||
sigs.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
class function TAstSchema.GenerateTypeScriptDefinition: string;
|
||||
var
|
||||
ctx: TRttiContext;
|
||||
@@ -215,6 +227,7 @@ begin
|
||||
if exampleList = nil then
|
||||
exampleList := TList<string>.Create;
|
||||
try
|
||||
// Parse example script to ensure valid JSON output in documentation
|
||||
var node := TAstScript.Parse(AstScriptExampleAttribute(attr).Script);
|
||||
var jsonVal := jsonConv.Serialize(node);
|
||||
try
|
||||
@@ -224,7 +237,7 @@ begin
|
||||
end;
|
||||
except
|
||||
on E: Exception do
|
||||
exampleList.Add('/* Error in example */');
|
||||
exampleList.Add('/* Error in example: ' + E.Message + ' */');
|
||||
end;
|
||||
end;
|
||||
if attr is AstFieldAttribute then
|
||||
@@ -309,87 +322,67 @@ end;
|
||||
class function TAstSchema.GenerateAvailableSymbols(const AScope: IExecutionScope): string;
|
||||
var
|
||||
sb: TStringBuilder;
|
||||
rtlMeta: TDictionary<string, TRtlMeta>;
|
||||
layout: IScopeLayout;
|
||||
descriptor: IScopeDescriptor;
|
||||
symbols: TArray<string>;
|
||||
name: string;
|
||||
meta: TRtlMeta;
|
||||
name, doc: string;
|
||||
typ: IStaticType;
|
||||
slot: Integer;
|
||||
begin
|
||||
if not Assigned(AScope) then
|
||||
exit('');
|
||||
|
||||
rtlMeta := GetRtlMetadata;
|
||||
sb := TStringBuilder.Create;
|
||||
try
|
||||
layout := AScope.Descriptor.Layout;
|
||||
descriptor := AScope.Descriptor;
|
||||
layout := descriptor.Layout;
|
||||
symbols := layout.GetSymbols;
|
||||
TArray.Sort<string>(symbols);
|
||||
|
||||
sb.AppendLine('### 3. Available Symbols (Environment & Library)');
|
||||
sb.AppendLine('### Available Symbols (Environment & Library)');
|
||||
sb.AppendLine('The following symbols are currently defined and can be used in your script:');
|
||||
sb.AppendLine;
|
||||
|
||||
for name in symbols do
|
||||
begin
|
||||
// Filter internal compiler symbols
|
||||
if name.StartsWith('<') or name.EndsWith('>') then
|
||||
continue;
|
||||
|
||||
slot := layout.FindSlot(name);
|
||||
typ := descriptor.GetSymbolType(slot);
|
||||
doc := layout.GetSymbolDoc(name);
|
||||
|
||||
sb.Append(Format('- `%s`', [name]));
|
||||
|
||||
if rtlMeta.TryGetValue(name, meta) then
|
||||
begin
|
||||
if Length(meta.Signatures) > 0 then
|
||||
sb.Append(': ' + string.Join(' | ', meta.Signatures));
|
||||
if meta.Doc <> '' then
|
||||
sb.Append(' — ' + meta.Doc);
|
||||
end
|
||||
else
|
||||
begin
|
||||
typ := AScope.Descriptor.GetSymbolType(layout.FindSlot(name));
|
||||
if (typ <> nil) then
|
||||
sb.Append(': ' + TypeToTs(typ));
|
||||
end;
|
||||
|
||||
if doc <> '' then
|
||||
sb.Append(' — ' + doc);
|
||||
|
||||
sb.AppendLine;
|
||||
end;
|
||||
Result := sb.ToString;
|
||||
finally
|
||||
sb.Free;
|
||||
rtlMeta.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
class function TAstSchema.GenerateAllRtlDocumentation: string;
|
||||
var
|
||||
rtlMeta: TDictionary<string, TRtlMeta>;
|
||||
sb: TStringBuilder;
|
||||
pair: TPair<string, TRtlMeta>;
|
||||
begin
|
||||
rtlMeta := GetRtlMetadata;
|
||||
sb := TStringBuilder.Create;
|
||||
try
|
||||
sb.AppendLine('### 3. Runtime Library (Global Functions)');
|
||||
sb.AppendLine;
|
||||
for pair in rtlMeta do
|
||||
begin
|
||||
sb.Append(Format('- `%s`', [pair.Key]));
|
||||
if Length(pair.Value.Signatures) > 0 then
|
||||
sb.Append(': ' + string.Join(' | ', pair.Value.Signatures));
|
||||
if pair.Value.Doc <> '' then
|
||||
sb.Append(' — ' + pair.Value.Doc);
|
||||
sb.AppendLine;
|
||||
end;
|
||||
Result := sb.ToString;
|
||||
finally
|
||||
sb.Free;
|
||||
rtlMeta.Free;
|
||||
end;
|
||||
// Create a temporary root scope.
|
||||
// The True parameter triggers the registration of all standard libraries (RTL).
|
||||
// This populates the scope with function definitions and their documentation strings.
|
||||
var tempScope := TAst.CreateScope(nil, nil, True);
|
||||
Result := GenerateAvailableSymbols(tempScope);
|
||||
end;
|
||||
|
||||
class function TAstSchema.GenerateGenerationRules: string;
|
||||
begin
|
||||
Result :=
|
||||
'''
|
||||
**⚠️ CRITICAL GENERATION RULES:**
|
||||
**CRITICAL GENERATION RULES:**
|
||||
|
||||
1. **NO JSON OBJECTS:** Do not use `{}`. Every node MUST be a JSON Array `["Tag", Arg1, Arg2]`.
|
||||
2. **EXPLICIT TUPLES:** Fields defined as `Tuple` MUST be wrapped in `["Tuple", [...]]`.
|
||||
@@ -412,12 +405,12 @@ begin
|
||||
You are a compiler frontend for a domain-specific scripting language.
|
||||
Your task is to generate a valid Abstract Syntax Tree (AST) in a specific Compact JSON format.
|
||||
|
||||
### 1. AST Schema (TypeScript Definition)
|
||||
### AST Schema (TypeScript Definition)
|
||||
'''
|
||||
+ sLineBreak
|
||||
+ GenerateTypeScriptDefinition
|
||||
+ sLineBreak
|
||||
+ '### 2. Constraints & Rules'
|
||||
+ '### Constraints & Rules'
|
||||
+ sLineBreak
|
||||
+ GenerateGenerationRules
|
||||
+ sLineBreak;
|
||||
|
||||
@@ -3,12 +3,17 @@ unit Myc.Ast.RTL.Core;
|
||||
interface
|
||||
|
||||
uses
|
||||
system.sysutils,
|
||||
System.SysUtils,
|
||||
System.Math,
|
||||
System.DateUtils,
|
||||
System.Generics.Collections,
|
||||
Myc.Utils,
|
||||
Myc.Data.Scalar,
|
||||
Myc.Data.Value,
|
||||
Myc.Ast.RTL,
|
||||
Myc.Ast.Attributes;
|
||||
Myc.Ast.Attributes,
|
||||
Myc.Data.Keyword,
|
||||
Myc.Data.Series;
|
||||
|
||||
type
|
||||
{ Contains the native implementations for the Myc standard library. }
|
||||
@@ -325,12 +330,6 @@ type
|
||||
|
||||
implementation
|
||||
|
||||
uses
|
||||
System.Generics.Collections,
|
||||
System.Math,
|
||||
System.Dateutils,
|
||||
Myc.Data.Decimal;
|
||||
|
||||
{ TRtlFunctions - Operator Implementations }
|
||||
|
||||
class function TRtlFunctions.Add(const Args: TArray<TDataValue>): TDataValue;
|
||||
|
||||
@@ -56,11 +56,13 @@ type
|
||||
|
||||
// --- Factories ---
|
||||
// Registers a factory function. T must be registered via RegisterType<T> first.
|
||||
// UPDATED: Now supports documentation parameter.
|
||||
class procedure RegisterFactory<T: IInterface>(
|
||||
const Scope: IExecutionScope;
|
||||
const FactoryName: string;
|
||||
const FactoryArgs: TArray<IStaticType>;
|
||||
const FactoryDelegate: TInterfaceFactoryDelegate<T>
|
||||
const FactoryDelegate: TInterfaceFactoryDelegate<T>;
|
||||
const Doc: string = ''
|
||||
);
|
||||
|
||||
// --- Resolution ---
|
||||
@@ -176,8 +178,6 @@ begin
|
||||
var rIntf := rType as TRttiInterfaceType;
|
||||
// If the Interface has an "Invoke" method and is marked as reference-to (or is generic TFunc/TProc),
|
||||
// map it directly to stMethod.
|
||||
// Note: RTTI for "IsReferenceTo" is not always directly available,
|
||||
// but we can check if there is exactly one method named "Invoke".
|
||||
var method := rIntf.GetMethod('Invoke');
|
||||
if Assigned(method) and (Length(rIntf.GetMethods) = 1) then
|
||||
begin
|
||||
@@ -496,7 +496,8 @@ class procedure TRtlTypeRegistry.RegisterFactory<T>(
|
||||
const Scope: IExecutionScope;
|
||||
const FactoryName: string;
|
||||
const FactoryArgs: TArray<IStaticType>;
|
||||
const FactoryDelegate: TInterfaceFactoryDelegate<T>
|
||||
const FactoryDelegate: TInterfaceFactoryDelegate<T>;
|
||||
const Doc: string
|
||||
);
|
||||
var
|
||||
staticType: IStaticType;
|
||||
@@ -518,7 +519,7 @@ begin
|
||||
end;
|
||||
|
||||
var factorySig := TTypes.CreateMethod(FactoryArgs, staticType);
|
||||
Scope.Define(FactoryName, TDataValue(factoryWrapper), factorySig);
|
||||
Scope.Define(FactoryName, TDataValue(factoryWrapper), factorySig, Doc);
|
||||
end;
|
||||
|
||||
end.
|
||||
|
||||
+17
-11
@@ -60,6 +60,7 @@ type
|
||||
public
|
||||
DynamicWrapper: TDataValue.TFunc;
|
||||
Signatures: TList<IMethodSignature>;
|
||||
Doc: string; // Documentation string from AstDoc attribute
|
||||
constructor Create;
|
||||
destructor Destroy; override;
|
||||
end;
|
||||
@@ -145,6 +146,7 @@ uses
|
||||
System.TypInfo,
|
||||
System.Hash,
|
||||
System.StrUtils,
|
||||
Myc.Ast.Attributes,
|
||||
Myc.Ast.RTL.Core;
|
||||
|
||||
type
|
||||
@@ -228,6 +230,7 @@ begin
|
||||
inherited Create;
|
||||
Signatures := TList<IMethodSignature>.Create;
|
||||
DynamicWrapper := nil;
|
||||
Doc := '';
|
||||
end;
|
||||
|
||||
destructor TRtlFunctionInfo.Destroy;
|
||||
@@ -262,6 +265,7 @@ var
|
||||
rttiParams: TArray<TRttiParameter>;
|
||||
isDynamic: Boolean;
|
||||
i: Integer;
|
||||
docString: string;
|
||||
begin
|
||||
// 1. Create global caches
|
||||
FStaticBootstrap := TStaticBootstrapCache.Create(TStaticSignatureKeyComparer.Create);
|
||||
@@ -278,13 +282,14 @@ begin
|
||||
continue;
|
||||
|
||||
exportAttr := nil;
|
||||
docString := '';
|
||||
|
||||
for attribute in method.GetAttributes do
|
||||
begin
|
||||
if attribute is TRtlExportAttribute then
|
||||
begin
|
||||
exportAttr := attribute as TRtlExportAttribute;
|
||||
break;
|
||||
end;
|
||||
exportAttr := attribute as TRtlExportAttribute
|
||||
else if attribute is AstDocAttribute then
|
||||
docString := AstDocAttribute(attribute).Description;
|
||||
end;
|
||||
|
||||
if not Assigned(exportAttr) then
|
||||
@@ -298,6 +303,10 @@ begin
|
||||
FStaticFuncMap.Add(rtlName, funcInfo);
|
||||
end;
|
||||
|
||||
// Update documentation (if not already set or override)
|
||||
if (funcInfo.Doc = '') and (docString <> '') then
|
||||
funcInfo.Doc := docString;
|
||||
|
||||
// Check Signature Type (Dynamic vs Static)
|
||||
rttiParams := method.GetParameters;
|
||||
isDynamic :=
|
||||
@@ -346,10 +355,6 @@ begin
|
||||
if (not Assigned(funcInfo.DynamicWrapper)) and (Length(argTypes) = 1) and (argTypes[0].Kind = stUnknown) then
|
||||
begin
|
||||
funcInfo.DynamicWrapper := wrapper;
|
||||
end
|
||||
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
|
||||
end;
|
||||
end
|
||||
else
|
||||
@@ -492,7 +497,8 @@ begin
|
||||
|
||||
var wrapper := funcInfo.DynamicWrapper; // Default is Void
|
||||
|
||||
AScope.Define(rtlName, wrapper, staticType);
|
||||
// Pass documentation to scope definition
|
||||
AScope.Define(rtlName, wrapper, staticType, funcInfo.Doc);
|
||||
end;
|
||||
end;
|
||||
|
||||
@@ -766,8 +772,8 @@ 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);
|
||||
AScope.Define('true', TDataValue(TScalar.FromBoolean(True)), TTypes.Boolean, 'Boolean true.');
|
||||
AScope.Define('false', TDataValue(TScalar.FromBoolean(False)), TTypes.Boolean, 'Boolean false.');
|
||||
|
||||
TRtlRegistry.RegisterAll(AScope);
|
||||
end;
|
||||
|
||||
+108
-20
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user