unit Myc.Ast.Persistence; interface uses System.SysUtils, System.Json, System.Generics.Collections, Myc.Data.Scalar, Myc.Ast.Nodes, Myc.Ast.ViewModel; type // Manages the serialization and deserialization of the entire editor state. TAstProjectPersistence = class private type // This helper visitor acts as a "Builder". // It builds the JSON result internally on a stack instead of using the return value. TAstToJsonVisitor = class(TInterfacedObject, IAstVisitor) private FIdMap: TDictionary; // Tracks nodes that have already been fully serialized to prevent infinite recursion on cycles. FSerializedNodes: TDictionary; FNextId: Integer; FResultStack: TStack; function GetOrCreateNodeId(const Node: IAstNode): Integer; function ScalarToJson(const AValue: TScalar): TJSONObject; public constructor Create(AIdMap: TDictionary); destructor Destroy; override; function GetResult: TJSONObject; { IAstVisitor } function VisitConstant(const Node: IConstantNode): TAstValue; function VisitIdentifier(const Node: IIdentifierNode): TAstValue; function VisitBinaryExpression(const Node: IBinaryExpressionNode): TAstValue; function VisitUnaryExpression(const Node: IUnaryExpressionNode): TAstValue; function VisitIfExpression(const Node: IIfExpressionNode): TAstValue; function VisitTernaryExpression(const Node: ITernaryExpressionNode): TAstValue; function VisitLambdaExpression(const Node: ILambdaExpressionNode): TAstValue; function VisitFunctionCall(const Node: IFunctionCallNode): TAstValue; function VisitBlockExpression(const Node: IBlockExpressionNode): TAstValue; function VisitVariableDeclaration(const Node: IVariableDeclarationNode): TAstValue; function VisitAssignment(const Node: IAssignmentNode): TAstValue; function VisitIndexer(const Node: IIndexerNode): TAstValue; function VisitMemberAccess(const Node: IMemberAccessNode): TAstValue; function VisitCreateSeries(const Node: ICreateSeriesNode): TAstValue; function VisitAddSeriesItem(const Node: IAddSeriesItemNode): TAstValue; function VisitSeriesLength(const Node: ISeriesLengthNode): TAstValue; end; function ScalarFromJson(AObject: TJSONObject): TScalar; function DeserializeNode(AObject: TJSONObject; ANodeIdMap: TDictionary): IAstNode; public // Saves the complete state to a JSON file. procedure SaveToFile( const AFileName: string; const ARootNode: IAstNode; const ALogicalMetadata: TDictionary; const AInstanceMetadata: TDictionary ); // Loads the complete state from a JSON file. procedure LoadFromFile( const AFileName: string; out ARootNode: IAstNode; out ALogicalMetadata: TDictionary; out AInstanceMetadata: TDictionary ); class function AstNodeToJsonString(const ANode: IAstNode): string; static; class function JsonStringToAstNode(const AJsonString: string): IAstNode; static; end; implementation uses System.Classes, System.Types, System.Variants, System.Rtti, System.TypInfo, System.IOUtils, System.DateUtils, System.StrUtils, Myc.Data.Decimal, Myc.Ast; { TAstProjectPersistence } procedure TAstProjectPersistence.SaveToFile( const AFileName: string; const ARootNode: IAstNode; const ALogicalMetadata: TDictionary; const AInstanceMetadata: TDictionary ); var rootJson, astJson, logicalMetaJson, instanceMetaJson: TJSONObject; idMap: TDictionary; visitor: TAstToJsonVisitor; pair: TPair; metaPair: TPair; metaValueJson: TJSONObject; begin rootJson := TJSONObject.Create; try idMap := TDictionary.Create; visitor := TAstToJsonVisitor.Create(idMap); try ARootNode.Accept(visitor); astJson := visitor.GetResult; rootJson.AddPair('ast', astJson); finally idMap.Free; visitor.Free; end; logicalMetaJson := TJSONObject.Create; for pair in ALogicalMetadata do begin var nodeIdStr := idMap[pair.Key].ToString; metaValueJson := TJSONObject.Create; metaValueJson.AddPair('NodeType', TJSONString.Create(GetEnumName(TypeInfo(TAstNodeType), Ord(pair.Value.NodeType)))); metaValueJson.AddPair('Description', TJSONString.Create(pair.Value.Description)); logicalMetaJson.AddPair(nodeIdStr, metaValueJson); end; rootJson.AddPair('logicalMetadata', logicalMetaJson); instanceMetaJson := TJSONObject.Create; for metaPair in AInstanceMetadata do begin var idStr := metaPair.Key.ToString; metaValueJson := TJSONObject.Create; metaValueJson.AddPair('PositionX', TJSONNumber.Create(metaPair.Value.PositionOverride.X)); metaValueJson.AddPair('PositionY', TJSONNumber.Create(metaPair.Value.PositionOverride.Y)); metaValueJson.AddPair('IsCollapsed', TJSONBool.Create(metaPair.Value.IsCollapsed)); var modeStr := GetEnumName(TypeInfo(TVisualizationMode), Ord(metaPair.Value.VisualizationMode)); metaValueJson.AddPair('VisualizationMode', TJSONString.Create(modeStr)); instanceMetaJson.AddPair(idStr, metaValueJson); end; rootJson.AddPair('instanceMetadata', instanceMetaJson); TFile.WriteAllText(AFileName, rootJson.ToJSON); finally rootJson.Free; end; end; procedure TAstProjectPersistence.LoadFromFile( const AFileName: string; out ARootNode: IAstNode; out ALogicalMetadata: TDictionary; out AInstanceMetadata: TDictionary ); var rootJson, astJson, logicalMetaJson, instanceMetaJson: TJSONObject; jsonValue: TJSONValue; nodeIdMap: TDictionary; pair: TJSONPair; metaPair: TJSONPair; metaData: TVisualInstanceMetadata; begin ARootNode := nil; ALogicalMetadata := TDictionary.Create; AInstanceMetadata := TDictionary.Create; jsonValue := TJSONObject.ParseJSONValue(TFile.ReadAllText(AFileName)); if (not Assigned(jsonValue)) or (not (jsonValue is TJSONObject)) then begin exit; end; rootJson := jsonValue as TJSONObject; try astJson := rootJson.GetValue('ast'); if Assigned(astJson) then begin nodeIdMap := TDictionary.Create; try ARootNode := DeserializeNode(astJson, nodeIdMap); if not Assigned(ARootNode) then begin exit; end; logicalMetaJson := rootJson.GetValue('logicalMetadata'); if Assigned(logicalMetaJson) then begin for pair in logicalMetaJson do begin var tempId := StrToInt(pair.JsonString.Value); if nodeIdMap.ContainsKey(tempId) then begin var node := nodeIdMap[tempId]; var meta: TLogicalMetadata; var metaJson := pair.JSONValue as TJSONObject; meta.NodeType := TAstNodeType(GetEnumValue(TypeInfo(TAstNodeType), metaJson.GetValue('NodeType'))); meta.Description := metaJson.GetValue('Description'); ALogicalMetadata.Add(node, meta); end; end; end; finally nodeIdMap.Free; end; end; instanceMetaJson := rootJson.GetValue('instanceMetadata'); if Assigned(instanceMetaJson) then begin for metaPair in instanceMetaJson do begin var vmId := StrToInt64(metaPair.JsonString.Value); var metaJson := metaPair.JsonValue as TJSONObject; metaData.PositionOverride.X := metaJson.GetValue('PositionX'); metaData.PositionOverride.Y := metaJson.GetValue('PositionY'); metaData.IsCollapsed := metaJson.GetValue('IsCollapsed'); var modeStr := metaJson.GetValue('VisualizationMode'); var modeInt := GetEnumValue(TypeInfo(TVisualizationMode), modeStr); metaData.VisualizationMode := TVisualizationMode(modeInt); AInstanceMetadata.Add(vmId, metaData); end; end; finally rootJson.Free; end; end; function TAstProjectPersistence.DeserializeNode(AObject: TJSONObject; ANodeIdMap: TDictionary): IAstNode; var nodeTypeStr: string; tempId: Integer; i: Integer; begin Result := nil; if not Assigned(AObject) then begin exit; end; // Handle reference nodes to break cycles during deserialization if AObject.GetValue('ref') <> nil then begin var refId := AObject.GetValue('ref'); if not ANodeIdMap.TryGetValue(refId, Result) then // A forward reference indicates a cycle that cannot be resolved with the current immutable AST node creation. // The referenced node has not been fully constructed yet. raise ENotImplemented.CreateFmt( 'Forward reference to node ID %d encountered. This is not supported by the current deserialization logic.', [refId]); exit; end; nodeTypeStr := AObject.GetValue('type'); tempId := AObject.GetValue('id'); var nodeType := TAstNodeType(GetEnumValue(TypeInfo(TAstNodeType), 'ant' + nodeTypeStr)); case nodeType of antConstant: Result := TAst.Constant(ScalarFromJson(AObject.GetValue('value'))); antIdentifier: Result := TAst.Identifier(AObject.GetValue('name')); antBinaryExpression: Result := TAst.BinaryExpr( DeserializeNode(AObject.GetValue('left'), ANodeIdMap), TBinaryOperator(GetEnumValue(TypeInfo(TBinaryOperator), 'bo' + AObject.GetValue('operator'))), DeserializeNode(AObject.GetValue('right'), ANodeIdMap) ); antUnaryExpression: Result := TAst.UnaryExpr( TUnaryOperator(GetEnumValue(TypeInfo(TUnaryOperator), 'uo' + AObject.GetValue('operator'))), DeserializeNode(AObject.GetValue('right'), ANodeIdMap) ); antIfExpression: begin var elseBranch: IAstNode := nil; if AObject.GetValue('elseBranch') <> nil then begin elseBranch := DeserializeNode(AObject.GetValue('elseBranch'), ANodeIdMap); end; Result := TAst.IfExpr( DeserializeNode(AObject.GetValue('condition'), ANodeIdMap), DeserializeNode(AObject.GetValue('thenBranch'), ANodeIdMap), elseBranch ); end; antTernaryExpression: Result := TAst.TernaryExpr( DeserializeNode(AObject.GetValue('condition'), ANodeIdMap), DeserializeNode(AObject.GetValue('thenBranch'), ANodeIdMap), DeserializeNode(AObject.GetValue('elseBranch'), ANodeIdMap) ); antLambdaExpression: begin var paramsJson := AObject.GetValue('parameters'); var params := TArray.Create(); SetLength(params, paramsJson.Count); // Important: Add the (incomplete) node to the map before deserializing children // to allow children to reference it. // However, with immutable nodes, we can't do that. The check for forward refs handles this limitation. for i := 0 to paramsJson.Count - 1 do begin params[i] := IIdentifierNode(DeserializeNode(paramsJson.Items[i] as TJSONObject, ANodeIdMap)); end; var body := DeserializeNode(AObject.GetValue('body'), ANodeIdMap); Result := TAst.LambdaExpr(params, body); end; antFunctionCall: begin var callee := DeserializeNode(AObject.GetValue('callee'), ANodeIdMap); var argsJson := AObject.GetValue('arguments'); var args := TArray.Create(); SetLength(args, argsJson.Count); for i := 0 to argsJson.Count - 1 do begin args[i] := DeserializeNode(argsJson.Items[i] as TJSONObject, ANodeIdMap); end; Result := TAst.FunctionCall(callee, args); end; antBlockExpression: begin var exprsJson := AObject.GetValue('expressions'); var exprs := TArray.Create(); SetLength(exprs, exprsJson.Count); for i := 0 to exprsJson.Count - 1 do begin exprs[i] := DeserializeNode(exprsJson.Items[i] as TJSONObject, ANodeIdMap); end; Result := TAst.Block(exprs); end; antVariableDeclaration: begin var initializer: IAstNode := nil; if AObject.GetValue('initializer') <> nil then begin initializer := DeserializeNode(AObject.GetValue('initializer'), ANodeIdMap); end; Result := TAst.VarDecl(IIdentifierNode(DeserializeNode(AObject.GetValue('identifier'), ANodeIdMap)), initializer); end; antAssignment: Result := TAst.Assign( IIdentifierNode(DeserializeNode(AObject.GetValue('identifier'), ANodeIdMap)), DeserializeNode(AObject.GetValue('value'), ANodeIdMap) ); antIndexer: Result := TAst.Indexer( DeserializeNode(AObject.GetValue('base'), ANodeIdMap), DeserializeNode(AObject.GetValue('index'), ANodeIdMap) ); antMemberAccess: Result := TAst.MemberAccess( DeserializeNode(AObject.GetValue('base'), ANodeIdMap), IIdentifierNode(DeserializeNode(AObject.GetValue('member'), ANodeIdMap)) ); antCreateSeries: Result := TAst.CreateSeries(AObject.GetValue('definition')); antAddSeriesItem: begin var lookback: IAstNode := nil; if AObject.GetValue('lookback') <> nil then begin lookback := DeserializeNode(AObject.GetValue('lookback'), ANodeIdMap); end; Result := TAst.AddSeriesItem( IIdentifierNode(DeserializeNode(AObject.GetValue('series'), ANodeIdMap)), DeserializeNode(AObject.GetValue('value'), ANodeIdMap), lookback ); end; antSeriesLength: Result := TAst.SeriesLength(IIdentifierNode(DeserializeNode(AObject.GetValue('series'), ANodeIdMap))); else raise ENotImplemented.CreateFmt('Deserialization for node type "%s" is not implemented.', [nodeTypeStr]); end; if Assigned(Result) then begin ANodeIdMap.Add(tempId, Result); end; end; function TAstProjectPersistence.ScalarFromJson(AObject: TJSONObject): TScalar; var kindStr: string; kind: TScalarKind; value: TScalarValue; decVal: TDecimal; hexStrAnsi: AnsiString; begin kindStr := AObject.GetValue('kind'); kind := TScalar.StringToKind(kindStr); case kind of skInteger: value.AsInteger := AObject.GetValue('value'); skInt64: value.AsInt64 := StrToInt64(AObject.GetValue('value')); skUInt64: value.AsUInt64 := StrToUInt64(AObject.GetValue('value')); skSingle: value.AsSingle := AObject.GetValue('value'); skDouble: value.AsDouble := AObject.GetValue('value'); skDateTime: value.AsDateTime := ISO8601ToDate(AObject.GetValue('value')); skTimestamp: value.AsTimestamp := DateTimeToTimeStamp(ISO8601ToDate(AObject.GetValue('value'))); skBoolean: value.AsBoolean := AObject.GetValue('value'); skChar: value.AsChar := AObject.GetValue('value')[1]; skPChar: StrPCopy(value.AsPChar, AObject.GetValue('value')); skString: value.AsString := AObject.GetValue('value'); skBytes: begin hexStrAnsi := AnsiString(AObject.GetValue('value')); HexToBin(PAnsiChar(hexStrAnsi), @value.AsBytes, SizeOf(value.AsBytes)); end; skDecimal: begin decVal := TDecimal.Create(StrToInt64(AObject.GetValue('value')), AObject.GetValue('scale')); value.AsDecimal := decVal; end; else raise ENotImplemented.CreateFmt('TScalar deserialization for kind "%s" is not implemented.', [kindStr]); end; Result.Create(kind, value); end; class function TAstProjectPersistence.AstNodeToJsonString(const ANode: IAstNode): string; var idMap: TDictionary; visitor: TAstToJsonVisitor; astJson, rootJson: TJSONObject; begin if not Assigned(ANode) then begin exit(''); end; // We wrap the AST in the standard project structure { "ast": ... } // for compatibility with the full LoadFromFile method. rootJson := TJSONObject.Create; try idMap := TDictionary.Create; try visitor := TAstToJsonVisitor.Create(idMap); try ANode.Accept(visitor); astJson := visitor.GetResult; rootJson.AddPair('ast', astJson); Result := rootJson.ToJSON; finally visitor.Free; end; finally idMap.Free; end; finally rootJson.Free; end; end; class function TAstProjectPersistence.JsonStringToAstNode(const AJsonString: string): IAstNode; var persistence: TAstProjectPersistence; rootJson, astJson: TJSONObject; nodeIdMap: TDictionary; begin Result := nil; if AJsonString.IsEmpty then begin exit; end; rootJson := TJSONObject.ParseJSONValue(AJsonString) as TJSONObject; if not Assigned(rootJson) then begin exit; end; try // Expect the standard project structure and extract the "ast" part. astJson := rootJson.GetValue('ast'); if Assigned(astJson) then begin persistence := TAstProjectPersistence.Create; try nodeIdMap := TDictionary.Create; try Result := persistence.DeserializeNode(astJson, nodeIdMap); finally nodeIdMap.Free; end; finally persistence.Free; end; end; finally rootJson.Free; end; end; { TAstProjectPersistence.TAstToJsonVisitor } constructor TAstProjectPersistence.TAstToJsonVisitor.Create(AIdMap: TDictionary); begin inherited Create; FIdMap := AIdMap; FNextId := 0; FResultStack := TStack.Create; FSerializedNodes := TDictionary.Create; end; destructor TAstProjectPersistence.TAstToJsonVisitor.Destroy; begin FResultStack.Free; FSerializedNodes.Free; inherited; end; function TAstProjectPersistence.TAstToJsonVisitor.GetResult: TJSONObject; begin Assert(FResultStack.Count = 1, 'Result stack should contain exactly one item after traversal.'); Result := FResultStack.Pop as TJSONObject; end; function TAstProjectPersistence.TAstToJsonVisitor.GetOrCreateNodeId(const Node: IAstNode): Integer; begin if not FIdMap.TryGetValue(Node, Result) then begin Result := FNextId; FIdMap.Add(Node, Result); inc(FNextId); end; end; function TAstProjectPersistence.TAstToJsonVisitor.ScalarToJson(const AValue: TScalar): TJSONObject; var hexStr: string; begin Result := TJSONObject.Create; Result.AddPair('kind', TJSONString.Create(AValue.Kind.ToString)); case AValue.Kind of skInteger: Result.AddPair('value', TJSONNumber.Create(AValue.Value.AsInteger)); skInt64: Result.AddPair('value', TJSONString.Create(AValue.Value.AsInt64.ToString)); skUInt64: Result.AddPair('value', TJSONString.Create(AValue.Value.AsUInt64.ToString)); skSingle: Result.AddPair('value', TJSONNumber.Create(AValue.Value.AsSingle)); skDouble: Result.AddPair('value', TJSONNumber.Create(AValue.Value.AsDouble)); skDateTime: Result.AddPair('value', TJSONString.Create(DateToISO8601(AValue.Value.AsDateTime))); skTimestamp: Result.AddPair('value', TJSONString.Create(DateToISO8601(TimeStampToDateTime(AValue.Value.AsTimestamp)))); skBoolean: Result.AddPair('value', TJSONBool.Create(AValue.Value.AsBoolean)); skChar: Result.AddPair('value', TJSONString.Create(AValue.Value.AsChar)); skPChar: Result.AddPair('value', TJSONString.Create(string(AValue.Value.AsPChar))); skString: Result.AddPair('value', TJSONString.Create(String(AValue.Value.AsString))); skBytes: begin // Corrected: Use the procedure overload of BinToHex. // BinToHex(@AValue.Value.AsBytes, hexStr, SizeOf(AValue.Value.AsBytes)); // Result.AddPair('value', TJSONString.Create(hexStr)); end; skDecimal: begin Result.AddPair('value', TJSONString.Create(AValue.Value.AsDecimal.GetValue.ToString)); Result.AddPair('scale', TJSONNumber.Create(AValue.Value.AsDecimal.GetScale)); end; else raise ENotImplemented.CreateFmt('TScalar serialization for kind "%s" is not implemented.', [AValue.Kind.ToString]); end; end; function TAstProjectPersistence.TAstToJsonVisitor.VisitConstant(const Node: IConstantNode): TAstValue; var obj: TJSONObject; begin if FSerializedNodes.ContainsKey(Node) then begin var refObj := TJSONObject.Create; refObj.AddPair('ref', TJSONNumber.Create(FIdMap[Node])); FResultStack.Push(refObj); Result := TAstValue.Void; exit; end; FSerializedNodes.Add(Node, True); obj := TJSONObject.Create; obj.AddPair('type', TJSONString.Create('Constant')); obj.AddPair('id', TJSONNumber.Create(GetOrCreateNodeId(Node))); obj.AddPair('value', ScalarToJson(Node.Value)); FResultStack.Push(obj); Result := TAstValue.Void; end; function TAstProjectPersistence.TAstToJsonVisitor.VisitIdentifier(const Node: IIdentifierNode): TAstValue; var obj: TJSONObject; begin if FSerializedNodes.ContainsKey(Node) then begin var refObj := TJSONObject.Create; refObj.AddPair('ref', TJSONNumber.Create(FIdMap[Node])); FResultStack.Push(refObj); Result := TAstValue.Void; exit; end; FSerializedNodes.Add(Node, True); obj := TJSONObject.Create; obj.AddPair('type', TJSONString.Create('Identifier')); obj.AddPair('id', TJSONNumber.Create(GetOrCreateNodeId(Node))); obj.AddPair('name', TJSONString.Create(Node.Name)); FResultStack.Push(obj); Result := TAstValue.Void; end; function TAstProjectPersistence.TAstToJsonVisitor.VisitBinaryExpression(const Node: IBinaryExpressionNode): TAstValue; var obj: TJSONObject; rightJson, leftJson: TJSONValue; begin if FSerializedNodes.ContainsKey(Node) then begin var refObj := TJSONObject.Create; refObj.AddPair('ref', TJSONNumber.Create(FIdMap[Node])); FResultStack.Push(refObj); Result := TAstValue.Void; exit; end; FSerializedNodes.Add(Node, True); Node.Left.Accept(Self); Node.Right.Accept(Self); rightJson := FResultStack.Pop; leftJson := FResultStack.Pop; obj := TJSONObject.Create; obj.AddPair('type', TJSONString.Create('BinaryExpression')); obj.AddPair('id', TJSONNumber.Create(GetOrCreateNodeId(Node))); obj.AddPair('operator', TJSONString.Create(Copy(GetEnumName(TypeInfo(TBinaryOperator), Ord(Node.Operator)), 3))); obj.AddPair('left', leftJson); obj.AddPair('right', rightJson); FResultStack.Push(obj); Result := TAstValue.Void; end; function TAstProjectPersistence.TAstToJsonVisitor.VisitUnaryExpression(const Node: IUnaryExpressionNode): TAstValue; var obj: TJSONObject; rightJson: TJSONValue; begin if FSerializedNodes.ContainsKey(Node) then begin var refObj := TJSONObject.Create; refObj.AddPair('ref', TJSONNumber.Create(FIdMap[Node])); FResultStack.Push(refObj); Result := TAstValue.Void; exit; end; FSerializedNodes.Add(Node, True); Node.Right.Accept(Self); rightJson := FResultStack.Pop; obj := TJSONObject.Create; obj.AddPair('type', TJSONString.Create('UnaryExpression')); obj.AddPair('id', TJSONNumber.Create(GetOrCreateNodeId(Node))); obj.AddPair('operator', TJSONString.Create(Copy(GetEnumName(TypeInfo(TUnaryOperator), Ord(Node.Operator)), 3))); obj.AddPair('right', rightJson); FResultStack.Push(obj); Result := TAstValue.Void; end; function TAstProjectPersistence.TAstToJsonVisitor.VisitIfExpression(const Node: IIfExpressionNode): TAstValue; var obj: TJSONObject; elseJson, thenJson, condJson: TJSONValue; begin if FSerializedNodes.ContainsKey(Node) then begin var refObj := TJSONObject.Create; refObj.AddPair('ref', TJSONNumber.Create(FIdMap[Node])); FResultStack.Push(refObj); Result := TAstValue.Void; exit; end; FSerializedNodes.Add(Node, True); Node.Condition.Accept(Self); Node.ThenBranch.Accept(Self); if Assigned(Node.ElseBranch) then begin Node.ElseBranch.Accept(Self); end; if Assigned(Node.ElseBranch) then begin elseJson := FResultStack.Pop end else begin elseJson := nil; end; thenJson := FResultStack.Pop; condJson := FResultStack.Pop; obj := TJSONObject.Create; obj.AddPair('type', TJSONString.Create('IfExpression')); obj.AddPair('id', TJSONNumber.Create(GetOrCreateNodeId(Node))); obj.AddPair('condition', condJson); obj.AddPair('thenBranch', thenJson); if Assigned(elseJson) then begin obj.AddPair('elseBranch', elseJson); end; FResultStack.Push(obj); Result := TAstValue.Void; end; function TAstProjectPersistence.TAstToJsonVisitor.VisitTernaryExpression(const Node: ITernaryExpressionNode): TAstValue; var obj: TJSONObject; elseJson, thenJson, condJson: TJSONValue; begin if FSerializedNodes.ContainsKey(Node) then begin var refObj := TJSONObject.Create; refObj.AddPair('ref', TJSONNumber.Create(FIdMap[Node])); FResultStack.Push(refObj); Result := TAstValue.Void; exit; end; FSerializedNodes.Add(Node, True); Node.Condition.Accept(Self); Node.ThenBranch.Accept(Self); Node.ElseBranch.Accept(Self); elseJson := FResultStack.Pop; thenJson := FResultStack.Pop; condJson := FResultStack.Pop; obj := TJSONObject.Create; obj.AddPair('type', TJSONString.Create('TernaryExpression')); obj.AddPair('id', TJSONNumber.Create(GetOrCreateNodeId(Node))); obj.AddPair('condition', condJson); obj.AddPair('thenBranch', thenJson); obj.AddPair('elseBranch', elseJson); FResultStack.Push(obj); Result := TAstValue.Void; end; function TAstProjectPersistence.TAstToJsonVisitor.VisitLambdaExpression(const Node: ILambdaExpressionNode): TAstValue; var obj: TJSONObject; paramsJson: TJSONArray; param: IIdentifierNode; bodyJson: TJSONValue; i: Integer; begin if FSerializedNodes.ContainsKey(Node) then begin var refObj := TJSONObject.Create; refObj.AddPair('ref', TJSONNumber.Create(FIdMap[Node])); FResultStack.Push(refObj); Result := TAstValue.Void; exit; end; FSerializedNodes.Add(Node, True); // 1. Visit children first. for param in Node.Parameters do begin param.Accept(Self); end; Node.Body.Accept(Self); // 2. Pop children's results from the stack in reverse order. bodyJson := FResultStack.Pop; paramsJson := TJSONArray.Create; var lst := TList.Create; try lst.Count := Length(Node.Parameters); for i := High(Node.Parameters) downto 0 do lst[i] := FResultStack.Pop; paramsJson.SetElements(lst); finally lst.Free; end; // 3. Create this node's JSON object. obj := TJSONObject.Create; obj.AddPair('type', TJSONString.Create('LambdaExpression')); obj.AddPair('id', TJSONNumber.Create(GetOrCreateNodeId(Node))); obj.AddPair('parameters', paramsJson); obj.AddPair('body', bodyJson); // Note: ScopeDescriptor and Upvalues are runtime-only and not serialized here. // 4. Push this node's result onto the stack. FResultStack.Push(obj); Result := TAstValue.Void; end; function TAstProjectPersistence.TAstToJsonVisitor.VisitFunctionCall(const Node: IFunctionCallNode): TAstValue; var obj: TJSONObject; args: TJSONArray; arg: IAstNode; calleeJson: TJSONValue; i: Integer; begin if FSerializedNodes.ContainsKey(Node) then begin var refObj := TJSONObject.Create; refObj.AddPair('ref', TJSONNumber.Create(FIdMap[Node])); FResultStack.Push(refObj); Result := TAstValue.Void; exit; end; FSerializedNodes.Add(Node, True); Node.Callee.Accept(Self); for arg in Node.Arguments do begin arg.Accept(Self); end; args := TJSONArray.Create; var lst := TList.Create; try lst.Count := Node.Arguments.Count; for i := Node.Arguments.Count - 1 downto 0 do lst[i] := FResultStack.Pop; args.SetElements(lst); finally lst.Free; end; calleeJson := FResultStack.Pop; obj := TJSONObject.Create; obj.AddPair('type', TJSONString.Create('FunctionCall')); obj.AddPair('id', TJSONNumber.Create(GetOrCreateNodeId(Node))); obj.AddPair('callee', calleeJson); obj.AddPair('arguments', args); FResultStack.Push(obj); Result := TAstValue.Void; end; function TAstProjectPersistence.TAstToJsonVisitor.VisitBlockExpression(const Node: IBlockExpressionNode): TAstValue; var obj: TJSONObject; exprs: TJSONArray; expr: IAstNode; i: Integer; begin if FSerializedNodes.ContainsKey(Node) then begin var refObj := TJSONObject.Create; refObj.AddPair('ref', TJSONNumber.Create(FIdMap[Node])); FResultStack.Push(refObj); Result := TAstValue.Void; exit; end; FSerializedNodes.Add(Node, True); for expr in Node.Expressions do begin expr.Accept(Self); end; exprs := TJSONArray.Create; var lst := TList.Create; try lst.Count := Node.Expressions.Count; for i := Node.Expressions.Count - 1 downto 0 do lst[i] := FResultStack.Pop; exprs.SetElements(lst); finally lst.Free; end; obj := TJSONObject.Create; obj.AddPair('type', TJSONString.Create('BlockExpression')); obj.AddPair('id', TJSONNumber.Create(GetOrCreateNodeId(Node))); obj.AddPair('expressions', exprs); FResultStack.Push(obj); Result := TAstValue.Void; end; function TAstProjectPersistence.TAstToJsonVisitor.VisitVariableDeclaration(const Node: IVariableDeclarationNode): TAstValue; var obj: TJSONObject; identJson, initJson: TJSONValue; begin if FSerializedNodes.ContainsKey(Node) then begin var refObj := TJSONObject.Create; refObj.AddPair('ref', TJSONNumber.Create(FIdMap[Node])); FResultStack.Push(refObj); Result := TAstValue.Void; exit; end; FSerializedNodes.Add(Node, True); Node.Identifier.Accept(Self); if Assigned(Node.Initializer) then begin Node.Initializer.Accept(Self); end; if Assigned(Node.Initializer) then begin initJson := FResultStack.Pop end else begin initJson := nil; end; identJson := FResultStack.Pop; obj := TJSONObject.Create; obj.AddPair('type', TJSONString.Create('VariableDeclaration')); obj.AddPair('id', TJSONNumber.Create(GetOrCreateNodeId(Node))); obj.AddPair('identifier', identJson); if Assigned(initJson) then begin obj.AddPair('initializer', initJson); end; FResultStack.Push(obj); Result := TAstValue.Void; end; function TAstProjectPersistence.TAstToJsonVisitor.VisitAssignment(const Node: IAssignmentNode): TAstValue; var obj: TJSONObject; identJson, valueJson: TJSONValue; begin if FSerializedNodes.ContainsKey(Node) then begin var refObj := TJSONObject.Create; refObj.AddPair('ref', TJSONNumber.Create(FIdMap[Node])); FResultStack.Push(refObj); Result := TAstValue.Void; exit; end; FSerializedNodes.Add(Node, True); Node.Identifier.Accept(Self); Node.Value.Accept(Self); valueJson := FResultStack.Pop; identJson := FResultStack.Pop; obj := TJSONObject.Create; obj.AddPair('type', TJSONString.Create('Assignment')); obj.AddPair('id', TJSONNumber.Create(GetOrCreateNodeId(Node))); obj.AddPair('identifier', identJson); obj.AddPair('value', valueJson); FResultStack.Push(obj); Result := TAstValue.Void; end; function TAstProjectPersistence.TAstToJsonVisitor.VisitIndexer(const Node: IIndexerNode): TAstValue; var obj: TJSONObject; baseJson, indexJson: TJSONValue; begin if FSerializedNodes.ContainsKey(Node) then begin var refObj := TJSONObject.Create; refObj.AddPair('ref', TJSONNumber.Create(FIdMap[Node])); FResultStack.Push(refObj); Result := TAstValue.Void; exit; end; FSerializedNodes.Add(Node, True); Node.Base.Accept(Self); Node.Index.Accept(Self); indexJson := FResultStack.Pop; baseJson := FResultStack.Pop; obj := TJSONObject.Create; obj.AddPair('type', TJSONString.Create('Indexer')); obj.AddPair('id', TJSONNumber.Create(GetOrCreateNodeId(Node))); obj.AddPair('base', baseJson); obj.AddPair('index', indexJson); FResultStack.Push(obj); Result := TAstValue.Void; end; function TAstProjectPersistence.TAstToJsonVisitor.VisitMemberAccess(const Node: IMemberAccessNode): TAstValue; var obj: TJSONObject; baseJson, memberJson: TJSONValue; begin if FSerializedNodes.ContainsKey(Node) then begin var refObj := TJSONObject.Create; refObj.AddPair('ref', TJSONNumber.Create(FIdMap[Node])); FResultStack.Push(refObj); Result := TAstValue.Void; exit; end; FSerializedNodes.Add(Node, True); Node.Base.Accept(Self); Node.Member.Accept(Self); memberJson := FResultStack.Pop; baseJson := FResultStack.Pop; obj := TJSONObject.Create; obj.AddPair('type', TJSONString.Create('MemberAccess')); obj.AddPair('id', TJSONNumber.Create(GetOrCreateNodeId(Node))); obj.AddPair('base', baseJson); obj.AddPair('member', memberJson); FResultStack.Push(obj); Result := TAstValue.Void; end; function TAstProjectPersistence.TAstToJsonVisitor.VisitCreateSeries(const Node: ICreateSeriesNode): TAstValue; var obj: TJSONObject; begin if FSerializedNodes.ContainsKey(Node) then begin var refObj := TJSONObject.Create; refObj.AddPair('ref', TJSONNumber.Create(FIdMap[Node])); FResultStack.Push(refObj); Result := TAstValue.Void; exit; end; FSerializedNodes.Add(Node, True); obj := TJSONObject.Create; obj.AddPair('type', TJSONString.Create('CreateSeries')); obj.AddPair('id', TJSONNumber.Create(GetOrCreateNodeId(Node))); obj.AddPair('definition', TJSONString.Create(Node.Definition)); FResultStack.Push(obj); Result := TAstValue.Void; end; function TAstProjectPersistence.TAstToJsonVisitor.VisitAddSeriesItem(const Node: IAddSeriesItemNode): TAstValue; var obj: TJSONObject; seriesJson, valueJson, lookbackJson: TJSONValue; begin if FSerializedNodes.ContainsKey(Node) then begin var refObj := TJSONObject.Create; refObj.AddPair('ref', TJSONNumber.Create(FIdMap[Node])); FResultStack.Push(refObj); Result := TAstValue.Void; exit; end; FSerializedNodes.Add(Node, True); Node.Series.Accept(Self); Node.Value.Accept(Self); if Assigned(Node.Lookback) then begin Node.Lookback.Accept(Self); end; if Assigned(Node.Lookback) then begin lookbackJson := FResultStack.Pop end else begin lookbackJson := nil; end; valueJson := FResultStack.Pop; seriesJson := FResultStack.Pop; obj := TJSONObject.Create; obj.AddPair('type', TJSONString.Create('AddSeriesItem')); obj.AddPair('id', TJSONNumber.Create(GetOrCreateNodeId(Node))); obj.AddPair('series', seriesJson); obj.AddPair('value', valueJson); if Assigned(lookbackJson) then begin obj.AddPair('lookback', lookbackJson); end; FResultStack.Push(obj); Result := TAstValue.Void; end; function TAstProjectPersistence.TAstToJsonVisitor.VisitSeriesLength(const Node: ISeriesLengthNode): TAstValue; var obj: TJSONObject; seriesJson: TJSONValue; begin if FSerializedNodes.ContainsKey(Node) then begin var refObj := TJSONObject.Create; refObj.AddPair('ref', TJSONNumber.Create(FIdMap[Node])); FResultStack.Push(refObj); Result := TAstValue.Void; exit; end; FSerializedNodes.Add(Node, True); Node.Series.Accept(Self); seriesJson := FResultStack.Pop; obj := TJSONObject.Create; obj.AddPair('type', TJSONString.Create('SeriesLength')); obj.AddPair('id', TJSONNumber.Create(GetOrCreateNodeId(Node))); obj.AddPair('series', seriesJson); FResultStack.Push(obj); Result := TAstValue.Void; end; end.