unit Myc.Ast.Json;

interface

uses
    System.SysUtils,
    System.Generics.Collections,
    System.JSON,
    Myc.Data.Scalar,
    Myc.Data.Value,
    Myc.Ast,
    Myc.Ast.Visitor,
    Myc.Ast.Nodes;

type
    IJsonAstConverter = interface
        function Serialize(const RootNode: IAstNode): TJSONObject;
        function Deserialize(const AJson: TJSONObject): IAstNode;
    end;

    // TJsonAstConverter implements the visitor pattern for serialization
    // and uses factory functions for deserialization.
    TJsonAstConverter = class(TAstVisitor<TJSONObject>, IJsonAstConverter)
    private
        procedure DataValueToJson(const AValue: TDataValue; const AParent: TJSONObject; const AName: string);
        function JsonToNode(const AJson: TJSONValue; const ExpectedType: string = ''): IAstNode;
        function JsonToDataValue(const AObj: TJSONObject; const AName: string): TDataValue;

        // Deserialization Helpers
        function JsonToConstantNode(const AObj: TJSONObject): IConstantNode;
        function JsonToIdentifierNode(const AObj: TJSONObject): IIdentifierNode;
        function JsonToKeywordNode(const AObj: TJSONObject): IKeywordNode;
        function JsonToIfExprNode(const AObj: TJSONObject): IIfExpressionNode;
        function JsonToCondExprNode(const AObj: TJSONObject): ICondExpressionNode;
        function JsonToTernaryExprNode(const AObj: TJSONObject): IAstNode;
        function JsonToLambdaExprNode(const AObj: TJSONObject): ILambdaExpressionNode;
        function JsonToMacroDefNode(const AObj: TJSONObject): IMacroDefinitionNode;
        function JsonToQuasiquoteNode(const AObj: TJSONObject): IQuasiquoteNode;
        function JsonToUnquoteNode(const AObj: TJSONObject): IUnquoteNode;
        function JsonToUnquoteSplicingNode(const AObj: TJSONObject): IUnquoteSplicingNode;
        function JsonToFunctionCallNode(const AObj: TJSONObject): IFunctionCallNode;
        function JsonToMacroExpansionNode(const AObj: TJSONObject): IMacroExpansionNode;
        function JsonToRecurNode(const AObj: TJSONObject): IRecurNode;
        function JsonToBlockNode(const AObj: TJSONObject): IBlockExpressionNode;
        function JsonToVarDeclNode(const AObj: TJSONObject): IVariableDeclarationNode;
        function JsonToAssignmentNode(const AObj: TJSONObject): IAssignmentNode;
        function JsonToIndexerNode(const AObj: TJSONObject): IIndexerNode;
        function JsonToMemberAccessNode(const AObj: TJSONObject): IMemberAccessNode;
        function JsonToRecordLiteralNode(const AObj: TJSONObject): IRecordLiteralNode;
        function JsonToCreateSeriesNode(const AObj: TJSONObject): ICreateSeriesNode;
        function JsonToAddSeriesItemNode(const AObj: TJSONObject): IAddSeriesItemNode;
        function JsonToSeriesLengthNode(const AObj: TJSONObject): ISeriesLengthNode;
        function JsonToNopNode(const AObj: TJSONObject): INopNode;

    strict private
        // Serialization Visitors (IAstNode signature)
        function VisitConstant(const Node: IAstNode): TJSONObject;
        function VisitIdentifier(const Node: IAstNode): TJSONObject;
        function VisitKeyword(const Node: IAstNode): TJSONObject;

        function VisitParameterList(const Node: IAstNode): TJSONObject;
        function VisitArgumentList(const Node: IAstNode): TJSONObject;
        function VisitExpressionList(const Node: IAstNode): TJSONObject;
        function VisitRecordFieldList(const Node: IAstNode): TJSONObject;
        function VisitRecordField(const Node: IAstNode): TJSONObject;

        function VisitIfExpression(const Node: IAstNode): TJSONObject;
        function VisitCondExpression(const Node: IAstNode): TJSONObject;
        function VisitLambdaExpression(const Node: IAstNode): TJSONObject;
        function VisitMacroDefinition(const Node: IAstNode): TJSONObject;
        function VisitQuasiquote(const Node: IAstNode): TJSONObject;
        function VisitUnquote(const Node: IAstNode): TJSONObject;
        function VisitUnquoteSplicing(const Node: IAstNode): TJSONObject;
        function VisitFunctionCall(const Node: IAstNode): TJSONObject;
        function VisitMacroExpansionNode(const Node: IAstNode): TJSONObject;
        function VisitRecurNode(const Node: IAstNode): TJSONObject;
        function VisitBlockExpression(const Node: IAstNode): TJSONObject;
        function VisitVariableDeclaration(const Node: IAstNode): TJSONObject;
        function VisitAssignment(const Node: IAstNode): TJSONObject;
        function VisitIndexer(const Node: IAstNode): TJSONObject;
        function VisitMemberAccess(const Node: IAstNode): TJSONObject;
        function VisitRecordLiteral(const Node: IAstNode): TJSONObject;
        function VisitCreateSeries(const Node: IAstNode): TJSONObject;
        function VisitAddSeriesItem(const Node: IAstNode): TJSONObject;
        function VisitSeriesLength(const Node: IAstNode): TJSONObject;
        function VisitNop(const Node: IAstNode): TJSONObject;

        function VisitPipeInput(const Node: IAstNode): TJSONObject;
        function VisitPipeSelectorList(const Node: IAstNode): TJSONObject;
        function VisitPipeInputList(const Node: IAstNode): TJSONObject;
        function VisitPipe(const Node: IAstNode): TJSONObject;

    protected
        procedure SetupHandlers; override;

    public
        constructor Create;
        function Serialize(const RootNode: IAstNode): TJSONObject;
        function Deserialize(const AJson: TJSONObject): IAstNode;
    end;

