Files
MycLib/Src/AST/Myc.Ast.Script.Print.pas
2026-01-13 23:17:32 +01:00

482 lines
12 KiB
ObjectPascal

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.