Ast Schema

This commit is contained in:
Michael Schimmel
2026-01-06 14:37:22 +01:00
parent 3b3966b2f7
commit 7313848538
6 changed files with 776 additions and 653 deletions
+3 -1
View File
@@ -42,7 +42,9 @@ uses
Myc.Data.Stream in '..\Src\Data\Myc.Data.Stream.pas',
Myc.Fmx.AstEditor.Handlers.Pipes in '..\Src\AST\Myc.Fmx.AstEditor.Handlers.Pipes.pas',
Demo.Finance in '..\Test\Demo.Finance.pas',
Myc.Ast.Script.Print in '..\Src\AST\Myc.Ast.Script.Print.pas';
Myc.Ast.Script.Print in '..\Src\AST\Myc.Ast.Script.Print.pas',
Myc.Ast.Json.Schema in '..\Src\AST\Myc.Ast.Json.Schema.pas',
Myc.Ast.Attributes in 'Myc.Ast.Attributes.pas';
{$R *.res}
+2
View File
@@ -173,6 +173,8 @@
<DCCReference Include="..\Src\AST\Myc.Fmx.AstEditor.Handlers.Pipes.pas"/>
<DCCReference Include="..\Test\Demo.Finance.pas"/>
<DCCReference Include="..\Src\AST\Myc.Ast.Script.Print.pas"/>
<DCCReference Include="..\Src\AST\Myc.Ast.Json.Schema.pas"/>
<DCCReference Include="Myc.Ast.Attributes.pas"/>
<BuildConfiguration Include="Base">
<Key>Base</Key>
</BuildConfiguration>
+14 -6
View File
@@ -43,6 +43,7 @@ uses
Myc.Ast.Environment,
Myc.Ast.RTL,
Myc.Ast.Compiler.Macros,
Myc.Ast.Json.Schema,
// Editor Units
Myc.Fmx.AstEditor,
Demo.Finance,
@@ -286,6 +287,8 @@ begin
CompilerStageBox.BringToFront;
ClearButtonClick(Self);
Memo1.Lines.Text := TAstSchema.GenerateFullSystemPrompt;
end;
procedure TForm1.ShowVizualization;
@@ -1223,24 +1226,29 @@ end;
procedure TForm1.ToJSONButtonClick(Sender: TObject);
var
jsonObj: TJSONObject;
jsonVal: TJSONValue;
converter: IJsonAstConverter;
begin
Memo1.Lines.Clear;
if not Assigned(FCurrExec.Func) then
// Wir prüfen besser direkt, ob der AST da ist, den wir serialisieren wollen
if not Assigned(FCurrUnboundAst) then
begin
Memo1.Lines.Add('No *compiled* AST has been generated yet. Click a test button first.');
Memo1.Lines.Add('No AST has been generated yet. Click a test button first.');
exit;
end;
try
converter := TJsonAstConverter.Create;
jsonObj := converter.Serialize(FCurrUnboundAst);
// Serialize liefert jetzt TJSONValue (Compact Array Format)
jsonVal := converter.Serialize(FCurrUnboundAst);
try
Memo1.Lines.Text := jsonObj.Format(4);
if Assigned(jsonVal) then
Memo1.Lines.Text := jsonVal.Format(4)
else
Memo1.Lines.Text := 'null';
finally
jsonObj.Free;
jsonVal.Free;
end;
except
on E: Exception do
+453 -638
View File
File diff suppressed because it is too large Load Diff
+219
View File
@@ -0,0 +1,219 @@
unit Myc.Ast.Json.Schema;
interface
uses
System.SysUtils,
System.Classes,
System.Generics.Collections,
System.Generics.Defaults,
System.Rtti,
System.TypInfo,
Myc.Ast.Nodes,
Myc.Ast.Attributes;
type
TAstSchema = class
public
// Generates the TypeScript definition by scanning classes via RTTI
class function GenerateTypeScriptDefinition: string;
// Static rules for the format
class function GenerateGenerationRules: string;
// Combined prompt
class function GenerateFullSystemPrompt: string;
end;
implementation
type
// Helper structure to collect fields per type
// Moved here to avoid scope issues with generics
TFieldInfo = record
Idx: Integer;
Def: string;
end;
{ TAstSchema }
class function TAstSchema.GenerateTypeScriptDefinition: string;
var
ctx: TRttiContext;
types: TArray<TRttiType>;
typ: TRttiType;
attr: TCustomAttribute;
fieldAttr: AstFieldAttribute;
nodeDefs: TDictionary<string, TList<TFieldInfo>>;
unionTypes: TList<string>;
fieldsList: TList<TFieldInfo>;
sb: TStringBuilder;
tag: string;
fieldInfo: TFieldInfo;
f: TRttiField;
i: Integer;
function GetTsType(Kind: TFieldKind): string;
begin
case Kind of
fkNode: Result := 'AstNode';
fkTuple: Result := 'Tuple';
fkString: Result := 'string';
fkValue: Result := 'number | string | boolean';
fkNullableNode: Result := 'AstNode | null';
fkArrayOfPairs: Result := 'Array<[AstNode, AstNode]>';
else
Result := 'any';
end;
end;
begin
ctx := TRttiContext.Create;
nodeDefs := TDictionary<string, TList<TFieldInfo>>.Create;
unionTypes := TList<string>.Create;
sb := TStringBuilder.Create;
try
// 1. Scan all types in the application context
types := ctx.GetTypes;
for typ in types do
begin
if not typ.IsInstance then
Continue;
// Check for [AstTag] attribute on the class
tag := '';
for attr in typ.GetAttributes do
if attr is AstTagAttribute then
begin
tag := AstTagAttribute(attr).Tag;
Break;
end;
if tag = '' then
Continue;
if not nodeDefs.ContainsKey(tag) then
begin
nodeDefs.Add(tag, TList<TFieldInfo>.Create);
unionTypes.Add(tag);
end;
fieldsList := nodeDefs[tag];
// Scan Fields (Variables) of the class
for f in typ.AsInstance.GetFields do
begin
for attr in f.GetAttributes do
if attr is AstFieldAttribute then
begin
fieldAttr := AstFieldAttribute(attr);
fieldInfo.Idx := fieldAttr.Index;
fieldInfo.Def := GetTsType(fieldAttr.Kind) + ' /*' + fieldAttr.Name + '*/';
fieldsList.Add(fieldInfo);
end;
end;
end;
// 2. Build the output
sb.AppendLine('// AST Schema Definition (Auto-Generated via RTTI)');
sb.AppendLine('// Format: Compact JSON Arrays [Tag, Arg1, Arg2, ...]');
sb.AppendLine;
unionTypes.Sort;
// Union Type Definition
sb.AppendLine('type AstNode = ');
for i := 0 to unionTypes.Count - 1 do
begin
sb.Append(' | ' + unionTypes[i]);
if i < unionTypes.Count - 1 then
sb.AppendLine;
end;
sb.AppendLine(';');
sb.AppendLine;
sb.AppendLine('type Tuple = ["Tuple", AstNode[]];');
sb.AppendLine;
// Node Definitions
for tag in unionTypes do
begin
// Tuple is special base type
if tag = 'Tuple' then
Continue;
fieldsList := nodeDefs[tag];
// Sort fields by Index to ensure correct JSON array order
fieldsList.Sort(TComparer<TFieldInfo>.Construct(function(const L, R: TFieldInfo): Integer begin Result := L.Idx - R.Idx; end));
sb.Append('type ' + tag + ' = ["' + tag + '"');
for fieldInfo in fieldsList do
sb.Append(', ' + fieldInfo.Def);
sb.AppendLine('];');
end;
Result := sb.ToString;
finally
// Clean up lists in dictionary
for tag in nodeDefs.Keys do
nodeDefs[tag].Free;
nodeDefs.Free;
unionTypes.Free;
sb.Free;
ctx.Free;
end;
end;
class function TAstSchema.GenerateGenerationRules: string;
var
sb: TStringBuilder;
begin
// These rules describe the PROTOCOL (Compact Arrays), not the content.
// They remain static unless you change TJsonAstConverter logic significantly.
sb := TStringBuilder.Create;
try
sb.AppendLine('**⚠️ CRITICAL GENERATION RULES:**');
sb.AppendLine;
sb.AppendLine('1. **NO JSON OBJECTS:** Do not use `{ "key": "value" }`. Every node MUST be a JSON Array `["Tag", Arg1, Arg2]`.');
sb.AppendLine(
'2. **EXPLICIT TUPLES:** Whenever a field in the schema is defined as `Tuple`, you MUST wrap the list in a `["Tuple", [...]]` node.'
);
sb.AppendLine(
'3. **EXPLICIT NULLS:** Optional fields (marked as `| null`) MUST be explicit `null` if not present. Do not omit them.'
);
sb.AppendLine('4. **STRICT ORDER:** The order of elements in the array is fixed defined by the schema. Never swap arguments.');
Result := sb.ToString;
finally
sb.Free;
end;
end;
class function TAstSchema.GenerateFullSystemPrompt: string;
begin
// Combine everything
Result :=
'You are a compiler frontend for the "Myc" scripting language.'
+ sLineBreak
+ 'Your task is to generate a valid Abstract Syntax Tree (AST) in a specific Compact JSON format.'
+ sLineBreak
+ sLineBreak
+ '### 1. AST Schema (TypeScript Definition)'
+ sLineBreak
+ GenerateTypeScriptDefinition
+ sLineBreak
+ '### 2. Constraints & Rules'
+ sLineBreak
+ GenerateGenerationRules;
end;
end.
+85 -8
View File
@@ -1,3 +1,6 @@
//==================================================================================================
//== FULL UNIT START: Myc.Ast.Nodes (from Myc.Ast.Nodes.pas)
//==================================================================================================
unit Myc.Ast.Nodes;
interface
@@ -10,7 +13,8 @@ uses
Myc.Data.Keyword,
Myc.Ast.Scope,
Myc.Ast.Types,
Myc.Ast.Identities;
Myc.Ast.Identities,
Myc.Ast.Attributes; // Hinzugefügt für [AstTag] und [AstField]
type
// --- Forward Declarations ---
@@ -197,7 +201,6 @@ type
function AsRecur: IRecurNode;
function AsNop: INopNode;
// Pipes
function AsPipe: IPipeNode;
property IsTyped: Boolean read GetIsTyped;
@@ -528,9 +531,12 @@ type
property StaticType: IStaticType read GetStaticType;
end;
[AstTag('Field')]
TRecordFieldNode = class(TAstNode, IRecordFieldNode)
private
[AstField(0, 'key', fkNode)]
FKey: IKeywordNode;
[AstField(1, 'value', fkNode)]
FValue: IAstNode;
function GetKey: IKeywordNode;
function GetValue: IAstNode;
@@ -543,8 +549,10 @@ type
// --- Core Nodes Implementations ---
[AstTag('Const')]
TConstantNode = class(TAstTypedNode, IConstantNode)
private
[AstField(0, 'value', fkValue)]
FConstIdentity: IConstantIdentity; // Typed reference for fast access
function GetValue: TDataValue;
protected
@@ -554,8 +562,10 @@ type
function AsConstant: IConstantNode; override;
end;
[AstTag('Id')]
TIdentifierNode = class(TAstTypedNode, IIdentifierNode)
private
[AstField(0, 'name', fkString)]
FNamedIdentity: INamedIdentity; // Typed reference for fast access
FAddress: TResolvedAddress;
function GetAddress: TResolvedAddress;
@@ -567,8 +577,10 @@ type
function AsIdentifier: IIdentifierNode; override;
end;
[AstTag('Key')]
TKeywordNode = class(TAstTypedNode, IKeywordNode)
private
[AstField(0, 'name', fkString)]
FKeywordIdentity: IKeywordIdentity;
function GetValue: IKeyword;
protected
@@ -578,8 +590,10 @@ type
function AsKeyword: IKeywordNode; override;
end;
[AstTag('Tuple')]
TTupleNode = class(TAstTypedNode, ITupleNode)
private
[AstField(0, 'elements', fkNode)] // Represents array
FElements: TArray<IAstNode>;
function GetElements: TArray<IAstNode>;
protected
@@ -589,8 +603,10 @@ type
function AsTuple: ITupleNode; override;
end;
[AstTag('Series')]
TCreateSeriesNode = class(TAstTypedNode, ICreateSeriesNode)
private
[AstField(0, 'definition', fkNode)]
FDefinitionNode: IAstNode;
FRecordDefinition: IScalarRecordDefinition;
function GetDefinitionNode: IAstNode;
@@ -609,9 +625,15 @@ type
// --- Structural Nodes (Identity only for location) ---
[AstTag('If')]
TIfExpressionNode = class(TAstTypedNode, IIfExpressionNode)
private
FCondition, FThenBranch, FElseBranch: IAstNode;
[AstField(0, 'cond', fkNode)]
FCondition: IAstNode;
[AstField(1, 'then', fkNode)]
FThenBranch: IAstNode;
[AstField(2, 'else', fkNullableNode)]
FElseBranch: IAstNode;
function GetCondition: IAstNode;
function GetThenBranch: IAstNode;
function GetElseBranch: IAstNode;
@@ -626,9 +648,12 @@ type
function AsIfExpression: IIfExpressionNode; override;
end;
[AstTag('Cond')]
TCondExpressionNode = class(TAstTypedNode, ICondExpressionNode)
private
[AstField(0, 'pairs', fkArrayOfPairs)]
FPairs: TArray<TCondPair>;
[AstField(1, 'else', fkNullableNode)]
FElseBranch: IAstNode;
function GetPairs: TArray<TCondPair>;
function GetElseBranch: IAstNode;
@@ -644,9 +669,12 @@ type
function AsCondExpression: ICondExpressionNode; override;
end;
[AstTag('Fn')]
TLambdaExpressionNode = class(TAstTypedNode, ILambdaExpressionNode, IFunctionDefinition)
private
[AstField(0, 'params', fkTuple)]
FParameters: ITupleNode;
[AstField(1, 'body', fkNode)]
FBody: IAstNode;
FLayout: IScopeLayout;
FDescriptor: IScopeDescriptor;
@@ -675,9 +703,12 @@ type
function AsLambdaExpression: ILambdaExpressionNode; override;
end;
[AstTag('Call')]
TFunctionCallNode = class(TAstTypedNode, IFunctionCallNode)
private
[AstField(0, 'callee', fkNode)]
FCallee: IAstNode;
[AstField(1, 'args', fkTuple)]
FArguments: ITupleNode;
FIsTailCall: Boolean;
FStaticTarget: TDataValue.TFunc;
@@ -702,10 +733,14 @@ type
function AsFunctionCall: IFunctionCallNode; override;
end;
[AstTag('Macro')]
TMacroDefinitionNode = class(TAstTypedNode, IMacroDefinitionNode)
private
[AstField(0, 'name', fkNode)]
FName: IIdentifierNode;
[AstField(1, 'params', fkTuple)]
FParameters: ITupleNode;
[AstField(2, 'body', fkNode)]
FBody: IAstNode;
function GetName: IIdentifierNode;
function GetParameters: ITupleNode;
@@ -735,8 +770,10 @@ type
function AsMacroExpansion: IMacroExpansionNode; override;
end;
[AstTag('Quote')]
TQuasiquoteNode = class(TAstNode, IQuasiquoteNode)
private
[AstField(0, 'expr', fkNode)]
FExpression: IAstNode;
function GetExpression: IAstNode;
protected
@@ -746,8 +783,10 @@ type
function AsQuasiquote: IQuasiquoteNode; override;
end;
[AstTag('Unquote')]
TUnquoteNode = class(TAstNode, IUnquoteNode)
private
[AstField(0, 'expr', fkNode)]
FExpression: IAstNode;
function GetExpression: IAstNode;
protected
@@ -757,8 +796,10 @@ type
function AsUnquote: IUnquoteNode; override;
end;
[AstTag('Splice')]
TUnquoteSplicingNode = class(TAstNode, IUnquoteSplicingNode)
private
[AstField(0, 'expr', fkNode)]
FExpression: IQuasiquoteNode;
function GetExpression: IQuasiquoteNode;
protected
@@ -768,8 +809,10 @@ type
function AsUnquoteSplicing: IUnquoteSplicingNode; override;
end;
[AstTag('Recur')]
TRecurNode = class(TAstTypedNode, IRecurNode)
private
[AstField(0, 'args', fkTuple)]
FArguments: ITupleNode;
function GetArguments: ITupleNode;
protected
@@ -779,8 +822,10 @@ type
function AsRecur: IRecurNode; override;
end;
[AstTag('Block')]
TBlockExpressionNode = class(TAstTypedNode, IBlockExpressionNode)
private
[AstField(0, 'expressions', fkTuple)]
FExpressions: ITupleNode;
function GetExpressions: ITupleNode;
protected
@@ -790,9 +835,13 @@ type
function AsBlockExpression: IBlockExpressionNode; override;
end;
[AstTag('Var')]
TVariableDeclarationNode = class(TAstTypedNode, IVariableDeclarationNode)
private
FTarget, FInitializer: IAstNode;
[AstField(0, 'target', fkNode)]
FTarget: IAstNode;
[AstField(1, 'init', fkNullableNode)]
FInitializer: IAstNode;
FIsBoxed: Boolean;
function GetTarget: IAstNode;
function GetInitializer: IAstNode;
@@ -810,9 +859,13 @@ type
function AsVariableDeclaration: IVariableDeclarationNode; override;
end;
[AstTag('Assign')]
TAssignmentNode = class(TAstTypedNode, IAssignmentNode)
private
FTarget, FValue: IAstNode;
[AstField(0, 'target', fkNode)]
FTarget: IAstNode;
[AstField(1, 'value', fkNode)]
FValue: IAstNode;
function GetTarget: IAstNode;
function GetValue: IAstNode;
protected
@@ -822,9 +875,13 @@ type
function AsAssignment: IAssignmentNode; override;
end;
[AstTag('Index')]
TIndexerNode = class(TAstTypedNode, IIndexerNode)
private
FBase, FIndex: IAstNode;
[AstField(0, 'base', fkNode)]
FBase: IAstNode;
[AstField(1, 'index', fkNode)]
FIndex: IAstNode;
function GetBase: IAstNode;
function GetIndex: IAstNode;
protected
@@ -834,9 +891,12 @@ type
function AsIndexer: IIndexerNode; override;
end;
[AstTag('Member')]
TMemberAccessNode = class(TAstTypedNode, IMemberAccessNode)
private
[AstField(0, 'base', fkNode)]
FBase: IAstNode;
[AstField(1, 'member', fkNode)]
FMember: IKeywordNode;
function GetBase: IAstNode;
function GetMember: IKeywordNode;
@@ -852,8 +912,10 @@ type
function AsMemberAccess: IMemberAccessNode; override;
end;
[AstTag('Record')]
TRecordLiteralNode = class(TAstTypedNode, IRecordLiteralNode)
private
[AstField(0, 'fields', fkTuple)]
FFields: ITupleNode;
FScalarDef: IScalarRecordDefinition;
FGenericDef: IGenericRecordDefinition;
@@ -873,10 +935,15 @@ type
function AsRecordLiteral: IRecordLiteralNode; override;
end;
[AstTag('Add')]
TAddSeriesItemNode = class(TAstTypedNode, IAddSeriesItemNode)
private
[AstField(0, 'series', fkNode)]
FSeries: IIdentifierNode;
FValue, FLookback: IAstNode;
[AstField(1, 'value', fkNode)]
FValue: IAstNode;
[AstField(2, 'lookback', fkNullableNode)]
FLookback: IAstNode;
function GetSeries: IIdentifierNode;
function GetValue: IAstNode;
function GetLookback: IAstNode;
@@ -892,8 +959,10 @@ type
function AsAddSeriesItem: IAddSeriesItemNode; override;
end;
[AstTag('Count')]
TSeriesLengthNode = class(TAstTypedNode, ISeriesLengthNode)
private
[AstField(0, 'series', fkNode)]
FSeries: IIdentifierNode;
function GetSeries: IIdentifierNode;
protected
@@ -903,6 +972,7 @@ type
function AsSeriesLength: ISeriesLengthNode; override;
end;
[AstTag('Nop')]
TNopNode = class(TAstTypedNode, INopNode)
protected
function GetKind: TAstNodeKind; override;
@@ -911,9 +981,12 @@ type
function AsNop: INopNode; override;
end;
[AstTag('Pipe')]
TPipeNode = class(TAstTypedNode, IPipeNode)
private
[AstField(0, 'inputs', fkTuple)]
FInputs: ITupleNode;
[AstField(1, 'transform', fkNode)]
FTransformation: ILambdaExpressionNode;
function GetInputs: ITupleNode;
function GetTransformation: ILambdaExpressionNode;
@@ -933,6 +1006,7 @@ implementation
uses
System.Classes,
System.TypInfo,
System.Rtti,
System.StrUtils;
@@ -949,7 +1023,7 @@ end;
function TAstNodeKindHelper.ToString: string;
begin
Result := TRttiEnumerationType.GetName<TAstNodeKind>(Self);
Result := GetEnumName(TypeInfo(TAstNodeKind), Ord(Self));
Assert(StartsText('ak', Result));
Result := Result.Substring(2);
end;
@@ -2055,3 +2129,6 @@ begin
end;
end.
//==================================================================================================
//== FULL UNIT END: Myc.Ast.Nodes
//==================================================================================================