Property definitions

nitc $ PrettyPrinterVisitor :: defaultinit
# 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