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: [Tag, Arg1, Arg2, ...] } TJsonAstConverter = class(TAstVisitor, IJsonAstConverter) private function DataValueToJson(const AValue: TDataValue): TJSONValue; function JsonToDataValue(const AJson: TJSONValue): TDataValue; function JsonToNode(const AJson: TJSONValue): IAstNode; function JsonToTuple(const AArray: TJSONArray): ITupleNode; strict private 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 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 VisitMacroDefinition(const Node: IAstNode): TJSONValue; function VisitQuasiquote(const Node: IAstNode): TJSONValue; function VisitUnquote(const Node: IAstNode): TJSONValue; function VisitUnquoteSplicing(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 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(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 Helpers --- 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(''); end; end; // --- Visitor Implementations --- function TJsonAstConverter.VisitConstant(const Node: IAstNode): TJSONValue; var arr: TJSONArray; begin arr := TJSONArray.Create; arr.Add('Const'); arr.AddElement(DataValueToJson(Node.AsConstant.Value)); Result := arr; end; function TJsonAstConverter.VisitIdentifier(const Node: IAstNode): TJSONValue; var arr: TJSONArray; begin arr := TJSONArray.Create; arr.Add('Id'); arr.Add(Node.AsIdentifier.Name); Result := arr; end; function TJsonAstConverter.VisitKeyword(const Node: IAstNode): TJSONValue; var arr: TJSONArray; begin arr := TJSONArray.Create; arr.Add('Key'); arr.Add(Node.AsKeyword.Value.Name); Result := arr; end; function TJsonAstConverter.VisitTuple(const Node: IAstNode): TJSONValue; var arr, elems: TJSONArray; begin arr := TJSONArray.Create; arr.Add('Tuple'); elems := TJSONArray.Create; for var item in Node.AsTuple.Elements do elems.AddElement(Visit(item)); arr.AddElement(elems); Result := arr; end; function TJsonAstConverter.VisitRecordField(const Node: IAstNode): TJSONValue; var arr: TJSONArray; begin arr := TJSONArray.Create; arr.Add('Field'); arr.AddElement(Visit(Node.AsRecordField.Key)); arr.AddElement(Visit(Node.AsRecordField.Value)); Result := arr; end; function TJsonAstConverter.VisitIfExpression(const Node: IAstNode): TJSONValue; var arr: TJSONArray; n: IIfExpressionNode; begin n := Node.AsIfExpression; arr := TJSONArray.Create; arr.Add('If'); arr.AddElement(Visit(n.Condition)); arr.AddElement(Visit(n.ThenBranch)); if Assigned(n.ElseBranch) then arr.AddElement(Visit(n.ElseBranch)) else arr.AddElement(TJSONNull.Create); Result := arr; end; function TJsonAstConverter.VisitCondExpression(const Node: IAstNode): TJSONValue; var arr, pairs: TJSONArray; n: ICondExpressionNode; begin n := Node.AsCondExpression; arr := TJSONArray.Create; arr.Add('Cond'); pairs := TJSONArray.Create; for var p in n.Pairs do begin var pairArr := TJSONArray.Create; pairArr.AddElement(Visit(p.Condition)); pairArr.AddElement(Visit(p.Branch)); pairs.AddElement(pairArr); end; arr.AddElement(pairs); if Assigned(n.ElseBranch) then arr.AddElement(Visit(n.ElseBranch)) else arr.AddElement(TJSONNull.Create); Result := arr; end; function TJsonAstConverter.VisitLambdaExpression(const Node: IAstNode): TJSONValue; var arr: TJSONArray; n: ILambdaExpressionNode; begin n := Node.AsLambdaExpression; arr := TJSONArray.Create; arr.Add('Fn'); arr.AddElement(Visit(n.Parameters)); arr.AddElement(Visit(n.Body)); Result := arr; end; function TJsonAstConverter.VisitFunctionCall(const Node: IAstNode): TJSONValue; var arr: TJSONArray; n: IFunctionCallNode; begin n := Node.AsFunctionCall; arr := TJSONArray.Create; arr.Add('Call'); arr.AddElement(Visit(n.Callee)); arr.AddElement(Visit(n.Arguments)); Result := arr; end; function TJsonAstConverter.VisitMacroExpansionNode(const Node: IAstNode): TJSONValue; begin Result := Visit(Node.AsMacroExpansion.CallNode); end; function TJsonAstConverter.VisitBlockExpression(const Node: IAstNode): TJSONValue; var arr: TJSONArray; begin arr := TJSONArray.Create; arr.Add('Block'); arr.AddElement(Visit(Node.AsBlockExpression.Expressions)); Result := arr; end; function TJsonAstConverter.VisitVariableDeclaration(const Node: IAstNode): TJSONValue; var arr: TJSONArray; n: IVariableDeclarationNode; begin n := Node.AsVariableDeclaration; arr := TJSONArray.Create; arr.Add('Var'); arr.AddElement(Visit(n.Target)); if Assigned(n.Initializer) then arr.AddElement(Visit(n.Initializer)) else arr.AddElement(TJSONNull.Create); Result := arr; end; function TJsonAstConverter.VisitAssignment(const Node: IAstNode): TJSONValue; var arr: TJSONArray; n: IAssignmentNode; begin n := Node.AsAssignment; arr := TJSONArray.Create; arr.Add('Assign'); arr.AddElement(Visit(n.Target)); arr.AddElement(Visit(n.Value)); Result := arr; end; function TJsonAstConverter.VisitMacroDefinition(const Node: IAstNode): TJSONValue; var arr: TJSONArray; n: IMacroDefinitionNode; begin n := Node.AsMacroDefinition; arr := TJSONArray.Create; arr.Add('Macro'); arr.AddElement(Visit(n.Name)); arr.AddElement(Visit(n.Parameters)); arr.AddElement(Visit(n.Body)); Result := arr; end; function TJsonAstConverter.VisitQuasiquote(const Node: IAstNode): TJSONValue; var arr: TJSONArray; begin arr := TJSONArray.Create; arr.Add('Quote'); arr.AddElement(Visit(Node.AsQuasiquote.Expression)); Result := arr; end; function TJsonAstConverter.VisitUnquote(const Node: IAstNode): TJSONValue; var arr: TJSONArray; begin arr := TJSONArray.Create; arr.Add('Unquote'); arr.AddElement(Visit(Node.AsUnquote.Expression)); Result := arr; end; function TJsonAstConverter.VisitUnquoteSplicing(const Node: IAstNode): TJSONValue; var arr: TJSONArray; begin arr := TJSONArray.Create; arr.Add('Splice'); arr.AddElement(Visit(Node.AsUnquoteSplicing.Expression)); Result := arr; end; function TJsonAstConverter.VisitIndexer(const Node: IAstNode): TJSONValue; var arr: TJSONArray; n: IIndexerNode; begin n := Node.AsIndexer; arr := TJSONArray.Create; arr.Add('Index'); arr.AddElement(Visit(n.Base)); arr.AddElement(Visit(n.Index)); Result := arr; end; function TJsonAstConverter.VisitMemberAccess(const Node: IAstNode): TJSONValue; var arr: TJSONArray; n: IMemberAccessNode; begin n := Node.AsMemberAccess; arr := TJSONArray.Create; arr.Add('Member'); arr.AddElement(Visit(n.Base)); arr.AddElement(Visit(n.Member)); Result := arr; end; function TJsonAstConverter.VisitRecordLiteral(const Node: IAstNode): TJSONValue; var arr: TJSONArray; begin arr := TJSONArray.Create; arr.Add('Record'); arr.AddElement(Visit(Node.AsRecordLiteral.Fields)); Result := arr; end; function TJsonAstConverter.VisitCreateSeries(const Node: IAstNode): TJSONValue; var arr: TJSONArray; begin arr := TJSONArray.Create; arr.Add('Series'); arr.AddElement(Visit(Node.AsCreateSeries.DefinitionNode)); Result := arr; end; function TJsonAstConverter.VisitAddSeriesItem(const Node: IAstNode): TJSONValue; var arr: TJSONArray; n: IAddSeriesItemNode; begin n := Node.AsAddSeriesItem; arr := TJSONArray.Create; arr.Add('Add'); arr.AddElement(Visit(n.Series)); arr.AddElement(Visit(n.Value)); if Assigned(n.Lookback) then arr.AddElement(Visit(n.Lookback)) else arr.AddElement(TJSONNull.Create); Result := arr; end; function TJsonAstConverter.VisitRecurNode(const Node: IAstNode): TJSONValue; var arr: TJSONArray; begin arr := TJSONArray.Create; arr.Add('Recur'); arr.AddElement(Visit(Node.AsRecur.Arguments)); Result := arr; end; function TJsonAstConverter.VisitNop(const Node: IAstNode): TJSONValue; var arr: TJSONArray; begin arr := TJSONArray.Create; arr.Add('Nop'); Result := arr; end; function TJsonAstConverter.VisitPipe(const Node: IAstNode): TJSONValue; var arr: TJSONArray; n: IPipeNode; begin n := Node.AsPipe; arr := TJSONArray.Create; arr.Add('Pipe'); arr.AddElement(Visit(n.Inputs)); arr.AddElement(Visit(n.Transformation)); Result := arr; end; // --- Deserialization --- 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('Keyword', s) then Result := TScalar.FromKeyword(TKeywordRegistry.Intern(s)) else Result := TDataValue.Void; end; function TJsonAstConverter.JsonToTuple(const AArray: TJSONArray): ITupleNode; var elems: TList; i: Integer; begin elems := TList.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; function UnpackTuple(Val: TJSONValue): TArray; begin var n := JsonToNode(Val); if Assigned(n) then Result := n.AsTuple.Elements else Result := []; end; begin if (not Assigned(AJson)) or (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 = 'Nop' then exit(TAst.Nop); if kind = 'Tuple' then exit(JsonToTuple(arr.Items[1] as TJSONArray)); if kind = 'Block' then exit(TAst.Block(UnpackTuple(arr.Items[1]))); if kind = 'Record' then begin var fields := UnpackTuple(arr.Items[1]); var recFields := TList.Create; try for var f in fields do recFields.Add(f.AsRecordField); exit(TAst.RecordLiteral(recFields.ToArray)); finally recFields.Free; end; end; if kind = 'Field' then exit(TAst.RecordField(JsonToNode(arr.Items[1]).AsKeyword, JsonToNode(arr.Items[2]))); if kind = 'Call' then exit(TAst.FunctionCall(JsonToNode(arr.Items[1]), UnpackTuple(arr.Items[2]))); if kind = 'Fn' then exit(TAst.LambdaExpr(TIdentities.Structural, JsonToNode(arr.Items[1]).AsTuple, JsonToNode(arr.Items[2]))); if kind = 'Recur' then exit(TAst.Recur(UnpackTuple(arr.Items[1]))); if kind = 'Macro' then exit( TAst.MacroDef( TIdentities.Structural, JsonToNode(arr.Items[1]).AsIdentifier, JsonToNode(arr.Items[2]).AsTuple, JsonToNode(arr.Items[3]) ) ); if kind = 'Var' then begin var init: IAstNode := nil; if (arr.Count > 2) and (not (arr.Items[2] is TJSONNull)) 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) and (not (arr.Items[3] is TJSONNull)) 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; 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) and (not (arr.Items[2] is TJSONNull)) 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]))); // Splice Fix: Direkte Instanziierung um Factory-Overload E2250 zu umgehen if kind = 'Splice' then exit(TUnquoteSplicingNode.Create(JsonToNode(arr.Items[1]), TIdentities.Structural(nil))); 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 = 'Series' then exit(TAst.CreateSeries(JsonToNode(arr.Items[1]))); if kind = 'Pipe' then exit(TAst.Pipe(JsonToNode(arr.Items[1]).AsTuple, JsonToNode(arr.Items[2]).AsLambdaExpression)); if kind = 'Add' then begin var lb: IAstNode := nil; if (arr.Count > 3) and (not (arr.Items[3] is TJSONNull)) then lb := JsonToNode(arr.Items[3]); exit(TAst.AddSeriesItem(JsonToNode(arr.Items[1]).AsIdentifier, JsonToNode(arr.Items[2]), lb)); end; raise ENotSupportedException.Create('Unknown JSON AST Kind: ' + kind); end; end.