Files
MycLib/Src/AST/Myc.Ast.JSON.pas
T
Michael Schimmel 7313848538 Ast Schema
2026-01-06 14:37:22 +01:00

600 lines
20 KiB
ObjectPascal

//==================================================================================================
//== FULL UNIT START: Myc.Ast.Json (from Myc.Ast.Json.pas)
//==================================================================================================
unit Myc.Ast.Json;
interface
uses
System.SysUtils,
System.Generics.Collections,
System.JSON,
Myc.Data.Scalar,
Myc.Data.Value,
Myc.Ast,
Myc.Ast.Visitor,
Myc.Ast.Nodes;
type
IJsonAstConverter = interface
function Serialize(const RootNode: IAstNode): TJSONValue;
function Deserialize(const AJson: TJSONValue): IAstNode;
end;
// Compact JSON Converter: [Kind, Arg1, Arg2, ...]
TJsonAstConverter = class(TAstVisitor<TJSONValue>, IJsonAstConverter)
private
// Helpers for compact data values
function DataValueToJson(const AValue: TDataValue): TJSONValue;
function JsonToDataValue(const AJson: TJSONValue): TDataValue;
// Deserialization Dispatcher
function JsonToNode(const AJson: TJSONValue): IAstNode;
function JsonToTuple(const AArray: TJSONArray): ITupleNode;
strict private
// Serialization Visitors (Array Builders)
function VisitConstant(const Node: IAstNode): TJSONValue;
function VisitIdentifier(const Node: IAstNode): TJSONValue;
function VisitKeyword(const Node: IAstNode): TJSONValue;
function VisitTuple(const Node: IAstNode): TJSONValue;
function VisitRecordField(const Node: IAstNode): TJSONValue;
function VisitIfExpression(const Node: IAstNode): TJSONValue;
function VisitCondExpression(const Node: IAstNode): TJSONValue;
function VisitLambdaExpression(const Node: IAstNode): TJSONValue;
function VisitMacroDefinition(const Node: IAstNode): TJSONValue;
function VisitQuasiquote(const Node: IAstNode): TJSONValue;
function VisitUnquote(const Node: IAstNode): TJSONValue;
function VisitUnquoteSplicing(const Node: IAstNode): TJSONValue;
function VisitFunctionCall(const Node: IAstNode): TJSONValue;
function VisitMacroExpansionNode(const Node: IAstNode): TJSONValue;
function VisitRecurNode(const Node: IAstNode): TJSONValue;
function VisitBlockExpression(const Node: IAstNode): TJSONValue;
function VisitVariableDeclaration(const Node: IAstNode): TJSONValue;
function VisitAssignment(const Node: IAstNode): TJSONValue;
function VisitIndexer(const Node: IAstNode): TJSONValue;
function VisitMemberAccess(const Node: IAstNode): TJSONValue;
function VisitRecordLiteral(const Node: IAstNode): TJSONValue;
function VisitCreateSeries(const Node: IAstNode): TJSONValue;
function VisitAddSeriesItem(const Node: IAstNode): TJSONValue;
function VisitSeriesLength(const Node: IAstNode): TJSONValue;
function VisitNop(const Node: IAstNode): TJSONValue;
function VisitPipe(const Node: IAstNode): TJSONValue;
protected
procedure SetupHandlers; override;
public
constructor Create;
function Serialize(const RootNode: IAstNode): TJSONValue;
function Deserialize(const AJson: TJSONValue): IAstNode;
end;
implementation
uses
Myc.Data.Keyword,
Myc.Ast.Identities;
{ TJsonAstConverter }
constructor TJsonAstConverter.Create;
begin
inherited;
end;
procedure TJsonAstConverter.SetupHandlers;
begin
Register(akConstant, VisitConstant);
Register(akIdentifier, VisitIdentifier);
Register(akKeyword, VisitKeyword);
Register(akTuple, VisitTuple);
Register(akRecordField, VisitRecordField);
Register(akIfExpression, VisitIfExpression);
Register(akCondExpression, VisitCondExpression);
Register(akLambdaExpression, VisitLambdaExpression);
Register(akFunctionCall, VisitFunctionCall);
Register(akMacroExpansion, VisitMacroExpansionNode);
Register(akBlockExpression, VisitBlockExpression);
Register(akVariableDeclaration, VisitVariableDeclaration);
Register(akAssignment, VisitAssignment);
Register(akMacroDefinition, VisitMacroDefinition);
Register(akQuasiquote, VisitQuasiquote);
Register(akUnquote, VisitUnquote);
Register(akUnquoteSplicing, VisitUnquoteSplicing);
Register(akIndexer, VisitIndexer);
Register(akMemberAccess, VisitMemberAccess);
Register(akRecordLiteral, VisitRecordLiteral);
Register(akCreateSeries, VisitCreateSeries);
Register(akAddSeriesItem, VisitAddSeriesItem);
Register(akSeriesLength, VisitSeriesLength);
Register(akRecur, VisitRecurNode);
Register(akNop, VisitNop);
Register(akPipe, VisitPipe);
end;
function TJsonAstConverter.Serialize(const RootNode: IAstNode): TJSONValue;
begin
if not Assigned(RootNode) then
exit(TJSONNull.Create);
Result := Visit(RootNode);
end;
function TJsonAstConverter.Deserialize(const AJson: TJSONValue): IAstNode;
begin
Result := JsonToNode(AJson);
end;
// --- Serialization (AST -> JSON Array) ---
function TJsonAstConverter.VisitIdentifier(const Node: IAstNode): TJSONValue;
begin
var arr := TJSONArray.Create;
arr.AddElement(TJSONString.Create('Id'));
arr.AddElement(TJSONString.Create(Node.AsIdentifier.Name));
Result := arr;
end;
function TJsonAstConverter.VisitKeyword(const Node: IAstNode): TJSONValue;
begin
var arr := TJSONArray.Create;
arr.AddElement(TJSONString.Create('Key'));
arr.AddElement(TJSONString.Create(Node.AsKeyword.Value.Name));
Result := arr;
end;
function TJsonAstConverter.VisitConstant(const Node: IAstNode): TJSONValue;
begin
var arr := TJSONArray.Create;
arr.AddElement(TJSONString.Create('Const'));
arr.AddElement(DataValueToJson(Node.AsConstant.Value));
Result := arr;
end;
function TJsonAstConverter.DataValueToJson(const AValue: TDataValue): TJSONValue;
begin
case AValue.Kind of
vkScalar:
begin
case AValue.AsScalar.Kind of
TScalar.TKind.Ordinal: Result := TJSONNumber.Create(AValue.AsScalar.Value.AsInt64);
TScalar.TKind.Float: Result := TJSONNumber.Create(AValue.AsScalar.Value.AsDouble);
TScalar.TKind.Boolean: Result := TJSONBool.Create(AValue.AsScalar.Value.AsInt64 <> 0);
TScalar.TKind.Keyword:
begin
var obj := TJSONObject.Create;
obj.AddPair('Keyword', TKeywordRegistry.GetName(AValue.AsScalar.Value.AsInt64));
Result := obj;
end;
else
Result := TJSONNull.Create;
end;
end;
vkText: Result := TJSONString.Create(AValue.AsText);
vkVoid: Result := TJSONNull.Create;
else
Result := TJSONString.Create('<Complex>');
end;
end;
function TJsonAstConverter.VisitTuple(const Node: IAstNode): TJSONValue;
begin
var arr := TJSONArray.Create;
arr.AddElement(TJSONString.Create('Tuple'));
var elems := TJSONArray.Create;
for var item in Node.AsTuple.Elements do
elems.AddElement(Visit(item));
arr.AddElement(elems);
Result := arr;
end;
function TJsonAstConverter.VisitFunctionCall(const Node: IAstNode): TJSONValue;
var
C: IFunctionCallNode;
begin
C := Node.AsFunctionCall;
var arr := TJSONArray.Create;
arr.AddElement(TJSONString.Create('Call'));
arr.AddElement(Visit(C.Callee));
arr.AddElement(Visit(C.Arguments)); // Arguments is a Tuple
Result := arr;
end;
function TJsonAstConverter.VisitBlockExpression(const Node: IAstNode): TJSONValue;
begin
var arr := TJSONArray.Create;
arr.AddElement(TJSONString.Create('Block'));
arr.AddElement(Visit(Node.AsBlockExpression.Expressions)); // Tuple
Result := arr;
end;
function TJsonAstConverter.VisitLambdaExpression(const Node: IAstNode): TJSONValue;
var
L: ILambdaExpressionNode;
begin
L := Node.AsLambdaExpression;
var arr := TJSONArray.Create;
arr.AddElement(TJSONString.Create('Fn'));
arr.AddElement(Visit(L.Parameters)); // Tuple
arr.AddElement(Visit(L.Body));
Result := arr;
end;
function TJsonAstConverter.VisitMacroDefinition(const Node: IAstNode): TJSONValue;
var
M: IMacroDefinitionNode;
begin
M := Node.AsMacroDefinition;
var arr := TJSONArray.Create;
arr.AddElement(TJSONString.Create('Macro'));
arr.AddElement(Visit(M.Name));
arr.AddElement(Visit(M.Parameters));
arr.AddElement(Visit(M.Body));
Result := arr;
end;
function TJsonAstConverter.VisitVariableDeclaration(const Node: IAstNode): TJSONValue;
var
V: IVariableDeclarationNode;
begin
V := Node.AsVariableDeclaration;
var arr := TJSONArray.Create;
arr.AddElement(TJSONString.Create('Var'));
arr.AddElement(Visit(V.Target));
if Assigned(V.Initializer) then
arr.AddElement(Visit(V.Initializer))
else
arr.AddElement(TJSONNull.Create);
Result := arr;
end;
function TJsonAstConverter.VisitAssignment(const Node: IAstNode): TJSONValue;
begin
var arr := TJSONArray.Create;
arr.AddElement(TJSONString.Create('Assign'));
arr.AddElement(Visit(Node.AsAssignment.Target));
arr.AddElement(Visit(Node.AsAssignment.Value));
Result := arr;
end;
function TJsonAstConverter.VisitIfExpression(const Node: IAstNode): TJSONValue;
var
E: IIfExpressionNode;
begin
E := Node.AsIfExpression;
var arr := TJSONArray.Create;
arr.AddElement(TJSONString.Create('If'));
arr.AddElement(Visit(E.Condition));
arr.AddElement(Visit(E.ThenBranch));
if Assigned(E.ElseBranch) then
arr.AddElement(Visit(E.ElseBranch))
else
arr.AddElement(TJSONNull.Create);
Result := arr;
end;
function TJsonAstConverter.VisitCondExpression(const Node: IAstNode): TJSONValue;
var
E: ICondExpressionNode;
pairs: TJSONArray;
begin
E := Node.AsCondExpression;
var arr := TJSONArray.Create;
arr.AddElement(TJSONString.Create('Cond'));
pairs := TJSONArray.Create;
for var p in E.Pairs do
begin
var pArr := TJSONArray.Create;
pArr.AddElement(Visit(p.Condition));
pArr.AddElement(Visit(p.Branch));
pairs.AddElement(pArr);
end;
arr.AddElement(pairs);
if Assigned(E.ElseBranch) then
arr.AddElement(Visit(E.ElseBranch))
else
arr.AddElement(TJSONNull.Create);
Result := arr;
end;
function TJsonAstConverter.VisitQuasiquote(const Node: IAstNode): TJSONValue;
begin
var arr := TJSONArray.Create;
arr.AddElement(TJSONString.Create('Quote'));
arr.AddElement(Visit(Node.AsQuasiquote.Expression));
Result := arr;
end;
function TJsonAstConverter.VisitUnquote(const Node: IAstNode): TJSONValue;
begin
var arr := TJSONArray.Create;
arr.AddElement(TJSONString.Create('Unquote'));
arr.AddElement(Visit(Node.AsUnquote.Expression));
Result := arr;
end;
function TJsonAstConverter.VisitUnquoteSplicing(const Node: IAstNode): TJSONValue;
begin
var arr := TJSONArray.Create;
arr.AddElement(TJSONString.Create('Splice'));
arr.AddElement(Visit(Node.AsUnquoteSplicing.Expression));
Result := arr;
end;
function TJsonAstConverter.VisitRecurNode(const Node: IAstNode): TJSONValue;
begin
var arr := TJSONArray.Create;
arr.AddElement(TJSONString.Create('Recur'));
arr.AddElement(Visit(Node.AsRecur.Arguments)); // Tuple
Result := arr;
end;
function TJsonAstConverter.VisitIndexer(const Node: IAstNode): TJSONValue;
begin
var arr := TJSONArray.Create;
arr.AddElement(TJSONString.Create('Index'));
arr.AddElement(Visit(Node.AsIndexer.Base));
arr.AddElement(Visit(Node.AsIndexer.Index));
Result := arr;
end;
function TJsonAstConverter.VisitMemberAccess(const Node: IAstNode): TJSONValue;
begin
var arr := TJSONArray.Create;
arr.AddElement(TJSONString.Create('Member'));
arr.AddElement(Visit(Node.AsMemberAccess.Base));
arr.AddElement(Visit(Node.AsMemberAccess.Member));
Result := arr;
end;
function TJsonAstConverter.VisitRecordField(const Node: IAstNode): TJSONValue;
begin
// Helper for RecordLiteral
var arr := TJSONArray.Create;
arr.AddElement(TJSONString.Create('Field'));
arr.AddElement(Visit(Node.AsRecordField.Key));
arr.AddElement(Visit(Node.AsRecordField.Value));
Result := arr;
end;
function TJsonAstConverter.VisitRecordLiteral(const Node: IAstNode): TJSONValue;
begin
var arr := TJSONArray.Create;
arr.AddElement(TJSONString.Create('Record'));
arr.AddElement(Visit(Node.AsRecordLiteral.Fields)); // Tuple
Result := arr;
end;
function TJsonAstConverter.VisitCreateSeries(const Node: IAstNode): TJSONValue;
begin
var arr := TJSONArray.Create;
arr.AddElement(TJSONString.Create('Series'));
arr.AddElement(Visit(Node.AsCreateSeries.DefinitionNode));
Result := arr;
end;
function TJsonAstConverter.VisitAddSeriesItem(const Node: IAstNode): TJSONValue;
begin
var arr := TJSONArray.Create;
arr.AddElement(TJSONString.Create('Add'));
arr.AddElement(Visit(Node.AsAddSeriesItem.Series));
arr.AddElement(Visit(Node.AsAddSeriesItem.Value));
if Assigned(Node.AsAddSeriesItem.Lookback) then
arr.AddElement(Visit(Node.AsAddSeriesItem.Lookback))
else
arr.AddElement(TJSONNull.Create);
Result := arr;
end;
function TJsonAstConverter.VisitSeriesLength(const Node: IAstNode): TJSONValue;
begin
var arr := TJSONArray.Create;
arr.AddElement(TJSONString.Create('Count'));
arr.AddElement(Visit(Node.AsSeriesLength.Series));
Result := arr;
end;
function TJsonAstConverter.VisitPipe(const Node: IAstNode): TJSONValue;
begin
var arr := TJSONArray.Create;
arr.AddElement(TJSONString.Create('Pipe'));
arr.AddElement(Visit(Node.AsPipe.Inputs));
arr.AddElement(Visit(Node.AsPipe.Transformation));
Result := arr;
end;
function TJsonAstConverter.VisitNop(const Node: IAstNode): TJSONValue;
begin
Result := TJSONArray.Create;
TJSONArray(Result).AddElement(TJSONString.Create('Nop'));
end;
function TJsonAstConverter.VisitMacroExpansionNode(const Node: IAstNode): TJSONValue;
begin
// Serialize original call
Result := Visit(Node.AsMacroExpansion.CallNode);
end;
// --- Deserialization (JSON Array -> AST) ---
function TJsonAstConverter.JsonToDataValue(const AJson: TJSONValue): TDataValue;
var
s: string;
begin
if AJson is TJSONNumber then
begin
var d := (AJson as TJSONNumber).AsDouble;
if Frac(d) = 0 then
Result := TScalar.FromInt64(Trunc(d))
else
Result := TScalar.FromDouble(d);
end
else if AJson is TJSONString then
Result := (AJson as TJSONString).Value
else if AJson is TJSONBool then
Result := TScalar.FromBoolean((AJson as TJSONBool).AsBoolean)
else if (AJson is TJSONObject) and (AJson as TJSONObject).TryGetValue<string>('Keyword', s) then
Result := TScalar.FromKeyword(TKeywordRegistry.Intern(s))
else
Result := TDataValue.Void;
end;
function TJsonAstConverter.JsonToTuple(const AArray: TJSONArray): ITupleNode;
var
elems: TList<IAstNode>;
i: Integer;
begin
elems := TList<IAstNode>.Create;
try
for i := 0 to AArray.Count - 1 do
elems.Add(JsonToNode(AArray.Items[i]));
Result := TAst.Tuple(elems.ToArray);
finally
elems.Free;
end;
end;
function TJsonAstConverter.JsonToNode(const AJson: TJSONValue): IAstNode;
var
arr: TJSONArray;
kind: string;
begin
if AJson is TJSONNull then
exit(nil);
if not (AJson is TJSONArray) then
raise EInvalidCast.Create('AST Node must be a JSON Array');
arr := AJson as TJSONArray;
if arr.Count = 0 then
raise EInvalidCast.Create('Empty node array');
kind := arr.Items[0].Value;
if kind = 'Id' then
Exit(TAst.Identifier(arr.Items[1].Value));
if kind = 'Key' then
Exit(TAst.Keyword(arr.Items[1].Value));
if kind = 'Const' then
Exit(TAst.Constant(JsonToDataValue(arr.Items[1])));
if kind = 'Tuple' then
Exit(JsonToTuple(arr.Items[1] as TJSONArray));
if kind = 'Block' then
Exit(TAst.Block(JsonToTuple(arr.Items[1] as TJSONArray).Elements));
if kind = 'Nop' then
Exit(TAst.Nop);
if kind = 'Call' then
Exit(TAst.FunctionCall(JsonToNode(arr.Items[1]), JsonToTuple(arr.Items[2] as TJSONArray).Elements));
if kind = 'Fn' then
// Pass TIdentities.Structural as Identity to satisfy the overload requiring (Identity, Tuple, Body, ...)
Exit(TAst.LambdaExpr(TIdentities.Structural, JsonToTuple(arr.Items[1] as TJSONArray), JsonToNode(arr.Items[2])));
if kind = 'Macro' then
// Pass TIdentities.Structural as Identity
Exit(
TAst.MacroDef(
TIdentities.Structural,
JsonToNode(arr.Items[1]).AsIdentifier,
JsonToTuple(arr.Items[2] as TJSONArray),
JsonToNode(arr.Items[3])
)
);
if kind = 'Var' then
begin
var init: IAstNode := nil;
if arr.Count > 2 then
init := JsonToNode(arr.Items[2]);
Exit(TAst.VarDecl(JsonToNode(arr.Items[1]), init));
end;
if kind = 'Assign' then
Exit(TAst.Assign(JsonToNode(arr.Items[1]), JsonToNode(arr.Items[2])));
if kind = 'If' then
begin
var el: IAstNode := nil;
if arr.Count > 3 then
el := JsonToNode(arr.Items[3]);
Exit(TAst.IfExpr(JsonToNode(arr.Items[1]), JsonToNode(arr.Items[2]), el));
end;
if kind = 'Cond' then
begin
var pairArr := arr.Items[1] as TJSONArray;
var pairs: TArray<TCondPair>;
SetLength(pairs, pairArr.Count);
for var i := 0 to pairArr.Count - 1 do
begin
var p := pairArr.Items[i] as TJSONArray;
pairs[i] := TCondPair.Create(JsonToNode(p.Items[0]), JsonToNode(p.Items[1]));
end;
var el: IAstNode := nil;
if arr.Count > 2 then
el := JsonToNode(arr.Items[2]);
Exit(TAst.CondExpr(pairs, el));
end;
if kind = 'Quote' then
Exit(TAst.Quasiquote(JsonToNode(arr.Items[1])));
if kind = 'Unquote' then
Exit(TAst.Unquote(JsonToNode(arr.Items[1])));
if kind = 'Splice' then
Exit(TAst.UnquoteSplicing(JsonToNode(arr.Items[1]).AsQuasiquote));
if kind = 'Recur' then
Exit(TAst.Recur(JsonToTuple(arr.Items[1] as TJSONArray).Elements));
if kind = 'Index' then
Exit(TAst.Indexer(JsonToNode(arr.Items[1]), JsonToNode(arr.Items[2])));
if kind = 'Member' then
Exit(TAst.MemberAccess(JsonToNode(arr.Items[1]), JsonToNode(arr.Items[2]).AsKeyword));
if kind = 'Record' then
begin
var fieldsTuple := JsonToTuple(arr.Items[1] as TJSONArray);
var recFields: TList<IRecordFieldNode> := TList<IRecordFieldNode>.Create;
try
for var elem in fieldsTuple.Elements do
begin
if elem.Kind = akRecordField then
recFields.Add(elem.AsRecordField)
else
raise EInvalidCast.Create('Expected RecordField');
end;
Exit(TAst.RecordLiteral(recFields.ToArray));
finally
recFields.Free;
end;
end;
// Explicit RecordField deserializer support
if kind = 'Field' then
Exit(TAst.RecordField(JsonToNode(arr.Items[1]).AsKeyword, JsonToNode(arr.Items[2])));
if kind = 'Series' then
Exit(TAst.CreateSeries(JsonToNode(arr.Items[1])));
if kind = 'Add' then
begin
var lb: IAstNode := nil;
if arr.Count > 3 then
lb := JsonToNode(arr.Items[3]);
Exit(TAst.AddSeriesItem(JsonToNode(arr.Items[1]).AsIdentifier, JsonToNode(arr.Items[2]), lb));
end;
if kind = 'Count' then
Exit(TAst.SeriesLength(JsonToNode(arr.Items[1]).AsIdentifier));
if kind = 'Pipe' then
Exit(TAst.Pipe(JsonToTuple(arr.Items[1] as TJSONArray), JsonToNode(arr.Items[2]).AsLambdaExpression));
raise ENotSupportedException.Create('Unknown JSON AST Kind: ' + kind);
end;
end.
//==================================================================================================
//== FULL UNIT END: Myc.Ast.Json
//==================================================================================================