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 procedure VisitConstant(const Node: IConstantNode); override; procedure VisitIdentifier(const Node: IIdentifierNode); override; procedure VisitKeyword(const Node: IKeywordNode); override; procedure VisitIfExpression(const Node: IIfExpressionNode); override; procedure VisitTernaryExpression(const Node: ITernaryExpressionNode); override; procedure VisitLambdaExpression(const Node: ILambdaExpressionNode); override; procedure VisitFunctionCall(const Node: IFunctionCallNode); override; procedure VisitMacroExpansionNode(const Node: IMacroExpansionNode); override; procedure VisitRecurNode(const Node: IRecurNode); override; procedure VisitBlockExpression(const Node: IBlockExpressionNode); override; procedure VisitVariableDeclaration(const Node: IVariableDeclarationNode); override; procedure VisitAssignment(const Node: IAssignmentNode); override; procedure VisitMacroDefinition(const Node: IMacroDefinitionNode); override; procedure VisitQuasiquote(const Node: IQuasiquoteNode); override; procedure VisitUnquote(const Node: IUnquoteNode); override; procedure VisitUnquoteSplicing(const Node: IUnquoteSplicingNode); override; procedure VisitIndexer(const Node: IIndexerNode); override; procedure VisitMemberAccess(const Node: IMemberAccessNode); override; procedure VisitRecordLiteral(const Node: IRecordLiteralNode); override; procedure VisitCreateSeries(const Node: ICreateSeriesNode); override; procedure VisitAddSeriesItem(const Node: IAddSeriesItemNode); override; procedure VisitSeriesLength(const Node: ISeriesLengthNode); override; procedure VisitNop(const Node: INopNode); 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. procedure VisitParameterList(const Node: IParameterList); override; procedure VisitArgumentList(const Node: IArgumentList); override; procedure VisitExpressionList(const Node: IExpressionList); override; procedure VisitRecordFieldList(const Node: IRecordFieldList); override; procedure VisitRecordField(const Node: IRecordFieldNode); 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; procedure TAstDumper.VisitConstant(const Node: IConstantNode); begin LogFmt('Constant: %s', [Node.Value.ToString], Node); end; procedure TAstDumper.VisitIdentifier(const Node: IIdentifierNode); 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; procedure TAstDumper.VisitKeyword(const Node: IKeywordNode); begin LogFmt('Keyword: :%s', [Node.Value.Name], Node); end; procedure TAstDumper.VisitIfExpression(const Node: IIfExpressionNode); 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; procedure TAstDumper.VisitTernaryExpression(const Node: ITernaryExpressionNode); 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; procedure TAstDumper.VisitLambdaExpression(const Node: ILambdaExpressionNode); 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; procedure TAstDumper.VisitFunctionCall(const Node: IFunctionCallNode); 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; procedure TAstDumper.VisitMacroExpansionNode(const Node: IMacroExpansionNode); 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; procedure TAstDumper.VisitRecurNode(const Node: IRecurNode); 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; procedure TAstDumper.VisitBlockExpression(const Node: IBlockExpressionNode); var expr: IAstNode; begin Log('BlockExpression', Node); Indent; for expr in Node.Expressions do expr.Accept(Self); Unindent; end; procedure TAstDumper.VisitVariableDeclaration(const Node: IVariableDeclarationNode); 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; procedure TAstDumper.VisitAssignment(const Node: IAssignmentNode); begin Log('Assignment', Node); Indent; Node.Target.Accept(Self); Log('Value:'); Node.Value.Accept(Self); Unindent; end; procedure TAstDumper.VisitMacroDefinition(const Node: IMacroDefinitionNode); 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; procedure TAstDumper.VisitQuasiquote(const Node: IQuasiquoteNode); begin Log('Quasiquote', Node); Indent; Node.Expression.Accept(Self); Unindent; end; procedure TAstDumper.VisitUnquote(const Node: IUnquoteNode); begin Log('Unquote', Node); Indent; Node.Expression.Accept(Self); Unindent; end; procedure TAstDumper.VisitUnquoteSplicing(const Node: IUnquoteSplicingNode); begin Log('UnquoteSplicing', Node); Indent; Node.Expression.Accept(Self); Unindent; end; procedure TAstDumper.VisitIndexer(const Node: IIndexerNode); begin Log('Indexer', Node); Indent; Log('Base:'); Node.Base.Accept(Self); Log('Index:'); Node.Index.Accept(Self); Unindent; end; procedure TAstDumper.VisitMemberAccess(const Node: IMemberAccessNode); begin Log('MemberAccess', Node); Indent; Log('Base:'); Node.Base.Accept(Self); Log('Member:'); Node.Member.Accept(Self); Unindent; end; procedure TAstDumper.VisitRecordLiteral(const Node: IRecordLiteralNode); 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; procedure TAstDumper.VisitCreateSeries(const Node: ICreateSeriesNode); begin LogFmt('CreateSeries: %s', [Node.Definition], Node); end; procedure TAstDumper.VisitAddSeriesItem(const Node: IAddSeriesItemNode); 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; procedure TAstDumper.VisitSeriesLength(const Node: ISeriesLengthNode); begin Log('SeriesLength', Node); Indent; Log('Series:'); Node.Series.Accept(Self); Unindent; end; procedure TAstDumper.VisitNop(const Node: INopNode); begin Log('Nop', Node); end; // --- List Visitors Implementation --- procedure TAstDumper.VisitParameterList(const Node: IParameterList); begin // Usually handled by Parent Node iteration (Lambda), // but if visited directly: for var item in Node do item.Accept(Self); end; procedure TAstDumper.VisitArgumentList(const Node: IArgumentList); begin for var item in Node do item.Accept(Self); end; procedure TAstDumper.VisitExpressionList(const Node: IExpressionList); begin for var item in Node do item.Accept(Self); end; procedure TAstDumper.VisitRecordFieldList(const Node: IRecordFieldList); begin for var item in Node do item.Accept(Self); end; procedure TAstDumper.VisitRecordField(const Node: IRecordFieldNode); begin // If visited directly (e.g. inside a list) LogFmt('Field :%s', [Node.Key.Value.Name]); Indent; Node.Value.Accept(Self); Unindent; end; end.