unit Myc.Ast.Script.Print; interface uses System.SysUtils, System.Classes, Myc.Data.Value, Myc.Data.Scalar, Myc.Ast.Nodes, Myc.Ast.Visitor, Myc.Ast; type TPrettyPrintVisitor = class(TAstVisitor) strict private FBuilder: TStringBuilder; FIndentLevel: Integer; procedure Indent; procedure Unindent; procedure Append(const S: string); procedure NewLine; // Handlers now strictly accept IAstNode function VisitConstant(const N: IAstNode): TVoid; function VisitIdentifier(const N: IAstNode): TVoid; function VisitKeyword(const N: IAstNode): TVoid; // Tuple handles generic lists [ ... ] function VisitTuple(const N: IAstNode): TVoid; // Single Element function VisitRecordField(const N: IAstNode): TVoid; function VisitIfExpression(const N: IAstNode): TVoid; function VisitCondExpression(const N: IAstNode): TVoid; function VisitLambdaExpression(const N: IAstNode): TVoid; function VisitMacroDefinition(const N: IAstNode): TVoid; function VisitQuasiquote(const N: IAstNode): TVoid; function VisitUnquote(const N: IAstNode): TVoid; function VisitUnquoteSplicing(const N: IAstNode): TVoid; function VisitFunctionCall(const N: IAstNode): TVoid; function VisitMacroExpansionNode(const N: IAstNode): TVoid; function VisitBlockExpression(const N: IAstNode): TVoid; function VisitVariableDeclaration(const N: IAstNode): TVoid; function VisitAssignment(const N: IAstNode): TVoid; function VisitIndexer(const N: IAstNode): TVoid; function VisitMemberAccess(const N: IAstNode): TVoid; function VisitRecordLiteral(const N: IAstNode): TVoid; function VisitCreateSeries(const N: IAstNode): TVoid; function VisitAddSeriesItem(const N: IAstNode): TVoid; function VisitRecurNode(const N: IAstNode): TVoid; function VisitNop(const N: IAstNode): TVoid; function VisitPipe(const N: IAstNode): TVoid; protected procedure SetupHandlers; override; public constructor Create; destructor Destroy; override; function GetResult: string; procedure Execute(const RootNode: IAstNode); end; implementation { TPrettyPrintVisitor } constructor TPrettyPrintVisitor.Create; begin inherited Create; FBuilder := TStringBuilder.Create; FIndentLevel := 0; end; destructor TPrettyPrintVisitor.Destroy; begin FBuilder.Free; inherited; end; procedure TPrettyPrintVisitor.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(akRecur, VisitRecurNode); Register(akNop, VisitNop); Register(akPipe, VisitPipe); end; function TPrettyPrintVisitor.GetResult: string; begin Result := FBuilder.ToString; end; procedure TPrettyPrintVisitor.Execute(const RootNode: IAstNode); begin if Assigned(RootNode) then Visit(RootNode); end; procedure TPrettyPrintVisitor.Indent; begin Inc(FIndentLevel, 2); end; procedure TPrettyPrintVisitor.Unindent; begin Dec(FIndentLevel, 2); end; procedure TPrettyPrintVisitor.Append(const S: string); begin FBuilder.Append(S); end; procedure TPrettyPrintVisitor.NewLine; begin FBuilder.AppendLine; FBuilder.Append(''.PadLeft(FIndentLevel)); end; // --- Implementations --- function TPrettyPrintVisitor.VisitPipe(const N: IAstNode): TVoid; var P: IPipeNode; begin P := N.AsPipe; Append('(pipe '); // Inputs is a Tuple containing Tuples. // VisitTuple will automatically print [ ... ] for the outer and inner lists. Visit(P.Inputs); Indent; NewLine; Visit(P.Transformation); Unindent; NewLine; Append(')'); end; function TPrettyPrintVisitor.VisitConstant(const N: IAstNode): TVoid; var C: IConstantNode; begin C := N.AsConstant; if C.Value.Kind = vkText then Append('"' + C.Value.AsText + '"') else Append(C.Value.ToString); end; function TPrettyPrintVisitor.VisitIdentifier(const N: IAstNode): TVoid; begin Append(N.AsIdentifier.Name); end; function TPrettyPrintVisitor.VisitKeyword(const N: IAstNode): TVoid; begin Append(':' + N.AsKeyword.Value.Name); end; function TPrettyPrintVisitor.VisitNop(const N: IAstNode): TVoid; begin Append('...'); end; function TPrettyPrintVisitor.VisitTuple(const N: IAstNode): TVoid; var T: ITupleNode; i: Integer; begin T := N.AsTuple; var elements := T.Elements; Append('['); for i := 0 to High(elements) do begin if i > 0 then Append(' '); Visit(elements[i]); end; Append(']'); end; function TPrettyPrintVisitor.VisitRecordField(const N: IAstNode): TVoid; var F: IRecordFieldNode; begin F := N.AsRecordField; Visit(F.Key); Append(' '); Visit(F.Value); end; function TPrettyPrintVisitor.VisitIfExpression(const N: IAstNode): TVoid; var E: IIfExpressionNode; begin E := N.AsIfExpression; Append('(if '); Visit(E.Condition); Indent; NewLine; Visit(E.ThenBranch); if Assigned(E.ElseBranch) then begin NewLine; Visit(E.ElseBranch); end; Unindent; NewLine; Append(')'); end; function TPrettyPrintVisitor.VisitCondExpression(const N: IAstNode): TVoid; var E: ICondExpressionNode; begin E := N.AsCondExpression; Append('(?'); Indent; for var pair in E.Pairs do begin NewLine; Visit(pair.Condition); Append(' '); Visit(pair.Branch); end; NewLine; Visit(E.ElseBranch); Unindent; NewLine; Append(')'); end; function TPrettyPrintVisitor.VisitLambdaExpression(const N: IAstNode): TVoid; var E: ILambdaExpressionNode; begin E := N.AsLambdaExpression; Append('(fn '); // Parameters is ITupleNode -> prints [ ... ] automatically Visit(E.Parameters); Indent; NewLine; Visit(E.Body); Unindent; NewLine; Append(')'); end; function TPrettyPrintVisitor.VisitMacroDefinition(const N: IAstNode): TVoid; var M: IMacroDefinitionNode; begin M := N.AsMacroDefinition; Append('(defmacro '); Visit(M.Name); Append(' '); // Parameters is ITupleNode -> prints [ ... ] automatically Visit(M.Parameters); Indent; NewLine; Visit(M.Body); Unindent; NewLine; Append(')'); end; function TPrettyPrintVisitor.VisitQuasiquote(const N: IAstNode): TVoid; begin Append('`'); Visit(N.AsQuasiquote.Expression); end; function TPrettyPrintVisitor.VisitUnquote(const N: IAstNode): TVoid; begin Append('~'); Visit(N.AsUnquote.Expression); end; function TPrettyPrintVisitor.VisitUnquoteSplicing(const N: IAstNode): TVoid; begin Append('~@'); Visit(N.AsUnquoteSplicing.Expression); end; function TPrettyPrintVisitor.VisitFunctionCall(const N: IAstNode): TVoid; var C: IFunctionCallNode; i: Integer; begin C := N.AsFunctionCall; var args := C.Arguments.Elements; // Special case for 'quote' if (C.Callee.Kind = akIdentifier) and (C.Callee.AsIdentifier.Name = 'quote') and (Length(args) = 1) then begin Append(''''); Visit(args[0]); exit; end; Append('('); Visit(C.Callee); // Manually iterate arguments tuple to avoid printing [...] inside () for i := 0 to High(args) do begin Append(' '); Visit(args[i]); end; Append(')'); end; function TPrettyPrintVisitor.VisitMacroExpansionNode(const N: IAstNode): TVoid; begin Visit(N.AsMacroExpansion.CallNode); end; function TPrettyPrintVisitor.VisitRecurNode(const N: IAstNode): TVoid; var R: IRecurNode; i: Integer; begin R := N.AsRecur; var args := R.Arguments.Elements; Append('(recur'); // Manually iterate arguments for i := 0 to High(args) do begin Append(' '); Visit(args[i]); end; Append(')'); end; function TPrettyPrintVisitor.VisitBlockExpression(const N: IAstNode): TVoid; var B: IBlockExpressionNode; i: Integer; begin B := N.AsBlockExpression; var exprs := B.Expressions.Elements; Append('(do'); Indent; // Manually iterate expressions tuple for newlines for i := 0 to High(exprs) do begin NewLine; Visit(exprs[i]); end; Unindent; NewLine; Append(')'); end; function TPrettyPrintVisitor.VisitVariableDeclaration(const N: IAstNode): TVoid; var V: IVariableDeclarationNode; begin V := N.AsVariableDeclaration; Append('(def '); Visit(V.Target); if Assigned(V.Initializer) then begin Append(' '); Visit(V.Initializer); end; Append(')'); end; function TPrettyPrintVisitor.VisitAssignment(const N: IAstNode): TVoid; var A: IAssignmentNode; begin A := N.AsAssignment; Append('(assign '); Visit(A.Target); Append(' '); Visit(A.Value); Append(')'); end; function TPrettyPrintVisitor.VisitIndexer(const N: IAstNode): TVoid; var I: IIndexerNode; begin I := N.AsIndexer; Append('(get '); Visit(I.Base); Append(' '); Visit(I.Index); Append(')'); end; function TPrettyPrintVisitor.VisitMemberAccess(const N: IAstNode): TVoid; var M: IMemberAccessNode; begin M := N.AsMemberAccess; Append('(.'); Visit(M.Member); Append(' '); Visit(M.Base); Append(')'); end; function TPrettyPrintVisitor.VisitRecordLiteral(const N: IAstNode): TVoid; var R: IRecordLiteralNode; i: Integer; begin R := N.AsRecordLiteral; var fields := R.Fields.Elements; if Length(fields) = 0 then begin Append('{}'); exit; end; Append('{'); Indent; for i := 0 to High(fields) do begin NewLine; Visit(fields[i]); end; Unindent; NewLine; Append('}'); end; function TPrettyPrintVisitor.VisitCreateSeries(const N: IAstNode): TVoid; begin Append('(new-series '); Visit(N.AsCreateSeries.DefinitionNode); Append(')'); end; function TPrettyPrintVisitor.VisitAddSeriesItem(const N: IAstNode): TVoid; var A: IAddSeriesItemNode; begin A := N.AsAddSeriesItem; Append('(add-item '); Visit(A.Series); Append(' '); Visit(A.Value); if Assigned(A.Lookback) then begin Append(' '); Visit(A.Lookback); end; Append(')'); end; end.