unit Myc.Ast.Dumper;

interface

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

type
    // Dumps a bound AST into a human-readable format for debugging purposes.
    IAstDumper = interface(IAstVisitor)
        procedure Execute(const RootNode: IAstNode);
    end;

    TAstDumper = class(TAstVisitor, IAstDumper)
    private
        FOutput: TStrings;
        FIndent: Integer;
        procedure Indent;
        procedure Unindent;
        procedure Log(const Text: string; const Node: IAstNode = nil); overload;
        procedure LogFmt(const Fmt: string; const Args: array of const; const Node: IAstNode = nil); overload;
        function FormatAddress(const Addr: TResolvedAddress): string;

    strict private
        // Internal visit helpers with strict IAstNode signature
        function VisitConstant(const Node: IAstNode): TVoid;
        function VisitIdentifier(const Node: IAstNode): TVoid;
        function VisitKeyword(const Node: IAstNode): TVoid;
        function VisitIfExpression(const Node: IAstNode): TVoid;
        function VisitCondExpression(const Node: IAstNode): TVoid;
        function VisitLambdaExpression(const Node: IAstNode): TVoid;
        function VisitFunctionCall(const Node: IAstNode): TVoid;
        function VisitMacroExpansionNode(const Node: IAstNode): TVoid;
        function VisitRecurNode(const Node: IAstNode): TVoid;
        function VisitBlockExpression(const Node: IAstNode): TVoid;
        function VisitVariableDeclaration(const Node: IAstNode): TVoid;
        function VisitAssignment(const Node: IAstNode): TVoid;
        function VisitMacroDefinition(const Node: IAstNode): TVoid;
        function VisitQuasiquote(const Node: IAstNode): TVoid;
        function VisitUnquote(const Node: IAstNode): TVoid;
        function VisitUnquoteSplicing(const Node: IAstNode): TVoid;
        function VisitIndexer(const Node: IAstNode): TVoid;
        function VisitMemberAccess(const Node: IAstNode): TVoid;
        function VisitRecordLiteral(const Node: IAstNode): TVoid;
        function VisitCreateSeries(const Node: IAstNode): TVoid;
        function VisitAddSeriesItem(const Node: IAstNode): TVoid;
        function VisitSeriesLength(const Node: IAstNode): TVoid;
        function VisitNop(const Node: IAstNode): TVoid;

        // List Visitors
        function VisitParameterList(const Node: IAstNode): TVoid;
        function VisitArgumentList(const Node: IAstNode): TVoid;
        function VisitExpressionList(const Node: IAstNode): TVoid;
        function VisitRecordFieldList(const Node: IAstNode): TVoid;
        function VisitRecordField(const Node: IAstNode): TVoid;

        // Pipe Visitors
        function VisitPipeInput(const Node: IAstNode): TVoid;
        function VisitPipeSelectorList(const Node: IAstNode): TVoid;
        function VisitPipeInputList(const Node: IAstNode): TVoid;
        function VisitPipe(const Node: IAstNode): TVoid;

    protected
        procedure SetupHandlers; override;

    public
        constructor Create(const AOutput: TStrings);
        class procedure Dump(const RootNode: IAstNode; const Output: TStrings);
        procedure Execute(const RootNode: IAstNode);
    end;

implementation

uses
    Myc.Data.Keyword,
    Myc.Ast.Types;

{ TAstDumper }

class procedure TAstDumper.Dump(const RootNode: IAstNode; const Output: TStrings);
var
    dumper: TAstDumper;
begin
    if (not Assigned(Output)) or (not Assigned(RootNode)) then
    begin
        exit;
    end;

    Output.Clear;
    dumper := TAstDumper.Create(Output);
    try
        dumper.Execute(RootNode);
    finally
        dumper.Free;
    end;
end;

constructor TAstDumper.Create(const AOutput: TStrings);
begin
    inherited Create;
    FOutput := AOutput;
    FIndent := 0;
end;

procedure TAstDumper.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;

procedure TAstDumper.Execute(const RootNode: IAstNode);
begin
    if Assigned(RootNode) then
    begin
        Visit(RootNode);
    end;
end;

procedure TAstDumper.Indent;
begin
    inc(FIndent, 2);
end;

procedure TAstDumper.Unindent;
begin
    dec(FIndent, 2);
end;

procedure TAstDumper.Log(const Text: string; const Node: IAstNode);
var
    typeStr: string;
    staticType: IStaticType;