implementation

uses
    Myc.Data.Keyword;

{ TJsonAstConverter }

constructor TJsonAstConverter.Create;
begin
    inherited;
end;

procedure TJsonAstConverter.SetupHandlers;
begin
    Register(akConstant, VisitConstant);
    Register(akIdentifier, VisitIdentifier);
    Register(akKeyword, VisitKeyword);

    Register(akParameterList, VisitParameterList);
    Register(akArgumentList, VisitArgumentList);
    Register(akExpressionList, VisitExpressionList);
    Register(akRecordFieldList, VisitRecordFieldList);
    Register(akRecordField, VisitRecordField);

    Register(akIfExpression, VisitIfExpression);
    Register(akCondExpression, VisitCondExpression);
    Register(akLambdaExpression, VisitLambdaExpression);
    Register(akFunctionCall, VisitFunctionCall);
    Register(akMacroExpansion, VisitMacroExpansionNode);
    Register(akBlockExpression, VisitBlockExpression);
    Register(akVariableDeclaration, VisitVariableDeclaration);
    Register(akAssignment, VisitAssignment);
    Register(akMacroDefinition, VisitMacroDefinition);
    Register(akQuasiquote, VisitQuasiquote);
    Register(akUnquote, VisitUnquote);
    Register(akUnquoteSplicing, VisitUnquoteSplicing);
    Register(akIndexer, VisitIndexer);
    Register(akMemberAccess, VisitMemberAccess);
    Register(akRecordLiteral, VisitRecordLiteral);
    Register(akCreateSeries, VisitCreateSeries);
    Register(akAddSeriesItem, VisitAddSeriesItem);
    Register(akSeriesLength, VisitSeriesLength);
    Register(akRecur, VisitRecurNode);
    Register(akNop, VisitNop);

    Register(akPipeInput, VisitPipeInput);
    Register(akPipeSelectorList, VisitPipeSelectorList);
    Register(akPipeInputList, VisitPipeInputList);
    Register(akPipe, VisitPipe);
end;

function TJsonAstConverter.Serialize(const RootNode: IAstNode): TJSONObject;
begin
    if not Assigned(RootNode) then
    begin
        exit(nil);
    end;
    Result := Visit(RootNode);
end;

function TJsonAstConverter.Deserialize(const AJson: TJSONObject): IAstNode;
begin
    Result := JsonToNode(AJson);
end;

{ Serialization Visitors }

procedure TJsonAstConverter.DataValueToJson(const AValue: TDataValue; const AParent: TJSONObject; const AName: string);
var
    valObj, scalarObj: TJSONObject;
begin
    valObj := TJSONObject.Create;
    valObj.AddPair('Kind', TJSONString.Create(AValue.Kind.ToString));

    case AValue.Kind of
        vkScalar:
        begin
            scalarObj := TJSONObject.Create;
            scalarObj.AddPair('Kind', TJSONString.Create(AValue.AsScalar.Kind.ToString));
            case AValue.AsScalar.Kind of
                TScalar.TKind.Ordinal: scalarObj.AddPair('Value', TJSONNumber.Create(AValue.AsScalar.Value.AsInt64));
                TScalar.TKind.Float: scalarObj.AddPair('Value', TJSONNumber.Create(AValue.AsScalar.Value.AsDouble));
                TScalar.TKind.Boolean: scalarObj.AddPair('Value', TJSONBool.Create(AValue.AsScalar.Value.AsInt64 <> 0));
                TScalar.TKind.DateTime: scalarObj.AddPair('Value', TJSONNumber.Create(AValue.AsScalar.Value.AsDouble));
                TScalar.TKind.Keyword:
                    scalarObj.AddPair('Value', TJSONString.Create(TKeywordRegistry.GetName(AValue.AsScalar.Value.AsInt64)));
            end;
            valObj.AddPair('Value', scalarObj);
        end;
        vkText: valObj.AddPair('Value', TJSONString.Create(AValue.AsText));
        vkVoid:;
    else
        raise ENotSupportedException.Create('Unsupported TDataValue kind for constant serialization.');
    end;

    AParent.AddPair(AName, valObj);
