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; function VisitTuple(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 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.SetupHandlers; begin Register(akConstant, VisitConstant); Register(akIdentifier, VisitIdentifier); Register(akKeyword, VisitKeyword); Register(akTuple, VisitTuple); 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(akPipe, VisitPipe); end; procedure TAstDumper.Execute(const RootNode: IAstNode); begin if Assigned(RootNode) then Visit(RootNode); 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 staticType: IStaticType; begin var typeStr := ''; if Assigned(Node) and Node.IsTyped then begin staticType := Node.AsTypedNode.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); 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 LogFmt('Identifier: %s -> %s', [I.Name, FormatAddress(I.Address)], Node) else LogFmt('Identifier: %s (unbound)', [I.Name], Node); 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; symbols: TArray; 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(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 var addr in E.Upvalues do Log(FormatAddress(addr)); Unindent; end; Log('Body:'); Visit(E.Body); Unindent; end; function TAstDumper.VisitFunctionCall(const Node: IAstNode): TVoid; var C: IFunctionCallNode; argTypes: TArray; 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); 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.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; 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; 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.VisitPipe(const Node: IAstNode): TVoid; var P: IPipeNode; begin P := Node.AsPipe; Log('Pipe', Node); Indent; Log('Inputs (Tuple of Tuples):'); Visit(P.Inputs); Log('Transformation:'); Visit(P.Transformation); Unindent; end; end.