begin
    typeStr := '';
    if Assigned(Node) and Node.IsTyped then
    begin
        staticType := Node.AsTypedNode.StaticType;
        if Assigned(staticType) then
        begin
            typeStr := Format(' <Type: %s>', [staticType.ToString]);
        end
        else
        begin
            typeStr := ' <Type: nil>';
        end;
    end;
    FOutput.Add(StringOfChar(' ', FIndent) + Text + typeStr);
end;

procedure TAstDumper.LogFmt(const Fmt: string; const Args: array of const; const Node: IAstNode);
begin
    Log(Format(Fmt, Args), Node);
end;

function TAstDumper.FormatAddress(const Addr: TResolvedAddress): string;
begin
    case Addr.Kind of
        akUnresolved: Result := '!! UNRESOLVED !!';
        akLocalOrParent: Result := Format('LocalOrParent (Depth: %d, Slot: %d)', [Addr.ScopeDepth, Addr.SlotIndex]);
        akUpvalue: Result := Format('Upvalue (Index: %d)', [Addr.SlotIndex]);
    else
        Result := 'Unknown Address Kind';
    end;
end;

function TAstDumper.VisitConstant(const Node: IAstNode): TVoid;
begin
    LogFmt('Constant: %s', [Node.AsConstant.Value.ToString], Node);
end;

function TAstDumper.VisitIdentifier(const Node: IAstNode): TVoid;
var
    I: IIdentifierNode;
begin
    I := Node.AsIdentifier;
    if I.Address.Kind <> akUnresolved then
    begin
        LogFmt('Identifier: %s -> %s', [I.Name, FormatAddress(I.Address)], Node);
    end
    else
    begin
        LogFmt('Identifier: %s (unbound)', [I.Name], Node);
    end;
end;

function TAstDumper.VisitKeyword(const Node: IAstNode): TVoid;
begin
    LogFmt('Keyword: :%s', [Node.AsKeyword.Value.Name], Node);
end;

function TAstDumper.VisitIfExpression(const Node: IAstNode): TVoid;
var
    E: IIfExpressionNode;
begin
    E := Node.AsIfExpression;
    Log('IfExpression', Node);
    Indent;
    Log('Condition:');
    Visit(E.Condition);
    Log('Then:');
    Visit(E.ThenBranch);
    if Assigned(E.ElseBranch) then
    begin
        Log('Else:');
        Visit(E.ElseBranch);
    end;
    Unindent;
end;

function TAstDumper.VisitCondExpression(const Node: IAstNode): TVoid;
var
    E: ICondExpressionNode;
    i: Integer;
begin
    E := Node.AsCondExpression;
    LogFmt('CondExpression (%d pairs)', [Length(E.Pairs)], Node);
    Indent;
    for i := 0 to High(E.Pairs) do
    begin
        LogFmt('Pair %d:', [i]);
        Indent;
        Log('Condition:');
        Visit(E.Pairs[i].Condition);
        Log('Branch:');
        Visit(E.Pairs[i].Branch);
        Unindent;
    end;
    Log('Else:');
    Visit(E.ElseBranch);
    Unindent;
end;

function TAstDumper.VisitLambdaExpression(const Node: IAstNode): TVoid;
var
    E: ILambdaExpressionNode;
    addr: TResolvedAddress;
    symbols: TArray<string>;
    layout: IScopeLayout;
    slot: Integer;
    typ: IStaticType;
begin
    E := Node.AsLambdaExpression;
    LogFmt(
        'LambdaExpression (HasNested: %s, IsPure: %s)',
        [E.HasNestedLambdas.ToString(TUseBoolStrs.True), E.IsPure.ToString(TUseBoolStrs.True)],
        Node
    );
    Indent;

    if Assigned(E.Layout) then
    begin
        LogFmt('Scope: Layout Slots=%d', [E.Layout.SlotCount]);
        if Assigned(E.Descriptor) then
        begin
            Log('Symbol Table:');
            Indent;
            layout := E.Layout;
            symbols := layout.GetSymbols;
            TArray.Sort<string>(symbols);
            for var name in symbols do
            begin
                slot := layout.FindSlot(name);
                typ := E.Descriptor.GetSymbolType(slot);
                LogFmt('"%s" -> Slot %d (Type: %s)', [name, slot, typ.ToString]);
            end;
            Unindent;
        end;
    end;

    Log('Parameters:');
    Indent;
    Visit(E.Parameters);
    Unindent;

    if Length(E.Upvalues) > 0 then
    begin
        LogFmt('Captured Upvalues (%d):', [Length(E.Upvalues)]);
        Indent;
        for addr in E.Upvalues do
        begin
            Log(FormatAddress(addr));
        end;
        Unindent;
    end;

    Log('Body:');
    Visit(E.Body);
    Unindent;