end;

function TJsonAstConverter.VisitConstant(const Node: IAstNode): TJSONObject;
begin
    Result := TJSONObject.Create;
    Result.AddPair('NodeType', TJSONString.Create('Constant'));
    DataValueToJson(Node.AsConstant.Value, Result, 'Value');
end;

function TJsonAstConverter.VisitIdentifier(const Node: IAstNode): TJSONObject;
begin
    Result := TJSONObject.Create;
    Result.AddPair('NodeType', TJSONString.Create('Identifier'));
    Result.AddPair('Name', TJSONString.Create(Node.AsIdentifier.Name));
end;

function TJsonAstConverter.VisitKeyword(const Node: IAstNode): TJSONObject;
begin
    Result := TJSONObject.Create;
    Result.AddPair('NodeType', TJSONString.Create('Keyword'));
    Result.AddPair('Name', TJSONString.Create(Node.AsKeyword.Value.Name));
end;

function TJsonAstConverter.VisitIfExpression(const Node: IAstNode): TJSONObject;
var
    E: IIfExpressionNode;
    elseObj: TJSONValue;
begin
    E := Node.AsIfExpression;
    Result := TJSONObject.Create;
    Result.AddPair('NodeType', TJSONString.Create('IfExpr'));
    Result.AddPair('Condition', Visit(E.Condition));
    Result.AddPair('ThenBranch', Visit(E.ThenBranch));

    if Assigned(E.ElseBranch) then
    begin
        elseObj := Visit(E.ElseBranch);
    end
    else
    begin
        elseObj := TJSONNull.Create;
    end;
    Result.AddPair('ElseBranch', elseObj);
end;

function TJsonAstConverter.VisitCondExpression(const Node: IAstNode): TJSONObject;
var
    E: ICondExpressionNode;
    pairsArray: TJSONArray;
    pairObj: TJSONObject;
    elseObj: TJSONValue;
    i: Integer;
begin
    E := Node.AsCondExpression;
    pairsArray := TJSONArray.Create;
    for i := 0 to High(E.Pairs) do
    begin
        pairObj := TJSONObject.Create;
        pairObj.AddPair('Condition', Visit(E.Pairs[i].Condition));
        pairObj.AddPair('Branch', Visit(E.Pairs[i].Branch));
        pairsArray.Add(pairObj);
    end;

    if Assigned(E.ElseBranch) then
    begin
        elseObj := Visit(E.ElseBranch);
    end
    else
    begin
        elseObj := TJSONNull.Create;
    end;

    Result := TJSONObject.Create;
    Result.AddPair('NodeType', TJSONString.Create('CondExpr'));
    Result.AddPair('Pairs', pairsArray);
    Result.AddPair('ElseBranch', elseObj);
end;

function TJsonAstConverter.VisitLambdaExpression(const Node: IAstNode): TJSONObject;
var
    L: ILambdaExpressionNode;
    paramsArray: TJSONArray;
    i: Integer;
begin
    L := Node.AsLambdaExpression;
    paramsArray := TJSONArray.Create;
    for i := 0 to L.Parameters.Count - 1 do
    begin
        paramsArray.Add(Visit(L.Parameters[i]));
    end;

    Result := TJSONObject.Create;
    Result.AddPair('NodeType', TJSONString.Create('LambdaExpr'));
    Result.AddPair('Parameters', paramsArray);
    Result.AddPair('Body', Visit(L.Body));
end;

function TJsonAstConverter.VisitMacroDefinition(const Node: IAstNode): TJSONObject;
var
    M: IMacroDefinitionNode;
    paramsArray: TJSONArray;
    i: Integer;
begin
    M := Node.AsMacroDefinition;
    paramsArray := TJSONArray.Create;
    for i := 0 to M.Parameters.Count - 1 do
    begin
        paramsArray.Add(Visit(M.Parameters[i]));
    end;

    Result := TJSONObject.Create;
    Result.AddPair('NodeType', TJSONString.Create('MacroDef'));
    Result.AddPair('Name', Visit(M.Name));
    Result.AddPair('Parameters', paramsArray);
    Result.AddPair('Body', Visit(M.Body));
end;

function TJsonAstConverter.VisitQuasiquote(const Node: IAstNode): TJSONObject;
begin
    Result := TJSONObject.Create;
    Result.AddPair('NodeType', TJSONString.Create('Quasiquote'));
    Result.AddPair('Expression', Visit(Node.AsQuasiquote.Expression));
