# The `PrettyPrinterVisitor` is used to visit a node and pretty print it.
#
# The main method here is `visit` that performs the pretty printing of a `ANode`.
#
# Because some tokens like `TComment` are not stored in the AST,
# we visit the AST like traditionnal visitor and also maintain a
# pointer to the `current_token` (actually the next one to be printed).
#
# Visited productions are in charges to move the token pointer using methods such as:
#
# * `consume`: print `current_token` and move to the next one
# * `skip`: move to the next token without printing the current one
# * `skip_to`: move to a specified token skipping all the tokens before
# * `catch_up`: consume all the tokens between `current_token` and a specified token
# * `finish_line`: consume all the tokens between `current_token` and the end of line
class PrettyPrinterVisitor
# Pretty print `n`.
fun pretty(n: ANode): Template do
clear
n.parentize_tokens
if n isa Prod then
current_token = n.first_token
visit n
else if n isa Token then
var p = n.parent
while p != null and not p isa Prod do
p = p.parent
end
current_token = p.first_token
visit p
end
return tpl.as(not null)
end
# Pretty print the whole `nmodule` with comments before and after.
fun pretty_nmodule(nmodule: AModule): Template do
clear
nmodule.parentize_tokens
current_token = nmodule.location.file.first_token
visit nmodule
catch_up nmodule.location.file.last_token
if skip_empty then tpl.add "\n"
return tpl.as(not null)
end
# Prepare `self` for a new visit.
private fun clear do
tpl = new Template
current_token = null
indent = 0
current_length = 0
previous_length = 0
wait_addn = 0
end
# Visit `n` if not null.
fun visit(n: nullable ANode) do
if n == null then return
n.accept_pretty_printer self
end
# Visit a list of arguments `ANode` with optional parentheses
fun visit_args(n: nullable ANodes[ANode]) do
if n == null or n.is_empty then return
if current_token isa TOpar then
consume "("
else
adds
end
visit_list n
if current_token isa TCpar then consume ")"
end
# Visit a list of `ANode`.
fun visit_list(n: nullable ANodes[ANode]) do
if n == null then return
n.accept_pretty_printer self
end
# Is the node inlinable and can fit on the line.
fun can_inline(n: nullable ANode): Bool do
if n == null then return true
if no_inline and n.location.line_start != n.location.line_end then return false
if n.must_be_inline then return true
if n.must_be_block then return false
# check length
if max_size > 0 and n.collect_length + current_length > max_size then return false
# check block is inlinable
return n.is_inlinable
end
# Collect all `TComment` between `from` and `to`.
fun collect_comments(from: nullable ANode, to: nullable ANode): Array[TComment] do
var res = new Array[TComment]
if from isa Prod then from = from.first_token
if to isa Prod then to = to.first_token
if from == null or to == null then return res
while from != to do
if from isa TComment then res.add from
from = from.as(Token).next_token
end
return res
end
# Token under cursor.
#
# This is the next token to visit.
var current_token: nullable Token = null
# Skip the `current_token`.
fun skip do current_token = current_token.next_token
# Skip `current_token` until the end of line.
fun skip_line do current_token = current_token.last_real_token_in_line
# Skip `current_token` until `target` is reached.
fun skip_to(target: nullable Token) do
if target == null then return
while current_token != null and current_token != target do skip
if current_token == null then
target.debug("Looked for, but not found :(")
abort
end
end
# Consume comments and end of lines if any
fun consume_comments do
while current_token isa TEol or current_token isa TComment do visit current_token
end
# Visit `current_token`.
fun consume(token: String) do
consume_comments
if current_token.text == token then else current_token.debug("Got `{current_token.text}`; expected `{token}`.")
visit current_token
end
# Is there token to visit between `current_token` and `target`?
fun need_catch_up(target: nullable Token): Bool do
if target == null then return false
return current_token != target
end
# Visit all tokens between `current_token` and `target`.
fun catch_up(target: nullable ANode) do
if target == null then return
if current_token == null then return
var token: Token
if target isa Token then
token = target
else if target isa Prod then
token = target.first_token.as(not null)
else
abort
end
if current_token.location > token.location then return
while current_token != token do visit current_token
end
# Visit all tokens between `current_token` and the end of line.
fun finish_line do
if current_token isa TComment then
adds
visit current_token
end
while current_token isa TEol do visit(current_token)
end
# The template under construction.
private var tpl: nullable Template = null
# Current indent level.
var indent = 0
# Size of a tabulation in spaces.
var tab_size = 8
# Max line size.
# 0 (or negative) to disable.
var max_size = 80 is writable
# Length of the current line.
var current_length = 0
# Length of the previous line.
var previous_length = 0
# Is the last line a blank line?
fun last_line_is_blank: Bool do return previous_length == 0
# Add `t` to current template.
fun add(t: String) do
if t.is_empty then return
while wait_addn > 0 do
tpl.add "\n"
wait_addn -= 1
end
tpl.add t
current_length += t.length
end
# Add a `'\n'`.
fun addn do
if current_length == 0 and last_line_is_blank then return
previous_length = current_length
current_length = 0
if skip_empty then wait_addn += 1
end
# Perform `addn` even if not `skip_empty`.
fun forcen do
if current_length == 0 and last_line_is_blank then return
previous_length = current_length
current_length = 0
wait_addn += 1
end
# End of line chars are stored until another char is added.
# This avoid empty files with only a '`\n`'.
private var wait_addn = 0
# Add `'\t' * indent`.
fun addt do add "\t" * indent
# Add a space.
fun adds do add " "
# Visit explicit receiver, implicit self will be ignored.
fun visit_recv(n_expr: AExpr) do
if not n_expr isa AImplicitSelfExpr then
visit n_expr
consume "."
end
end
# Do we break string literals that are too long?
var break_strings = false is public writable
# Do we force `do` to be on the same line as the method signature?
var inline_do = false is public writable
# Do we force the deletion of empty lines?
var skip_empty = false is public writable
# Disable automatic inlining.
var no_inline = false is writable
end
src/pretty.nit:37,1--291,3