Files
MycLib/Src/AST/Myc.Ast.Dumper.pas
T
Michael Schimmel 242ec9a56e AST list refactoring
2026-01-04 22:41:44 +01:00

620 lines
16 KiB
ObjectPascal

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 VisitRecordField(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;
// Unified List Visitor
function VisitTuple(const Node: IAstNode): TVoid;
// Pipe Visitors (Specific lists kept for now)
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);
// Unified List
Register(akTuple, VisitTuple);
// Element
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); // Visits Tuple
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;
args: ITupleNode;
begin
C := Node.AsFunctionCall;
args := C.Arguments;
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, args.Count);
for i := 0 to args.Count - 1 do
begin
if args.Items[i].IsTyped then
argTypes[i] := args.Items[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):', [args.Count]);
Visit(args); // Visits Tuple
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;
// --- Unified List Visitor ---
function TAstDumper.VisitTuple(const Node: IAstNode): TVoid;
var
T: ITupleNode;
i: Integer;
begin
T := Node.AsTuple;
LogFmt('Tuple (%d elements)', [T.Count], Node);
Indent;
for i := 0 to T.Count - 1 do
begin
LogFmt('Item %d:', [i]);
Indent;
Visit(T.Items[i]);
Unindent;
end;
Unindent;
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.