end;

function TJsonAstConverter.VisitUnquote(const Node: IAstNode): TJSONObject;
begin
    Result := TJSONObject.Create;
    Result.AddPair('NodeType', TJSONString.Create('Unquote'));
    Result.AddPair('Expression', Visit(Node.AsUnquote.Expression));
end;

function TJsonAstConverter.VisitUnquoteSplicing(const Node: IAstNode): TJSONObject;
begin
    Result := TJSONObject.Create;
    Result.AddPair('NodeType', TJSONString.Create('UnquoteSplicing'));
    Result.AddPair('Expression', Visit(Node.AsUnquoteSplicing.Expression));
end;

function TJsonAstConverter.VisitFunctionCall(const Node: IAstNode): TJSONObject;
var
    C: IFunctionCallNode;
    argsArray: TJSONArray;
    i: Integer;
begin
    C := Node.AsFunctionCall;
    argsArray := TJSONArray.Create;
    for i := 0 to C.Arguments.Count - 1 do
    begin
        argsArray.Add(Visit(C.Arguments[i]));
    end;

    Result := TJSONObject.Create;
    Result.AddPair('NodeType', TJSONString.Create('FunctionCall'));
    Result.AddPair('Callee', Visit(C.Callee));
    Result.AddPair('Arguments', argsArray);
end;

function TJsonAstConverter.VisitMacroExpansionNode(const Node: IAstNode): TJSONObject;
var
    M: IMacroExpansionNode;
    argsArray: TJSONArray;
    i: Integer;
begin
    M := Node.AsMacroExpansion;
    argsArray := TJSONArray.Create;
    for i := 0 to M.CallNode.Arguments.Count - 1 do
    begin
        argsArray.Add(Visit(M.CallNode.Arguments[i]));
    end;

    Result := TJSONObject.Create;
    Result.AddPair('NodeType', TJSONString.Create('MacroExpansion'));
    Result.AddPair('Callee', Visit(M.CallNode.Callee));
    Result.AddPair('Arguments', argsArray);
    Result.AddPair('ExpandedBody', Visit(M.ExpandedBody));
end;

function TJsonAstConverter.VisitRecurNode(const Node: IAstNode): TJSONObject;
var
    R: IRecurNode;
    argsArray: TJSONArray;
    i: Integer;
begin
    R := Node.AsRecur;
    argsArray := TJSONArray.Create;
    for i := 0 to R.Arguments.Count - 1 do
    begin
        argsArray.Add(Visit(R.Arguments[i]));
    end;

    Result := TJSONObject.Create;
    Result.AddPair('NodeType', TJSONString.Create('Recur'));
    Result.AddPair('Arguments', argsArray);
end;

function TJsonAstConverter.VisitBlockExpression(const Node: IAstNode): TJSONObject;
var
    B: IBlockExpressionNode;
    exprsArray: TJSONArray;
    i: Integer;
begin
    B := Node.AsBlockExpression;
    exprsArray := TJSONArray.Create;
    for i := 0 to B.Expressions.Count - 1 do
    begin
        exprsArray.Add(Visit(B.Expressions[i]));
    end;

    Result := TJSONObject.Create;
    Result.AddPair('NodeType', TJSONString.Create('Block'));
    Result.AddPair('Expressions', exprsArray);
end;

function TJsonAstConverter.VisitVariableDeclaration(const Node: IAstNode): TJSONObject;
var
    V: IVariableDeclarationNode;
    initObj: TJSONValue;
begin
    V := Node.AsVariableDeclaration;
    if Assigned(V.Initializer) then
    begin
        initObj := Visit(V.Initializer);
    end
    else
    begin
        initObj := TJSONNull.Create;
    end;

    Result := TJSONObject.Create;
    Result.AddPair('NodeType', TJSONString.Create('VarDecl'));
    Result.AddPair('Identifier', Visit(V.Target));
    Result.AddPair('Initializer', initObj);
end;

function TJsonAstConverter.VisitAssignment(const Node: IAstNode): TJSONObject;
var
    A: IAssignmentNode;
begin
    A := Node.AsAssignment;
    Result := TJSONObject.Create;
    Result.AddPair('NodeType', TJSONString.Create('Assignment'));
    Result.AddPair('Identifier', Visit(A.Target));
    Result.AddPair('Value', Visit(A.Value));
end;

function TJsonAstConverter.VisitIndexer(const Node: IAstNode): TJSONObject;
var
    I: IIndexerNode;
begin
    I := Node.AsIndexer;
    Result := TJSONObject.Create;
    Result.AddPair('NodeType', TJSONString.Create('Indexer'));
    Result.AddPair('Base', Visit(I.Base));
    Result.AddPair('Index', Visit(I.Index));
end;

