From 8a29cf7f7467ae6ec58c0bc4d97f6664f22e12a0 Mon Sep 17 00:00:00 2001 From: Michael Schimmel Date: Tue, 6 Jan 2026 20:55:11 +0100 Subject: [PATCH] Json Schema for LLMs --- Src/AST/Myc.Ast.Environment.pas | 15 +- Src/AST/Myc.Ast.Json.Schema.pas | 225 +++++++++++++-------------- Src/AST/Myc.Ast.RTL.Core.pas | 15 +- Src/AST/Myc.Ast.RTL.TypeRegistry.pas | 11 +- Src/AST/Myc.Ast.RTL.pas | 28 ++-- Src/AST/Myc.Ast.Scope.pas | 128 ++++++++++++--- Src/Myc.Trade.Broker.pas | 9 +- Test/Demo.Finance.pas | 11 +- 8 files changed, 263 insertions(+), 179 deletions(-) diff --git a/Src/AST/Myc.Ast.Environment.pas b/Src/AST/Myc.Ast.Environment.pas index ae573b7..a396f28 100644 --- a/Src/AST/Myc.Ast.Environment.pas +++ b/Src/AST/Myc.Ast.Environment.pas @@ -98,7 +98,8 @@ type function Run(const ANode: IAstNode; const Params: TArray = []; const Args: TArray = []): 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 diff --git a/Src/AST/Myc.Ast.Json.Schema.pas b/Src/AST/Myc.Ast.Json.Schema.pas index 40c3536..5b7b551 100644 --- a/Src/AST/Myc.Ast.Json.Schema.pas +++ b/Src/AST/Myc.Ast.Json.Schema.pas @@ -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; - 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; 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.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.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.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.Create; + + var sigStrings := TList.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.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; -var - ctx: TRttiContext; - typ: TRttiType; - meth: TRttiMethod; - attr: TCustomAttribute; - meta: TRtlMeta; - rtlName: string; - sigs: TList; -begin - Result := TDictionary.Create; - sigs := TList.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.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.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; layout: IScopeLayout; + descriptor: IScopeDescriptor; symbols: TArray; - 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(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; - sb: TStringBuilder; - pair: TPair; 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; diff --git a/Src/AST/Myc.Ast.RTL.Core.pas b/Src/AST/Myc.Ast.RTL.Core.pas index 15ebeed..2f2bac5 100644 --- a/Src/AST/Myc.Ast.RTL.Core.pas +++ b/Src/AST/Myc.Ast.RTL.Core.pas @@ -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; diff --git a/Src/AST/Myc.Ast.RTL.TypeRegistry.pas b/Src/AST/Myc.Ast.RTL.TypeRegistry.pas index 6dc3cce..7fd1c12 100644 --- a/Src/AST/Myc.Ast.RTL.TypeRegistry.pas +++ b/Src/AST/Myc.Ast.RTL.TypeRegistry.pas @@ -56,11 +56,13 @@ type // --- Factories --- // Registers a factory function. T must be registered via RegisterType first. + // UPDATED: Now supports documentation parameter. class procedure RegisterFactory( const Scope: IExecutionScope; const FactoryName: string; const FactoryArgs: TArray; - const FactoryDelegate: TInterfaceFactoryDelegate + const FactoryDelegate: TInterfaceFactoryDelegate; + 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( const Scope: IExecutionScope; const FactoryName: string; const FactoryArgs: TArray; - const FactoryDelegate: TInterfaceFactoryDelegate + const FactoryDelegate: TInterfaceFactoryDelegate; + 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. diff --git a/Src/AST/Myc.Ast.RTL.pas b/Src/AST/Myc.Ast.RTL.pas index d710932..ec65b8d 100644 --- a/Src/AST/Myc.Ast.RTL.pas +++ b/Src/AST/Myc.Ast.RTL.pas @@ -60,6 +60,7 @@ type public DynamicWrapper: TDataValue.TFunc; Signatures: TList; + 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.Create; DynamicWrapper := nil; + Doc := ''; end; destructor TRtlFunctionInfo.Destroy; @@ -262,6 +265,7 @@ var rttiParams: TArray; 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; diff --git a/Src/AST/Myc.Ast.Scope.pas b/Src/AST/Myc.Ast.Scope.pas index 6202c00..5dc58c3 100644 --- a/Src/AST/Myc.Ast.Scope.pas +++ b/Src/AST/Myc.Ast.Scope.pas @@ -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; @@ -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; - FSlotCount: Integer; // Explicit slot count + FMap: TDictionary; + FSlotCount: Integer; function GetParent: IScopeLayout; function GetSlotCount: Integer; public - constructor Create(const AParent: IScopeLayout; AMap: TDictionary; ASlotCount: Integer); + constructor Create(const AParent: IScopeLayout; AMap: TDictionary; ASlotCount: Integer); destructor Destroy; override; function FindSlot(const Name: string): Integer; + function GetSymbolDoc(const Name: string): string; function GetSymbols: TArray; end; @@ -148,16 +162,17 @@ type TScopeBuilder = class(TInterfacedObject, IScopeBuilder, IScopeLayout) private FParentLayout: IScopeLayout; - FMap: TDictionary; - FNextSlot: Integer; // Counter for sequential slot allocation + FMap: TDictionary; + 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; 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; end; @@ -224,9 +240,12 @@ type FSlotTypes: TArray; FValues: TArray; FCapturedUpvalues: TArray; + + // Dynamic Name Mapping FNames: TDictionary; FNameStrings: TList; FNameToIndex: TDictionary; + FDocs: TDictionary; // 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; ASlotCount: Integer); +constructor TScopeLayout.Create(const AParent: IScopeLayout; AMap: TDictionary; 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; begin Result := FMap.Keys.ToArray; @@ -343,7 +389,7 @@ constructor TScopeBuilder.Create(const AParentLayout: IScopeLayout); begin inherited Create; FParentLayout := AParentLayout; - FMap := TDictionary.Create; + FMap := TDictionary.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; 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.Create(FMap), FNextSlot); + Result := TScopeLayout.Create(FParentLayout, TDictionary.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; begin Result := FOwner.FNames.Keys.ToArray; @@ -532,6 +597,8 @@ begin FNameStrings := TList.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.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.Create, 0); + Result := TScopeLayout.Create(nil, TDictionary.Create, 0); end; end. diff --git a/Src/Myc.Trade.Broker.pas b/Src/Myc.Trade.Broker.pas index fd95ce4..ca4a7e2 100644 --- a/Src/Myc.Trade.Broker.pas +++ b/Src/Myc.Trade.Broker.pas @@ -16,7 +16,8 @@ uses type TSymProc = reference to procedure(const Symbol: String; Amount: Integer); - // Das Interface, das im AST-Skript verfgbar sein wird. + // Das Interface, das im AST-Skript als Record-Struktur verfgbar sein wird. + // Die Methodennamen dienen als Dokumentation. IMycBroker = interface ['{D8E9F1A2-B3C4-4D5E-F6A7-B8C9D0E1F2A3}'] @@ -27,7 +28,6 @@ type function GetPosition(const Symbol: string): Integer; // Iteriert ber alle Positionen und ruft fr jede den Callback auf. - // Bentigt Registrierung von TProc. procedure ListPositions(const Callback: TSymProc); property Cash: Double read GetCash; @@ -138,8 +138,6 @@ end; procedure RegisterBroker(const Scope: IExecutionScope); begin // 1. Registrierung des Callback-Typs. - // Dies ermglicht dem TypeRegistry-Marshaller, die Signatur von Invoke(string, integer) zu analysieren - // und AST-Lambdas (TDataValue.TFunc) in native TProc zu wrappen. TRtlTypeRegistry.RegisterType; // 2. Registrierung des Broker-Interfaces @@ -152,7 +150,8 @@ begin Scope, 'create-broker', argTypes, - function(const Args: TArray): IMycBroker begin Result := TMockBroker.Create(Args[0].AsScalar.Value.AsDouble); end + function(const Args: TArray): IMycBroker begin Result := TMockBroker.Create(Args[0].AsScalar.Value.AsDouble); end, + 'Creates a new mock broker instance with the specified initial cash.' // Doc fr das Symbol selbst ); end; diff --git a/Test/Demo.Finance.pas b/Test/Demo.Finance.pas index 9560383..0ec4506 100644 --- a/Test/Demo.Finance.pas +++ b/Test/Demo.Finance.pas @@ -85,7 +85,8 @@ begin // Fr den Compiler ist es eine RecordSeries (Typ), zur Laufzeit ein Stream (Wert) StaticType := TTypes.CreateRecordSeries(Def); - Scope.Define(VarName, Stream, StaticType); + // UPDATED: Dokumentation fr die Stream-Variable hinzufgen + Scope.Define(VarName, Stream, StaticType, 'Financial OHLC Data Stream (Time, Open, High, Low, Close, Vol).'); end; procedure TFinanceSimulator.AddRandomCandle(const Scope: IExecutionScope; const VarName: string); @@ -169,7 +170,8 @@ begin Sim.CreateOHLCSeries(Scope, Args[0].AsText); Result := TDataValue.Void; end, - Sig + Sig, + 'Creates a new OHLC data stream with the specified name.' // UPDATED: Doc ); Scope.Define( @@ -181,10 +183,11 @@ begin Sim.AddRandomCandle(Scope, Args[0].AsText); Result := TDataValue.Void; end, - Sig + Sig, + 'Simulates and pushes a new 5-minute candle to the specified stream.' // UPDATED: Doc ); - // Some Demo-Stream + // Some Demo-Stream (This will now also be documented in the scope) Sim.CreateOHLCSeries(Scope, 'btc'); end;