1111 lines
40 KiB
ObjectPascal
1111 lines
40 KiB
ObjectPascal
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<IAstNode, Integer>;
|
|
// Tracks nodes that have already been fully serialized to prevent infinite recursion on cycles.
|
|
FSerializedNodes: TDictionary<IAstNode, Boolean>;
|
|
FNextId: Integer;
|
|
FResultStack: TStack<TJSONObject>;
|
|
function GetOrCreateNodeId(const Node: IAstNode): Integer;
|
|
function ScalarToJson(const AValue: TScalar): TJSONObject;
|
|
public
|
|
constructor Create(AIdMap: TDictionary<IAstNode, Integer>);
|
|
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<Integer, IAstNode>): IAstNode;
|
|
|
|
public
|
|
// Saves the complete state to a JSON file.
|
|
procedure SaveToFile(
|
|
const AFileName: string;
|
|
const ARootNode: IAstNode;
|
|
const ALogicalMetadata: TDictionary<IAstNode, TLogicalMetadata>;
|
|
const AInstanceMetadata: TDictionary<TViewModelID, TVisualInstanceMetadata>
|
|
);
|
|
|
|
// Loads the complete state from a JSON file.
|
|
procedure LoadFromFile(
|
|
const AFileName: string;
|
|
out ARootNode: IAstNode;
|
|
out ALogicalMetadata: TDictionary<IAstNode, TLogicalMetadata>;
|
|
out AInstanceMetadata: TDictionary<TViewModelID, TVisualInstanceMetadata>
|
|
);
|
|
|
|
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<IAstNode, TLogicalMetadata>;
|
|
const AInstanceMetadata: TDictionary<TViewModelID, TVisualInstanceMetadata>
|
|
);
|
|
var
|
|
rootJson, astJson, logicalMetaJson, instanceMetaJson: TJSONObject;
|
|
idMap: TDictionary<IAstNode, Integer>;
|
|
visitor: TAstToJsonVisitor;
|
|
pair: TPair<IAstNode, TLogicalMetadata>;
|
|
metaPair: TPair<TViewModelID, TVisualInstanceMetadata>;
|
|
metaValueJson: TJSONObject;
|
|
begin
|
|
rootJson := TJSONObject.Create;
|
|
try
|
|
idMap := TDictionary<IAstNode, Integer>.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<IAstNode, TLogicalMetadata>;
|
|
out AInstanceMetadata: TDictionary<TViewModelID, TVisualInstanceMetadata>
|
|
);
|
|
var
|
|
rootJson, astJson, logicalMetaJson, instanceMetaJson: TJSONObject;
|
|
jsonValue: TJSONValue;
|
|
nodeIdMap: TDictionary<Integer, IAstNode>;
|
|
pair: TJSONPair;
|
|
metaPair: TJSONPair;
|
|
metaData: TVisualInstanceMetadata;
|
|
begin
|
|
ARootNode := nil;
|
|
ALogicalMetadata := TDictionary<IAstNode, TLogicalMetadata>.Create;
|
|
AInstanceMetadata := TDictionary<TViewModelID, TVisualInstanceMetadata>.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<TJSONObject>('ast');
|
|
if Assigned(astJson) then
|
|
begin
|
|
nodeIdMap := TDictionary<Integer, IAstNode>.Create;
|
|
try
|
|
ARootNode := DeserializeNode(astJson, nodeIdMap);
|
|
if not Assigned(ARootNode) then
|
|
begin
|
|
exit;
|
|
end;
|
|
|
|
logicalMetaJson := rootJson.GetValue<TJSONObject>('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<string>('NodeType')));
|
|
meta.Description := metaJson.GetValue<string>('Description');
|
|
ALogicalMetadata.Add(node, meta);
|
|
end;
|
|
end;
|
|
end;
|
|
finally
|
|
nodeIdMap.Free;
|
|
end;
|
|
end;
|
|
|
|
instanceMetaJson := rootJson.GetValue<TJSONObject>('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<Double>('PositionX');
|
|
metaData.PositionOverride.Y := metaJson.GetValue<Double>('PositionY');
|
|
metaData.IsCollapsed := metaJson.GetValue<Boolean>('IsCollapsed');
|
|
var modeStr := metaJson.GetValue<string>('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<Integer, IAstNode>): 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<Integer>('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<string>('type');
|
|
tempId := AObject.GetValue<Integer>('id');
|
|
|
|
var nodeType := TAstNodeType(GetEnumValue(TypeInfo(TAstNodeType), 'ant' + nodeTypeStr));
|
|
case nodeType of
|
|
antConstant: Result := TAst.Constant(ScalarFromJson(AObject.GetValue<TJSONObject>('value')));
|
|
antIdentifier: Result := TAst.Identifier(AObject.GetValue<string>('name'));
|
|
antBinaryExpression:
|
|
Result :=
|
|
TAst.BinaryExpr(
|
|
DeserializeNode(AObject.GetValue<TJSONObject>('left'), ANodeIdMap),
|
|
TBinaryOperator(GetEnumValue(TypeInfo(TBinaryOperator), 'bo' + AObject.GetValue<string>('operator'))),
|
|
DeserializeNode(AObject.GetValue<TJSONObject>('right'), ANodeIdMap)
|
|
);
|
|
antUnaryExpression:
|
|
Result :=
|
|
TAst.UnaryExpr(
|
|
TUnaryOperator(GetEnumValue(TypeInfo(TUnaryOperator), 'uo' + AObject.GetValue<string>('operator'))),
|
|
DeserializeNode(AObject.GetValue<TJSONObject>('right'), ANodeIdMap)
|
|
);
|
|
antIfExpression:
|
|
begin
|
|
var elseBranch: IAstNode := nil;
|
|
if AObject.GetValue('elseBranch') <> nil then
|
|
begin
|
|
elseBranch := DeserializeNode(AObject.GetValue<TJSONObject>('elseBranch'), ANodeIdMap);
|
|
end;
|
|
Result :=
|
|
TAst.IfExpr(
|
|
DeserializeNode(AObject.GetValue<TJSONObject>('condition'), ANodeIdMap),
|
|
DeserializeNode(AObject.GetValue<TJSONObject>('thenBranch'), ANodeIdMap),
|
|
elseBranch
|
|
);
|
|
end;
|
|
antTernaryExpression:
|
|
Result :=
|
|
TAst.TernaryExpr(
|
|
DeserializeNode(AObject.GetValue<TJSONObject>('condition'), ANodeIdMap),
|
|
DeserializeNode(AObject.GetValue<TJSONObject>('thenBranch'), ANodeIdMap),
|
|
DeserializeNode(AObject.GetValue<TJSONObject>('elseBranch'), ANodeIdMap)
|
|
);
|
|
antLambdaExpression:
|
|
begin
|
|
var paramsJson := AObject.GetValue<TJSONArray>('parameters');
|
|
var params := TArray<IIdentifierNode>.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<TJSONObject>('body'), ANodeIdMap);
|
|
Result := TAst.LambdaExpr(params, body);
|
|
end;
|
|
antFunctionCall:
|
|
begin
|
|
var callee := DeserializeNode(AObject.GetValue<TJSONObject>('callee'), ANodeIdMap);
|
|
var argsJson := AObject.GetValue<TJSONArray>('arguments');
|
|
var args := TArray<IAstNode>.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<TJSONArray>('expressions');
|
|
var exprs := TArray<IAstNode>.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<TJSONObject>('initializer'), ANodeIdMap);
|
|
end;
|
|
Result := TAst.VarDecl(IIdentifierNode(DeserializeNode(AObject.GetValue<TJSONObject>('identifier'), ANodeIdMap)), initializer);
|
|
end;
|
|
antAssignment:
|
|
Result :=
|
|
TAst.Assign(
|
|
IIdentifierNode(DeserializeNode(AObject.GetValue<TJSONObject>('identifier'), ANodeIdMap)),
|
|
DeserializeNode(AObject.GetValue<TJSONObject>('value'), ANodeIdMap)
|
|
);
|
|
antIndexer:
|
|
Result :=
|
|
TAst.Indexer(
|
|
DeserializeNode(AObject.GetValue<TJSONObject>('base'), ANodeIdMap),
|
|
DeserializeNode(AObject.GetValue<TJSONObject>('index'), ANodeIdMap)
|
|
);
|
|
antMemberAccess:
|
|
Result :=
|
|
TAst.MemberAccess(
|
|
DeserializeNode(AObject.GetValue<TJSONObject>('base'), ANodeIdMap),
|
|
IIdentifierNode(DeserializeNode(AObject.GetValue<TJSONObject>('member'), ANodeIdMap))
|
|
);
|
|
antCreateSeries: Result := TAst.CreateSeries(AObject.GetValue<string>('definition'));
|
|
antAddSeriesItem:
|
|
begin
|
|
var lookback: IAstNode := nil;
|
|
if AObject.GetValue('lookback') <> nil then
|
|
begin
|
|
lookback := DeserializeNode(AObject.GetValue<TJSONObject>('lookback'), ANodeIdMap);
|
|
end;
|
|
Result :=
|
|
TAst.AddSeriesItem(
|
|
IIdentifierNode(DeserializeNode(AObject.GetValue<TJSONObject>('series'), ANodeIdMap)),
|
|
DeserializeNode(AObject.GetValue<TJSONObject>('value'), ANodeIdMap),
|
|
lookback
|
|
);
|
|
end;
|
|
antSeriesLength: Result := TAst.SeriesLength(IIdentifierNode(DeserializeNode(AObject.GetValue<TJSONObject>('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<string>('kind');
|
|
kind := TScalar.StringToKind(kindStr);
|
|
case kind of
|
|
skInteger: value.AsInteger := AObject.GetValue<Integer>('value');
|
|
skInt64: value.AsInt64 := StrToInt64(AObject.GetValue<string>('value'));
|
|
skUInt64: value.AsUInt64 := StrToUInt64(AObject.GetValue<string>('value'));
|
|
skSingle: value.AsSingle := AObject.GetValue<Double>('value');
|
|
skDouble: value.AsDouble := AObject.GetValue<Double>('value');
|
|
skDateTime: value.AsDateTime := ISO8601ToDate(AObject.GetValue<string>('value'));
|
|
skTimestamp: value.AsTimestamp := DateTimeToTimeStamp(ISO8601ToDate(AObject.GetValue<string>('value')));
|
|
skBoolean: value.AsBoolean := AObject.GetValue<Boolean>('value');
|
|
skChar: value.AsChar := AObject.GetValue<string>('value')[1];
|
|
skPChar: StrPCopy(value.AsPChar, AObject.GetValue<string>('value'));
|
|
skString: value.AsString := AObject.GetValue<shortstring>('value');
|
|
skBytes:
|
|
begin
|
|
hexStrAnsi := AnsiString(AObject.GetValue<string>('value'));
|
|
HexToBin(PAnsiChar(hexStrAnsi), @value.AsBytes, SizeOf(value.AsBytes));
|
|
end;
|
|
skDecimal:
|
|
begin
|
|
decVal := TDecimal.Create(StrToInt64(AObject.GetValue<string>('value')), AObject.GetValue<Integer>('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<IAstNode, Integer>;
|
|
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<IAstNode, Integer>.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<Integer, IAstNode>;
|
|
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<TJSONObject>('ast');
|
|
if Assigned(astJson) then
|
|
begin
|
|
persistence := TAstProjectPersistence.Create;
|
|
try
|
|
nodeIdMap := TDictionary<Integer, IAstNode>.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<IAstNode, Integer>);
|
|
begin
|
|
inherited Create;
|
|
FIdMap := AIdMap;
|
|
FNextId := 0;
|
|
FResultStack := TStack<TJSONObject>.Create;
|
|
FSerializedNodes := TDictionary<IAstNode, Boolean>.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<TJSONValue>.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<TJSONValue>.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<TJSONValue>.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.
|