function TJsonAstConverter.VisitMemberAccess(const Node: IAstNode): TJSONObject;
var
    M: IMemberAccessNode;
begin
    M := Node.AsMemberAccess;
    Result := TJSONObject.Create;
    Result.AddPair('NodeType', TJSONString.Create('MemberAccess'));
    Result.AddPair('Base', Visit(M.Base));
    Result.AddPair('Member', Visit(M.Member));
end;

function TJsonAstConverter.VisitRecordLiteral(const Node: IAstNode): TJSONObject;
var
    R: IRecordLiteralNode;
    fieldsArray: TJSONArray;
    i: Integer;
begin
    R := Node.AsRecordLiteral;
    Result := TJSONObject.Create;
    Result.AddPair('NodeType', TJSONString.Create('RecordLiteral'));
    fieldsArray := TJSONArray.Create;
    for i := 0 to R.Fields.Count - 1 do
    begin
        fieldsArray.Add(Visit(R.Fields[i]));
    end;
    Result.AddPair('Fields', fieldsArray);
end;

function TJsonAstConverter.VisitRecordField(const Node: IAstNode): TJSONObject;
var
    F: IRecordFieldNode;
begin
    F := Node.AsRecordField;
    Result := TJSONObject.Create;
    Result.AddPair('Name', TJSONString.Create(F.Key.Value.Name));
    Result.AddPair('Value', Visit(F.Value));
end;

function TJsonAstConverter.VisitCreateSeries(const Node: IAstNode): TJSONObject;
begin
    Result := TJSONObject.Create;
    Result.AddPair('NodeType', TJSONString.Create('CreateSeries'));
    Result.AddPair('Definition', TJSONString.Create(Node.AsCreateSeries.Definition));
end;

function TJsonAstConverter.VisitAddSeriesItem(const Node: IAstNode): TJSONObject;
var
    A: IAddSeriesItemNode;
    lookbackObj: TJSONValue;
begin
    A := Node.AsAddSeriesItem;
    if Assigned(A.Lookback) then
    begin
        lookbackObj := Visit(A.Lookback);
    end
    else
    begin
        lookbackObj := TJSONNull.Create;
    end;

    Result := TJSONObject.Create;
    Result.AddPair('NodeType', TJSONString.Create('AddSeriesItem'));
    Result.AddPair('Series', Visit(A.Series));
    Result.AddPair('Value', Visit(A.Value));
    Result.AddPair('Lookback', lookbackObj);
end;

function TJsonAstConverter.VisitSeriesLength(const Node: IAstNode): TJSONObject;
begin
    Result := TJSONObject.Create;
    Result.AddPair('NodeType', TJSONString.Create('SeriesLength'));
    Result.AddPair('Series', Visit(Node.AsSeriesLength.Series));
end;

function TJsonAstConverter.VisitNop(const Node: IAstNode): TJSONObject;
begin
    Result := TJSONObject.Create;
    Result.AddPair('NodeType', TJSONString.Create('Nop'));
end;

function TJsonAstConverter.VisitParameterList(const Node: IAstNode): TJSONObject;
begin
    Result := nil;
end;
function TJsonAstConverter.VisitArgumentList(const Node: IAstNode): TJSONObject;
begin
    Result := nil;
end;
function TJsonAstConverter.VisitExpressionList(const Node: IAstNode): TJSONObject;
begin
    Result := nil;
end;
function TJsonAstConverter.VisitRecordFieldList(const Node: IAstNode): TJSONObject;
begin
    Result := nil;
end;

function TJsonAstConverter.VisitPipeInput(const Node: IAstNode): TJSONObject;
begin
    raise ENotSupportedException.Create('JSON Pipe Serialization not implemented.');
end;
function TJsonAstConverter.VisitPipeSelectorList(const Node: IAstNode): TJSONObject;
begin
    raise ENotSupportedException.Create('JSON Pipe Serialization not implemented.');
end;
function TJsonAstConverter.VisitPipeInputList(const Node: IAstNode): TJSONObject;
begin
    raise ENotSupportedException.Create('JSON Pipe Serialization not implemented.');
end;
function TJsonAstConverter.VisitPipe(const Node: IAstNode): TJSONObject;
begin
    raise ENotSupportedException.Create('JSON Pipe Serialization not implemented.');
end;

{ Deserialization Handlers }

function TJsonAstConverter.JsonToDataValue(const AObj: TJSONObject; const AName: string): TDataValue;
var
    valObj, scalarObj: TJSONObject;
    kindStr: string;