end;

function TAstDumper.VisitFunctionCall(const Node: IAstNode): TVoid;
var
    C: IFunctionCallNode;
    argTypes: TArray<string>;
    i: Integer;
begin
    C := Node.AsFunctionCall;
    LogFmt(
        'FunctionCall (IsTailCall: %s, StaticTarget: %s, IsTargetPure: %s)',
        [
            C.IsTailCall.ToString(TUseBoolStrs.True),
            Assigned(C.StaticTarget).ToString(TUseBoolStrs.True),
            C.IsTargetPure.ToString(TUseBoolStrs.True)
        ],
        Node
    );

    if Assigned(C.StaticTarget) then
    begin
        Indent;
        SetLength(argTypes, C.Arguments.Count);
        for i := 0 to C.Arguments.Count - 1 do
        begin
            if C.Arguments[i].IsTyped then
                argTypes[i] := C.Arguments[i].AsTypedNode.StaticType.ToString
            else
                argTypes[i] := 'Untyped';
        end;
        LogFmt('ResolvedSig: Method(%s): %s', [string.Join(', ', argTypes), C.StaticType.ToString]);
        Unindent;
    end;

    Indent;
    Log('Callee:');
    Visit(C.Callee);
    LogFmt('Arguments (%d):', [C.Arguments.Count]);
    Visit(C.Arguments);
    Unindent;
end;

function TAstDumper.VisitMacroExpansionNode(const Node: IAstNode): TVoid;
var
    M: IMacroExpansionNode;
begin
    M := Node.AsMacroExpansion;
    Log('MacroExpansion', Node);
    Indent;
    Log('Original Call:');
    Visit(M.CallNode);
    Log('Expanded Body:');
    Visit(M.ExpandedBody);
    Unindent;
end;

function TAstDumper.VisitRecurNode(const Node: IAstNode): TVoid;
begin
    Log('Recur', Node);
    Indent;
    Visit(Node.AsRecur.Arguments);
    Unindent;
end;

function TAstDumper.VisitBlockExpression(const Node: IAstNode): TVoid;
begin
    Log('BlockExpression', Node);
    Indent;
    Visit(Node.AsBlockExpression.Expressions);
    Unindent;
end;

function TAstDumper.VisitVariableDeclaration(const Node: IAstNode): TVoid;
var
    V: IVariableDeclarationNode;
begin
    V := Node.AsVariableDeclaration;
    LogFmt('VariableDeclaration (IsBoxed: %s)', [V.IsBoxed.ToString(TUseBoolStrs.True)], Node);
    Indent;
    Visit(V.Target);
    if Assigned(V.Initializer) then
    begin
        Log('Initializer:');
        Visit(V.Initializer);
    end;
    Unindent;
end;

function TAstDumper.VisitAssignment(const Node: IAstNode): TVoid;
var
    A: IAssignmentNode;
begin
    A := Node.AsAssignment;
    Log('Assignment', Node);
    Indent;
    Visit(A.Target);
    Log('Value:');
    Visit(A.Value);
    Unindent;
end;

function TAstDumper.VisitMacroDefinition(const Node: IAstNode): TVoid;
var
    M: IMacroDefinitionNode;
begin
    M := Node.AsMacroDefinition;
    Log('MacroDefinition', Node);
    Indent;
    Log('Name:');
    Visit(M.Name);
    Log('Parameters:');
    Visit(M.Parameters);
    Log('Body:');
    Visit(M.Body);
    Unindent;
end;

function TAstDumper.VisitQuasiquote(const Node: IAstNode): TVoid;
begin
    Log('Quasiquote', Node);
    Indent;
    Visit(Node.AsQuasiquote.Expression);
    Unindent;
end;

function TAstDumper.VisitUnquote(const Node: IAstNode): TVoid;
begin
    Log('Unquote', Node);
    Indent;
    Visit(Node.AsUnquote.Expression);
    Unindent;
end;

