Files
MycLib/Src/AST/Myc.Ast.Dumper.pas
T
2025-12-04 11:28:40 +01:00

546 lines
15 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;
protected
// Override abstract PROCEDURES from TAstVisitor
function VisitConstant(const Node: IConstantNode): TVoid; override;
function VisitIdentifier(const Node: IIdentifierNode): TVoid; override;
function VisitKeyword(const Node: IKeywordNode): TVoid; override;
function VisitIfExpression(const Node: IIfExpressionNode): TVoid; override;
function VisitTernaryExpression(const Node: ITernaryExpressionNode): TVoid; override;
function VisitLambdaExpression(const Node: ILambdaExpressionNode): TVoid; override;
function VisitFunctionCall(const Node: IFunctionCallNode): TVoid; override;
function VisitMacroExpansionNode(const Node: IMacroExpansionNode): TVoid; override;
function VisitRecurNode(const Node: IRecurNode): TVoid; override;
function VisitBlockExpression(const Node: IBlockExpressionNode): TVoid; override;
function VisitVariableDeclaration(const Node: IVariableDeclarationNode): TVoid; override;
function VisitAssignment(const Node: IAssignmentNode): TVoid; override;
function VisitMacroDefinition(const Node: IMacroDefinitionNode): TVoid; override;
function VisitQuasiquote(const Node: IQuasiquoteNode): TVoid; override;
function VisitUnquote(const Node: IUnquoteNode): TVoid; override;
function VisitUnquoteSplicing(const Node: IUnquoteSplicingNode): TVoid; override;
function VisitIndexer(const Node: IIndexerNode): TVoid; override;
function VisitMemberAccess(const Node: IMemberAccessNode): TVoid; override;
function VisitRecordLiteral(const Node: IRecordLiteralNode): TVoid; override;
function VisitCreateSeries(const Node: ICreateSeriesNode): TVoid; override;
function VisitAddSeriesItem(const Node: IAddSeriesItemNode): TVoid; override;
function VisitSeriesLength(const Node: ISeriesLengthNode): TVoid; override;
function VisitNop(const Node: INopNode): TVoid; override;
// List Visitors are handled implicitly by parent node iteration in Dumper,
// but we implement them empty/default to satisfy the abstract base class if called directly.
function VisitParameterList(const Node: IParameterList): TVoid; override;
function VisitArgumentList(const Node: IArgumentList): TVoid; override;
function VisitExpressionList(const Node: IExpressionList): TVoid; override;
function VisitRecordFieldList(const Node: IRecordFieldList): TVoid; override;
function VisitRecordField(const Node: IRecordFieldNode): TVoid; 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
exit;
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.Execute(const RootNode: IAstNode);
begin
if Assigned(RootNode) then
RootNode.Accept(Self);
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 = nil);
var
typeStr: string;
typedNode: IAstTypedNode;
staticType: IStaticType;
begin
typeStr := '';
if Assigned(Node) and Node.IsTyped then
begin
typedNode := Node.AsTypedNode;
staticType := typedNode.StaticType;
if Assigned(staticType) then
typeStr := Format(' <Type: %s>', [staticType.ToString])
else
typeStr := ' <Type: nil>';
end;
FOutput.Add(StringOfChar(' ', FIndent) + Text + typeStr);
end;
procedure TAstDumper.LogFmt(const Fmt: string; const Args: array of const; const Node: IAstNode = nil);
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: IConstantNode): TVoid;
begin
LogFmt('Constant: %s', [Node.Value.ToString], Node);
end;
function TAstDumper.VisitIdentifier(const Node: IIdentifierNode): TVoid;
var
adr: TResolvedAddress;
begin
adr := Node.Address;
if adr.Kind <> akUnresolved then
LogFmt('Identifier: %s -> %s', [Node.Name, FormatAddress(adr)], Node)
else
LogFmt('Identifier: %s (unbound)', [Node.Name], Node);
end;
function TAstDumper.VisitKeyword(const Node: IKeywordNode): TVoid;
begin
LogFmt('Keyword: :%s', [Node.Value.Name], Node);
end;
function TAstDumper.VisitIfExpression(const Node: IIfExpressionNode): TVoid;
begin
Log('IfExpression', Node);
Indent;
Log('Condition:');
Node.Condition.Accept(Self);
Log('Then:');
Node.ThenBranch.Accept(Self);
if Assigned(Node.ElseBranch) then
begin
Log('Else:');
Node.ElseBranch.Accept(Self);
end;
Unindent;
end;
function TAstDumper.VisitTernaryExpression(const Node: ITernaryExpressionNode): TVoid;
begin
Log('TernaryExpression', Node);
Indent;
Log('Condition:');
Node.Condition.Accept(Self);
Log('Then:');
Node.ThenBranch.Accept(Self);
Log('Else:');
Node.ElseBranch.Accept(Self);
Unindent;
end;
function TAstDumper.VisitLambdaExpression(const Node: ILambdaExpressionNode): TVoid;
var
upvalueAddr: TResolvedAddress;
symbols: TArray<string>;
layout: IScopeLayout;
slot: Integer;
typ: IStaticType;
begin
LogFmt(
'LambdaExpression (HasNested: %s, IsPure: %s)',
[Node.HasNestedLambdas.ToString(TUseBoolStrs.True), BoolToStr(Node.IsPure, True)],
Node
);
Indent;
// 1. Layout & Symbols
if Assigned(Node.Layout) then
begin
LogFmt('Scope: Layout Slots=%d', [Node.Layout.SlotCount]);
if Assigned(Node.Descriptor) then
begin
Log('Symbol Table:');
Indent;
layout := Node.Layout;
symbols := layout.GetSymbols;
TArray.Sort<string>(symbols);
for var name in symbols do
begin
slot := layout.FindSlot(name);
typ := Node.Descriptor.GetSymbolType(slot);
LogFmt('"%s" -> Slot %d (Type: %s)', [name, slot, typ.ToString]);
end;
Unindent;
end;
end
else
Log('Scope: No Layout (Raw)');
// 3. Parameters
Log('Parameters:');
Indent;
// Iterate over IParameterList
for var param in Node.Parameters do
param.Accept(Self);
Unindent;
// 4. Upvalues
if Length(Node.Upvalues) > 0 then
begin
LogFmt('Captured Upvalues (%d):', [Length(Node.Upvalues)]);
Indent;
for upvalueAddr in Node.Upvalues do
Log(FormatAddress(upvalueAddr));
Unindent;
end;
// 5. Body
Log('Body:');
Node.Body.Accept(Self);
Unindent;
end;
function TAstDumper.VisitFunctionCall(const Node: IFunctionCallNode): TVoid;
var
arg: IAstNode;
staticStatus: string;
sigStr: string;
argTypes: TArray<string>;
i: Integer;
begin
sigStr := '';
// Note: Node.Arguments is IArgumentList now.
if Assigned(Node.StaticTarget) then
begin
staticStatus := 'Assigned';
SetLength(argTypes, Node.Arguments.Count);
for i := 0 to Node.Arguments.Count - 1 do
begin
if Node.Arguments[i].IsTyped then
argTypes[i] := Node.Arguments[i].AsTypedNode.StaticType.ToString
else
argTypes[i] := 'Untyped';
end;
sigStr := Format(' <ResolvedSig: Method(%s): %s>', [string.Join(', ', argTypes), Node.StaticType.ToString]);
end
else
staticStatus := 'nil';
LogFmt(
'FunctionCall (IsTailCall: %s, StaticTarget: %s%s, IsTargetPure: %s)',
[Node.IsTailCall.ToString(TUseBoolStrs.True), staticStatus, sigStr, BoolToStr(Node.IsTargetPure, True)],
Node
);
Indent;
Log('Callee:');
Node.Callee.Accept(Self);
LogFmt('Arguments (%d):', [Node.Arguments.Count]);
Indent;
// Iterate over IArgumentList
for arg in Node.Arguments do
arg.Accept(Self);
Unindent;
Unindent;
end;
function TAstDumper.VisitMacroExpansionNode(const Node: IMacroExpansionNode): TVoid;
var
arg: IAstNode;
begin
Log('MacroExpansion', Node);
Indent;
Log('Original Call:');
Indent;
Log('Callee:');
Node.CallNode.Callee.Accept(Self);
LogFmt('Arguments (%d):', [Node.CallNode.Arguments.Count]);
Indent;
for arg in Node.CallNode.Arguments do
arg.Accept(Self);
Unindent;
Unindent;
Log('Expanded Body:');
Indent;
Node.ExpandedBody.Accept(Self);
Unindent;
Unindent;
end;
function TAstDumper.VisitRecurNode(const Node: IRecurNode): TVoid;
var
arg: IAstNode;
begin
Log('Recur', Node);
Indent;
LogFmt('Arguments (%d):', [Node.Arguments.Count]);
Indent;
for arg in Node.Arguments do
arg.Accept(Self);
Unindent;
Unindent;
end;
function TAstDumper.VisitBlockExpression(const Node: IBlockExpressionNode): TVoid;
var
expr: IAstNode;
begin
Log('BlockExpression', Node);
Indent;
for expr in Node.Expressions do
expr.Accept(Self);
Unindent;
end;
function TAstDumper.VisitVariableDeclaration(const Node: IVariableDeclarationNode): TVoid;
begin
LogFmt('VariableDeclaration (IsBoxed: %s)', [Node.IsBoxed.ToString(TUseBoolStrs.True)], Node);
Indent;
Node.Target.Accept(Self);
if Assigned(Node.Initializer) then
begin
Log('Initializer:');
Node.Initializer.Accept(Self);
end;
Unindent;
end;
function TAstDumper.VisitAssignment(const Node: IAssignmentNode): TVoid;
begin
Log('Assignment', Node);
Indent;
Node.Target.Accept(Self);
Log('Value:');
Node.Value.Accept(Self);
Unindent;
end;
function TAstDumper.VisitMacroDefinition(const Node: IMacroDefinitionNode): TVoid;
begin
Log('MacroDefinition', Node);
Indent;
Log('Name:');
Indent;
Node.Name.Accept(Self);
Unindent;
Log('Parameters:');
Indent;
for var param in Node.Parameters do
param.Accept(Self);
Unindent;
Log('Body:');
Indent;
Node.Body.Accept(Self);
Unindent;
Unindent;
end;
function TAstDumper.VisitQuasiquote(const Node: IQuasiquoteNode): TVoid;
begin
Log('Quasiquote', Node);
Indent;
Node.Expression.Accept(Self);
Unindent;
end;
function TAstDumper.VisitUnquote(const Node: IUnquoteNode): TVoid;
begin
Log('Unquote', Node);
Indent;
Node.Expression.Accept(Self);
Unindent;
end;
function TAstDumper.VisitUnquoteSplicing(const Node: IUnquoteSplicingNode): TVoid;
begin
Log('UnquoteSplicing', Node);
Indent;
Node.Expression.Accept(Self);
Unindent;
end;
function TAstDumper.VisitIndexer(const Node: IIndexerNode): TVoid;
begin
Log('Indexer', Node);
Indent;
Log('Base:');
Node.Base.Accept(Self);
Log('Index:');
Node.Index.Accept(Self);
Unindent;
end;
function TAstDumper.VisitMemberAccess(const Node: IMemberAccessNode): TVoid;
begin
Log('MemberAccess', Node);
Indent;
Log('Base:');
Node.Base.Accept(Self);
Log('Member:');
Node.Member.Accept(Self);
Unindent;
end;
function TAstDumper.VisitRecordLiteral(const Node: IRecordLiteralNode): TVoid;
var
field: IRecordFieldNode;
begin
LogFmt('RecordLiteral (%d fields)', [Node.Fields.Count], Node);
Indent;
// Iterate over IRecordFieldList
for field in Node.Fields do
begin
LogFmt('Field :%s', [field.Key.Value.Name]);
Indent;
field.Value.Accept(Self);
Unindent;
end;
Unindent;
end;
function TAstDumper.VisitCreateSeries(const Node: ICreateSeriesNode): TVoid;
begin
LogFmt('CreateSeries: %s', [Node.Definition], Node);
end;
function TAstDumper.VisitAddSeriesItem(const Node: IAddSeriesItemNode): TVoid;
begin
Log('AddSeriesItem', Node);
Indent;
Log('Series:');
Node.Series.Accept(Self);
Log('Value:');
Node.Value.Accept(Self);
if Assigned(Node.Lookback) then
begin
Log('Lookback:');
Node.Lookback.Accept(Self);
end;
Unindent;
end;
function TAstDumper.VisitSeriesLength(const Node: ISeriesLengthNode): TVoid;
begin
Log('SeriesLength', Node);
Indent;
Log('Series:');
Node.Series.Accept(Self);
Unindent;
end;
function TAstDumper.VisitNop(const Node: INopNode): TVoid;
begin
Log('Nop', Node);
end;
// --- List Visitors Implementation ---
function TAstDumper.VisitParameterList(const Node: IParameterList): TVoid;
begin
// Usually handled by Parent Node iteration (Lambda),
// but if visited directly:
for var item in Node do
item.Accept(Self);
end;
function TAstDumper.VisitArgumentList(const Node: IArgumentList): TVoid;
begin
for var item in Node do
item.Accept(Self);
end;
function TAstDumper.VisitExpressionList(const Node: IExpressionList): TVoid;
begin
for var item in Node do
item.Accept(Self);
end;
function TAstDumper.VisitRecordFieldList(const Node: IRecordFieldList): TVoid;
begin
for var item in Node do
item.Accept(Self);
end;
function TAstDumper.VisitRecordField(const Node: IRecordFieldNode): TVoid;
begin
// If visited directly (e.g. inside a list)
LogFmt('Field :%s', [Node.Key.Value.Name]);
Indent;
Node.Value.Accept(Self);
Unindent;
end;
end.