begin
    valObj := AObj.GetValue<TJSONObject>(AName);
    kindStr := valObj.GetValue<string>('Kind');

    if SameText(kindStr, 'Scalar') then
    begin
        scalarObj := valObj.GetValue<TJSONObject>('Value');
        case TScalar.StringToKind(scalarObj.GetValue<string>('Kind')) of
            TScalar.TKind.Ordinal: Result := TScalar.FromInt64(scalarObj.GetValue<TJSONNumber>('Value').AsInt64);
            TScalar.TKind.Float: Result := TScalar.FromDouble(scalarObj.GetValue<TJSONNumber>('Value').AsDouble);
            TScalar.TKind.Boolean: Result := TScalar.FromBoolean(scalarObj.GetValue<TJSONBool>('Value').AsBoolean);
            TScalar.TKind.DateTime: Result := TScalar.FromDateTime(scalarObj.GetValue<TJSONNumber>('Value').AsDouble);
            TScalar.TKind.Keyword: Result := TScalar.FromKeyword(TKeywordRegistry.Intern(scalarObj.GetValue<string>('Value')));
        end;
    end
    else if SameText(kindStr, 'Text') then
    begin
        Result := valObj.GetValue<string>('Value');
    end
    else if SameText(kindStr, 'Void') then
    begin
        Result := TDataValue.Void;
    end
    else
    begin
        raise ENotSupportedException.Create('Unsupported TDataValue kind.');
    end;
end;

function TJsonAstConverter.JsonToNode(const AJson: TJSONValue; const ExpectedType: string): IAstNode;
var
    obj: TJSONObject;
    nodeType: string;
begin
    if not (AJson is TJSONObject) then
    begin
        raise EInvalidCast.Create('Expected JSON object.');
    end;
    obj := AJson as TJSONObject;
    nodeType := obj.GetValue<string>('NodeType');

    if nodeType = 'Constant' then
        Result := JsonToConstantNode(obj)
    else if nodeType = 'Identifier' then
        Result := JsonToIdentifierNode(obj)
    else if nodeType = 'Keyword' then
        Result := JsonToKeywordNode(obj)
    else if nodeType = 'IfExpr' then
        Result := JsonToIfExprNode(obj)
    else if nodeType = 'CondExpr' then
        Result := JsonToCondExprNode(obj)
    else if nodeType = 'TernaryExpr' then
        Result := JsonToTernaryExprNode(obj)
    else if nodeType = 'LambdaExpr' then
        Result := JsonToLambdaExprNode(obj)
    else if nodeType = 'MacroDef' then
        Result := JsonToMacroDefNode(obj)
    else if nodeType = 'MacroExpansion' then
        Result := JsonToMacroExpansionNode(obj)
    else if nodeType = 'Quasiquote' then
        Result := JsonToQuasiquoteNode(obj)
    else if nodeType = 'Unquote' then
        Result := JsonToUnquoteNode(obj)
    else if nodeType = 'UnquoteSplicing' then
        Result := JsonToUnquoteSplicingNode(obj)
    else if nodeType = 'FunctionCall' then
        Result := JsonToFunctionCallNode(obj)
    else if nodeType = 'Recur' then
        Result := JsonToRecurNode(obj)
    else if nodeType = 'Block' then
        Result := JsonToBlockNode(obj)
    else if nodeType = 'VarDecl' then
        Result := JsonToVarDeclNode(obj)
    else if nodeType = 'Assignment' then
        Result := JsonToAssignmentNode(obj)
    else if nodeType = 'Indexer' then
        Result := JsonToIndexerNode(obj)
    else if nodeType = 'MemberAccess' then
        Result := JsonToMemberAccessNode(obj)
    else if nodeType = 'RecordLiteral' then
        Result := JsonToRecordLiteralNode(obj)
    else if nodeType = 'CreateSeries' then
        Result := JsonToCreateSeriesNode(obj)
    else if nodeType = 'AddSeriesItem' then
        Result := JsonToAddSeriesItemNode(obj)
    else if nodeType = 'SeriesLength' then
        Result := JsonToSeriesLengthNode(obj)
    else if nodeType = 'Nop' then
        Result := JsonToNopNode(obj)
    else
        raise ENotSupportedException.CreateFmt('Unsupported NodeType "%s".', [nodeType]);
end;

function TJsonAstConverter.JsonToConstantNode(const AObj: TJSONObject): IConstantNode;
begin
    Result := TAst.Constant(JsonToDataValue(AObj, 'Value'));
end;

function TJsonAstConverter.JsonToIdentifierNode(const AObj: TJSONObject): IIdentifierNode;
begin
    Result := TAst.Identifier(AObj.GetValue<string>('Name'));
end;

function TJsonAstConverter.JsonToKeywordNode(const AObj: TJSONObject): IKeywordNode;
begin
    Result := TAst.Keyword(AObj.GetValue<string>('Name'));
end;

function TJsonAstConverter.JsonToIfExprNode(const AObj: TJSONObject): IIfExpressionNode;
var
    elseNode: IAstNode;
