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 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 VisitCondExpression(const Node: ICondExpressionNode): TVoid; override; // Replaced Ternary 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(' ', [staticType.ToString]) else typeStr := ' '; 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.VisitCondExpression(const Node: ICondExpressionNode): TVoid; var i: Integer; begin LogFmt('CondExpression (%d pairs)', [Length(Node.Pairs)], Node); Indent; for i := 0 to High(Node.Pairs) do begin LogFmt('Pair %d:', [i]); Indent; Log('Condition:'); Node.Pairs[i].Condition.Accept(Self); Log('Branch:'); Node.Pairs[i].Branch.Accept(Self); Unindent; end; Log('Else:'); Node.ElseBranch.Accept(Self); Unindent; end; function TAstDumper.VisitLambdaExpression(const Node: ILambdaExpressionNode): TVoid; var upvalueAddr: TResolvedAddress; symbols: TArray; 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(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; 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(' ', [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.