Files
MycLib/Src/AST/Myc.Ast.Persistence.pas
T
Michael Schimmel a9cc9633a2 Beginning Editor
2025-09-08 18:59:04 +02:00

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.