begin
    if AObj.GetValue('ElseBranch') is TJSONNull then
    begin
        elseNode := nil;
    end
    else
    begin
        elseNode := JsonToNode(AObj.GetValue('ElseBranch'));
    end;

    Result := TAst.IfExpr(JsonToNode(AObj.GetValue('Condition')), JsonToNode(AObj.GetValue('ThenBranch')), elseNode);
end;

function TJsonAstConverter.JsonToCondExprNode(const AObj: TJSONObject): ICondExpressionNode;
var
    pairsArray: TJSONArray;
    pairs: TArray<TCondPair>;
    i: Integer;
    elseNode: IAstNode;
begin
    pairsArray := AObj.GetValue<TJSONArray>('Pairs');
    SetLength(pairs, pairsArray.Count);
    for i := 0 to pairsArray.Count - 1 do
    begin
        pairs[i] :=
            TCondPair.Create(
                JsonToNode((pairsArray.Items[i] as TJSONObject).GetValue('Condition')),
                JsonToNode((pairsArray.Items[i] as TJSONObject).GetValue('Branch'))
            );
    end;

    if AObj.GetValue('ElseBranch') is TJSONNull then
    begin
        elseNode := nil;
    end
    else
    begin
        elseNode := JsonToNode(AObj.GetValue('ElseBranch'));
    end;

    Result := TAst.CondExpr(pairs, elseNode);
end;

function TJsonAstConverter.JsonToTernaryExprNode(const AObj: TJSONObject): IAstNode;
begin
    Result :=
        TAst.TernaryExpr(
            JsonToNode(AObj.GetValue('Condition')),
            JsonToNode(AObj.GetValue('ThenBranch')),
            JsonToNode(AObj.GetValue('ElseBranch'))
        );
end;

function TJsonAstConverter.JsonToLambdaExprNode(const AObj: TJSONObject): ILambdaExpressionNode;
var
    params: TArray<IIdentifierNode>;
    paramArray: TJSONArray;
    i: Integer;
begin
    paramArray := AObj.GetValue<TJSONArray>('Parameters');
    SetLength(params, paramArray.Count);
    for i := 0 to paramArray.Count - 1 do
    begin
        params[i] := JsonToIdentifierNode(paramArray.Items[i] as TJSONObject);
    end;
    Result := TAst.LambdaExpr(params, JsonToNode(AObj.GetValue('Body')));
end;

function TJsonAstConverter.JsonToMacroDefNode(const AObj: TJSONObject): IMacroDefinitionNode;
var
    params: TArray<IIdentifierNode>;
    paramArray: TJSONArray;
    i: Integer;
begin
    paramArray := AObj.GetValue<TJSONArray>('Parameters');
    SetLength(params, paramArray.Count);
    for i := 0 to paramArray.Count - 1 do
    begin
        params[i] := JsonToIdentifierNode(paramArray.Items[i] as TJSONObject);
    end;
    Result := TAst.MacroDef(JsonToIdentifierNode(AObj.GetValue('Name') as TJSONObject), params, JsonToNode(AObj.GetValue('Body')));
end;

function TJsonAstConverter.JsonToQuasiquoteNode(const AObj: TJSONObject): IQuasiquoteNode;
begin
    Result := TAst.Quasiquote(JsonToNode(AObj.GetValue('Expression')));
end;

function TJsonAstConverter.JsonToUnquoteNode(const AObj: TJSONObject): IUnquoteNode;
begin
    Result := TAst.Unquote(JsonToNode(AObj.GetValue('Expression')));
end;

function TJsonAstConverter.JsonToUnquoteSplicingNode(const AObj: TJSONObject): IUnquoteSplicingNode;
begin
    Result := TAst.UnquoteSplicing(JsonToNode(AObj.GetValue('Expression')).AsQuasiquote);
end;

function TJsonAstConverter.JsonToFunctionCallNode(const AObj: TJSONObject): IFunctionCallNode;
var
    args: TArray<IAstNode>;
    argsArray: TJSONArray;
    i: Integer;
begin
    argsArray := AObj.GetValue<TJSONArray>('Arguments');
    SetLength(args, argsArray.Count);
    for i := 0 to argsArray.Count - 1 do
    begin
        args[i] := JsonToNode(argsArray.Items[i]);
    end;
    Result := TAst.FunctionCall(JsonToNode(AObj.GetValue('Callee')), args);
end;

function TJsonAstConverter.JsonToMacroExpansionNode(const AObj: TJSONObject): IMacroExpansionNode;
var
    callee, expandedBody: IAstNode;
    args: TArray<IAstNode>;
    argsArray: TJSONArray;
    i: Integer;
    tempCallNode: IFunctionCallNode;