function TAstDumper.VisitUnquoteSplicing(const Node: IAstNode): TVoid;
begin
    Log('UnquoteSplicing', Node);
    Indent;
    Visit(Node.AsUnquoteSplicing.Expression);
    Unindent;
end;

function TAstDumper.VisitIndexer(const Node: IAstNode): TVoid;
var
    I: IIndexerNode;
begin
    I := Node.AsIndexer;
    Log('Indexer', Node);
    Indent;
    Log('Base:');
    Visit(I.Base);
    Log('Index:');
    Visit(I.Index);
    Unindent;
end;

function TAstDumper.VisitMemberAccess(const Node: IAstNode): TVoid;
var
    M: IMemberAccessNode;
begin
    M := Node.AsMemberAccess;
    Log('MemberAccess', Node);
    Indent;
    Log('Base:');
    Visit(M.Base);
    Log('Member:');
    Visit(M.Member);
    Unindent;
end;

function TAstDumper.VisitRecordLiteral(const Node: IAstNode): TVoid;
var
    R: IRecordLiteralNode;
begin
    R := Node.AsRecordLiteral;
    LogFmt('RecordLiteral (%d fields)', [R.Fields.Count], Node);
    Indent;
    Visit(R.Fields);
    Unindent;
end;

function TAstDumper.VisitCreateSeries(const Node: IAstNode): TVoid;
begin
    LogFmt('CreateSeries: %s', [Node.AsCreateSeries.Definition], Node);
end;

function TAstDumper.VisitAddSeriesItem(const Node: IAstNode): TVoid;
var
    A: IAddSeriesItemNode;
begin
    A := Node.AsAddSeriesItem;
    Log('AddSeriesItem', Node);
    Indent;
    Log('Series:');
    Visit(A.Series);
    Log('Value:');
    Visit(A.Value);
    if Assigned(A.Lookback) then
    begin
        Log('Lookback:');
        Visit(A.Lookback);
    end;
    Unindent;
end;

function TAstDumper.VisitSeriesLength(const Node: IAstNode): TVoid;
begin
    Log('SeriesLength', Node);
    Indent;
    Visit(Node.AsSeriesLength.Series);
    Unindent;
end;

function TAstDumper.VisitNop(const Node: IAstNode): TVoid;
begin
    Log('Nop', Node);
end;

{ List Visitors }

function TAstDumper.VisitParameterList(const Node: IAstNode): TVoid;
begin
    for var item in Node.AsParameterList do
        Visit(item);
end;

function TAstDumper.VisitArgumentList(const Node: IAstNode): TVoid;
begin
    Indent;
    for var item in Node.AsArgumentList do
        Visit(item);
    Unindent;
end;

function TAstDumper.VisitExpressionList(const Node: IAstNode): TVoid;
begin
    for var item in Node.AsExpressionList do
        Visit(item);
end;

function TAstDumper.VisitRecordFieldList(const Node: IAstNode): TVoid;
begin
    for var item in Node.AsRecordFieldList do
        Visit(item);
end;

function TAstDumper.VisitRecordField(const Node: IAstNode): TVoid;
var
    F: IRecordFieldNode;
begin
    F := Node.AsRecordField;
    LogFmt('Field :%s', [F.Key.Value.Name]);
    Indent;
    Visit(F.Value);
    Unindent;
end;

{ Pipe Visitors }

function TAstDumper.VisitPipeInput(const Node: IAstNode): TVoid;
var
    P: IPipeInputNode;
begin
    P := Node.AsPipeInput;
    Log('PipeInput', Node);
    Indent;
    Log('Source:');
    Visit(P.StreamSource);
    Log('Selectors:');
    Visit(P.Selectors);
    Unindent;
end;

function TAstDumper.VisitPipeSelectorList(const Node: IAstNode): TVoid;
begin
    for var item in Node.AsPipeSelectorList do
        Visit(item);
end;

function TAstDumper.VisitPipeInputList(const Node: IAstNode): TVoid;
begin
    for var item in Node.AsPipeInputList do
        Visit(item);
end;

function TAstDumper.VisitPipe(const Node: IAstNode): TVoid;
var
    P: IPipeNode;
begin
    P := Node.AsPipe;
    Log('Pipe', Node);
    Indent;
    Log('Inputs:');
    Visit(P.Inputs);
    Log('Transformation:');
    Visit(P.Transformation);
    Unindent;
end;

end.