begin
    callee := JsonToNode(AObj.GetValue('Callee'));
    expandedBody := JsonToNode(AObj.GetValue('ExpandedBody'));
    argsArray := AObj.GetValue<TJSONArray>('Arguments');
    SetLength(args, argsArray.Count);
    for i := 0 to argsArray.Count - 1 do
    begin
        args[i] := JsonToNode(argsArray.Items[i]);
    end;

    tempCallNode := TAst.FunctionCall(callee, args);
    Result := TAst.MacroExpansionNode(tempCallNode, expandedBody);
end;

function TJsonAstConverter.JsonToRecurNode(const AObj: TJSONObject): IRecurNode;
var
    args: TArray<IAstNode>;
    argsArray: TJSONArray;
    i: Integer;
begin
    argsArray := AObj.GetValue<TJSONArray>('Arguments');
    SetLength(args, argsArray.Count);
    for i := 0 to argsArray.Count - 1 do
    begin
        args[i] := JsonToNode(argsArray.Items[i]);
    end;
    Result := TAst.Recur(args);
end;

function TJsonAstConverter.JsonToBlockNode(const AObj: TJSONObject): IBlockExpressionNode;
var
    exprs: TArray<IAstNode>;
    exprsArray: TJSONArray;
    i: Integer;
begin
    exprsArray := AObj.GetValue<TJSONArray>('Expressions');
    SetLength(exprs, exprsArray.Count);
    for i := 0 to exprsArray.Count - 1 do
    begin
        exprs[i] := JsonToNode(exprsArray.Items[i]);
    end;
    Result := TAst.Block(exprs);
end;

function TJsonAstConverter.JsonToVarDeclNode(const AObj: TJSONObject): IVariableDeclarationNode;
var
    initNode: IAstNode;
begin
    if AObj.GetValue('Initializer') is TJSONNull then
    begin
        initNode := nil;
    end
    else
    begin
        initNode := JsonToNode(AObj.GetValue('Initializer'));
    end;

    Result := TAst.VarDecl(JsonToIdentifierNode(AObj.GetValue('Identifier') as TJSONObject), initNode);
end;

function TJsonAstConverter.JsonToAssignmentNode(const AObj: TJSONObject): IAssignmentNode;
begin
    Result := TAst.Assign(JsonToIdentifierNode(AObj.GetValue('Identifier') as TJSONObject), JsonToNode(AObj.GetValue('Value')));
end;

function TJsonAstConverter.JsonToIndexerNode(const AObj: TJSONObject): IIndexerNode;
begin
    Result := TAst.Indexer(JsonToNode(AObj.GetValue('Base')), JsonToNode(AObj.GetValue('Index')));
end;

function TJsonAstConverter.JsonToMemberAccessNode(const AObj: TJSONObject): IMemberAccessNode;
begin
    Result := TAst.MemberAccess(JsonToNode(AObj.GetValue('Base')), JsonToKeywordNode(AObj.GetValue('Member') as TJSONObject));
end;

function TJsonAstConverter.JsonToRecordLiteralNode(const AObj: TJSONObject): IRecordLiteralNode;
var
    fields: TArray<IRecordFieldNode>;
    fieldsArray: TJSONArray;
    i: Integer;
begin
    fieldsArray := AObj.GetValue<TJSONArray>('Fields');
    SetLength(fields, fieldsArray.Count);
    for i := 0 to fieldsArray.Count - 1 do
    begin
        fields[i] :=
            TAst.RecordField(
                TAst.Keyword((fieldsArray.Items[i] as TJSONObject).GetValue<string>('Name')),
                JsonToNode((fieldsArray.Items[i] as TJSONObject).GetValue('Value'))
            );
    end;
    Result := TAst.RecordLiteral(fields);
end;

function TJsonAstConverter.JsonToCreateSeriesNode(const AObj: TJSONObject): ICreateSeriesNode;
begin
    Result := TAst.CreateSeries(AObj.GetValue<string>('Definition'));
end;

function TJsonAstConverter.JsonToAddSeriesItemNode(const AObj: TJSONObject): IAddSeriesItemNode;
var
    lookNode: IAstNode;
begin
    if AObj.GetValue('Lookback') is TJSONNull then
    begin
        lookNode := nil;
    end
    else
    begin
        lookNode := JsonToNode(AObj.GetValue('Lookback'));
    end;

    Result :=
        TAst.AddSeriesItem(JsonToIdentifierNode(AObj.GetValue('Series') as TJSONObject), JsonToNode(AObj.GetValue('Value')), lookNode);
end;

function TJsonAstConverter.JsonToSeriesLengthNode(const AObj: TJSONObject): ISeriesLengthNode;
begin
    Result := TAst.SeriesLength(JsonToIdentifierNode(AObj.GetValue('Series') as TJSONObject));
end;

function TJsonAstConverter.JsonToNopNode(const AObj: TJSONObject): INopNode;
begin
    Result := TAst.Nop.AsNop;
end;

end.
