# ~~~
module nitpretty
-import template
-import toolcontext
-import modelbuilder
-import astutil
-
-# 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
- 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 `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 n.must_be_inline then return true
- if n.must_be_block then return false
- # check length
- if 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 != target do skip
- end
-
- # Visit `current_token`.
- fun consume(token: String) do
- assert current_token.text == 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
- assert current_token.location <= token.location
- 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 skip
- 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.
- var max_size = 80
-
- # 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
- 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 " "
-
- fun visit_recv(n_expr: AExpr) do
- if not n_expr isa AImplicitSelfExpr then
- visit n_expr
- consume "."
- end
- end
-end
-
-# Base framework redefs
-
-redef class ANodes[E]
- private fun accept_pretty_printer(v: PrettyPrinterVisitor) do
- for e in self do
- var e_can_inline = v.can_inline(e)
-
- if e != first then
- if not e_can_inline then
- v.add ","
- v.addn
- v.addt
- v.addt
- else
- v.add ", "
- end
- end
-
- v.visit e
- end
- end
-end
-
-redef class ANode
- # Start visit of `self` using a `PrettyPrinterVisitor`
- private fun accept_pretty_printer(v: PrettyPrinterVisitor) is abstract
-
- # Collect the length (in `Char`) of the node.
- private fun collect_length: Int is abstract
-
- # Is `self` printable in one line?
- private fun is_inlinable: Bool do return true
-
- # Force `self` to be rendered as a block.
- private var force_block = false
-
- # Does `self` have to be rendered as a block?
- private fun must_be_block: Bool do return force_block
-
- # Force `self` to be rendered as a line.
- private var force_inline = false
-
- # Does `self` have be rendered as a line?
- private fun must_be_inline: Bool do
- if parent != null and parent.must_be_inline then return true
- return force_inline
- end
-
- # Does `self` was written in one line before transformation?
- private fun was_inline: Bool is abstract
-end
-
-redef class Token
- redef fun accept_pretty_printer(v) do
- v.add text.trim
- v.current_token = next_token
- end
-
- redef fun collect_length do return text.length
- redef fun is_inlinable do return true
- redef fun was_inline do return true
-end
-
-redef class Prod
- redef fun accept_pretty_printer(v) do v.visit first_token
-
- # The token where the production really start (skipping ADoc).
- private fun start_token: nullable Token do return first_token
-
- # Collect all `TComment` contained in the production
- # (between `start_token` and `end_token`).
- private fun collect_comments: Array[TComment] do
- var res = new Array[TComment]
- if start_token == null or last_token == null then return res
- var token = start_token
-
- while token != last_token do
- if token isa TComment then res.add token
- token = token.next_token
- end
-
- return res
- end
-
- redef fun collect_length do
- var res = 0
- if start_token == null or last_token == null then return res
- var token = start_token
-
- while token != last_token do
- res += token.text.length
- token = token.next_token
- end
-
- res += token.text.length
- return res
- end
-
- redef fun was_inline do
- return first_token.location.line_start == last_token.location.line_end
- end
-end
-
-# Comments
-
-redef class TComment
- redef fun accept_pretty_printer(v) do
- if is_adoc then
- v.addt
- super
- v.addn
- return
- end
-
- if is_licence then
- super
- v.addn
- if is_last_in_group then v.addn
- return
- end
-
- if is_orphan then
- v.addn
- v.addt
- super
- v.addn
- v.addn
- return
- end
-
- if is_inline then
- if next_token isa TComment and is_first_in_group then v.addn
- v.addt
- super
- v.addn
- var prev_token = self.prev_token
- if prev_token isa TComment and prev_token.is_inline and is_last_in_group then v.addn
- return
- end
-
- super
- end
-
- # Is `self` part of an `ADoc`?
- private fun is_adoc: Bool do return parent isa ADoc and parent.parent != null
-
- # Is `self` part of a licence?
- private fun is_licence: Bool do
- var prev_token = self.prev_token
-
- if prev_token == null then
- return true
- else if prev_token isa TComment then
- return prev_token.is_licence
- else
- return false
- end
- end
-
- # Is `self` starts and ends its line?
- private fun is_inline: Bool do
- return self == first_real_token_in_line and self == last_real_token_in_line
- end
-
- # Is `self` an orphan line (blank before and after)?
- private fun is_orphan: Bool do
- return prev_token isa TEol and
- (prev_token.prev_token isa TEol or prev_token.prev_token isa TComment) and
- next_token isa TEol
- end
-
- # Is `self` the first comment of a group of comment?
- private fun is_first_in_group: Bool do return not prev_token isa TComment
-
- # Is `self` the last comment of a group of comments?
- private fun is_last_in_group: Bool do return not next_token isa TComment
-end
-
-redef class ADoc
- redef fun accept_pretty_printer(v) do for comment in n_comment do v.visit comment
- redef fun is_inlinable do return n_comment.length <= 1
-end
-
-# Annotations
-
-redef class AAnnotations
- redef fun accept_pretty_printer(v) do
- v.adds
- v.consume "is"
-
- if v.can_inline(self) then
- v.adds
- for n_item in n_items do
- v.visit n_item
- if n_item != n_items.last then
- v.add ", "
- end
- end
- v.finish_line
- else if n_items.length > 1 then
- v.addn
- v.indent += 1
-
- for n_item in n_items do
- v.addt
- v.visit n_item
- v.finish_line
- v.addn
- end
-
- v.indent -= 1
- end
- if not was_inline and v.current_token isa TKwend then v.skip
- end
-
- redef fun is_inlinable do
- if not super then return false
- for annot in n_items do if not annot.is_inlinable then return false
- return true
- end
-end
-
-redef class AAnnotation
- redef fun accept_pretty_printer(v) do
- v.visit n_atid
- if not n_args.is_empty then
- if n_opar == null then
- v.adds
- else
- v.visit n_opar
- end
- v.visit_list n_args
- v.visit n_cpar
- end
- end
-end
-
-redef class ATypeExpr
- redef fun accept_pretty_printer(v) do v.visit n_type
-end
-
-# Modules
-
-redef class AModule
- redef fun accept_pretty_printer(v) do
- v.catch_up start_token
- v.visit n_moduledecl
-
- if not n_imports.is_empty then
- v.addn
-
- for n_import in n_imports do
- v.catch_up n_import
- v.visit n_import
- end
- end
-
- if not n_extern_code_blocks.is_empty then
- v.addn
-
- for n_extern_block in n_extern_code_blocks do
- v.catch_up n_extern_block
- v.visit n_extern_block
- v.addn
- if n_extern_block != n_extern_code_blocks.last then v.addn
- end
-
- if not n_classdefs.is_empty then v.addn
- end
-
- if not n_classdefs.is_empty then
- v.addn
-
- for n_classdef in n_classdefs do
- v.catch_up n_classdef
- v.visit n_classdef
- if n_classdef != n_classdefs.last then v.addn
- end
- end
-
- assert v.indent == 0
- end
-
- # Skip doc if any.
- redef fun start_token do
- if n_moduledecl != null then return n_moduledecl.first_token
- if not n_imports.is_empty then return n_imports.first.first_token
- if not n_classdefs.is_empty then return n_classdefs.first.first_token
- return first_token
- end
-
- redef fun is_inlinable do return false
-end
-
-redef class AModuledecl
- redef fun accept_pretty_printer(v) do
- v.visit n_doc
- v.visit n_kwmodule
- v.adds
- v.visit n_name
-
- if n_annotations != null then
- var annot_inline = v.can_inline(n_annotations)
- v.visit n_annotations
-
- if not annot_inline then
- if v.current_token isa TKwend then
- v.consume "end"
- v.finish_line
- else
- v.add "end"
- end
- end
- end
-
- v.finish_line
- v.addn
- end
-end
-
-redef class AModuleName
- redef fun accept_pretty_printer(v) do
- for path in n_path do
- v.visit path
- v.add "::"
- end
-
- v.visit n_id
- end
-end
-
-redef class ANoImport
- redef fun accept_pretty_printer(v) do
- v.visit n_kwimport
- v.adds
- v.visit n_kwend
- v.finish_line
- v.addn
- end
-end
-
-redef class AStdImport
- redef fun accept_pretty_printer(v) do
- if not n_visibility isa APublicVisibility then
- v.visit n_visibility
- v.adds
- end
-
- v.visit n_kwimport
- v.adds
- v.visit n_name
- v.finish_line
- v.addn
- end
-end
-
-# Classes
-
-redef class AClassdef
- redef fun accept_pretty_printer(v) do
- for n_propdef in n_propdefs do
- v.catch_up n_propdef
-
- if n_propdef.n_doc != null or not v.can_inline(n_propdef) then
- if n_propdef != n_propdefs.first then v.addn
- v.visit n_propdef
- if n_propdef != n_propdefs.last then v.addn
- else
- v.visit n_propdef
- end
- end
- end
-end
-
-redef class AStdClassdef
- redef fun accept_pretty_printer(v) do
- v.visit n_doc
- var can_inline = v.can_inline(self)
-
- if not n_visibility isa APublicVisibility then
- v.visit n_visibility
- v.adds
- end
-
- if n_kwredef != null then
- v.visit n_kwredef
- v.adds
- end
-
- v.visit n_classkind
- v.adds
- v.visit n_id
-
- if not n_formaldefs.is_empty then
- v.consume "["
- v.visit_list n_formaldefs
- v.consume "]"
- end
-
- if n_extern_code_block != null then
- v.adds
- v.visit n_extern_code_block
- end
-
- if can_inline then
- v.adds
-
- if not n_superclasses.is_empty then
- for n_superclass in n_superclasses do
- v.visit n_superclass
- v.adds
- end
- end
- else
- v.finish_line
- v.addn
- v.indent += 1
-
- for n_superclass in n_superclasses do
- v.catch_up n_superclass
- v.addt
- v.visit n_superclass
- v.finish_line
- v.addn
- end
-
- if not n_superclasses.is_empty and not n_propdefs.is_empty then
- v.addn
- end
-
- super
- v.catch_up n_kwend
- v.indent -= 1
- end
-
- v.visit n_kwend
- v.finish_line
- v.addn
- assert v.indent == 0
- end
-
- redef fun is_inlinable do
- if not super then return false
- if not n_propdefs.is_empty then return false
- if n_superclasses.length > 1 then return false
- if not collect_comments.is_empty then return false
- return true
- end
-
- redef fun start_token do
- if not n_visibility isa APublicVisibility then return n_visibility.first_token
- if n_kwredef != null then return n_kwredef
- return n_classkind.first_token
- end
-end
-
-redef class AAbstractClasskind
- redef fun accept_pretty_printer(v) do
- v.visit n_kwabstract
- v.adds
- v.visit n_kwclass
- end
-end
-
-redef class AExternClasskind
- redef fun accept_pretty_printer(v) do
- v.visit n_kwextern
- v.adds
- v.visit n_kwclass
- end
-end
-
-redef class AFormaldef
- redef fun accept_pretty_printer(v) do
- v.visit n_id
-
- if n_type != null then
- v.consume ":"
- v.adds
- v.visit n_type
- end
- end
-end
-
-redef class AType
- redef fun accept_pretty_printer(v) do
- if n_kwnullable != null then
- v.visit n_kwnullable
- v.adds
- end
-
- v.visit n_id
-
- if not n_types.is_empty then
- v.consume "["
- v.visit_list n_types
- v.consume "]"
- end
- end
-end
-
-redef class ASuperclass
- redef fun accept_pretty_printer(v) do
- v.visit n_kwsuper
- v.adds
- v.visit n_type
- end
-end
-
-# Properties
-
-redef class APropdef
- redef fun accept_pretty_printer(v) do
- v.visit n_doc
- v.addt
-
- if not n_visibility isa APublicVisibility then
- v.visit n_visibility
- v.adds
- end
-
- if n_kwredef != null then
- v.visit n_kwredef
- v.adds
- end
- end
-
- redef fun start_token do
- if n_doc == null then return super
- return n_doc.last_token.next_token
- end
-end
-
-redef class AAttrPropdef
- redef fun accept_pretty_printer(v) do
- super
- v.visit n_kwvar
- v.adds
- v.visit n_id2
-
- if n_type != null then
- v.consume ":"
- v.adds
- v.visit n_type
- end
-
- if n_expr != null then
- v.adds
- v.consume "="
- v.adds
- v.visit n_expr
- end
-
- if n_annotations != null then v.visit n_annotations
- v.finish_line
- v.addn
- end
-
- redef fun first_token do
- if n_doc != null then return n_doc.first_token
- if not n_visibility isa APublicVisibility then return n_visibility.first_token
- if n_kwredef != null then return n_kwredef
- return n_kwvar
- end
-
- redef fun is_inlinable do return true
-end
-
-redef class ATypePropdef
- redef fun accept_pretty_printer(v) do
- super
- v.visit n_kwtype
- v.adds
- v.visit n_id
- v.consume ":"
- v.adds
- v.visit n_type
- v.finish_line
- v.addn
- end
-
- redef fun is_inlinable do return true
-end
-
-redef class AMethPropdef
- redef fun accept_pretty_printer(v) do
- # TODO: Handle extern annotations
-
- var before = v.indent
- var can_inline = v.can_inline(self)
- super
- if n_kwinit != null then v.visit n_kwinit
- if n_kwmeth != null then v.visit n_kwmeth
- if n_kwnew != null then v.visit n_kwnew
-
- if not n_methid == null then
- v.adds
- v.visit n_methid
- end
-
- v.visit n_signature
-
- if n_annotations != null then
- v.visit n_annotations
- else
- v.adds
- end
-
- if n_extern_calls != null or n_extern_code_block != null then
- if n_annotations != null then v.adds
- if n_extern_calls != null then v.visit n_extern_calls
- if n_extern_code_block != null then v.visit n_extern_code_block
- end
-
- var n_block = self.n_block
-
- if n_block != null then
- while not v.current_token isa TKwdo do v.skip
- if n_annotations != null then
- if v.can_inline(n_annotations) then
- v.adds
- else
- v.addt
- end
- end
- v.consume "do"
-
- if can_inline then
- v.adds
-
- if n_block isa ABlockExpr then
- if n_block.n_expr.is_empty then
- v.visit n_block.n_kwend
- else
- v.visit n_block.n_expr.first
- v.current_token = n_block.n_kwend
- v.skip
- end
- else
- v.visit n_block
- if v.current_token isa TKwend then v.skip
- end
- else
- v.finish_line
- v.addn
- v.indent += 1
-
- if n_block isa ABlockExpr then
- n_block.force_block = true
- v.visit n_block
- v.catch_up n_block.n_kwend
- else
- v.addt
- v.visit n_block
- v.addn
- end
-
- v.indent -= 1
- v.addt
- if n_block isa ABlockExpr then
- v.visit n_block.n_kwend
- else
- v.add "end"
- end
- end
- end
-
- v.finish_line
- v.addn
- assert v.indent == before
- end
-
- # Can be inlined if:
- # * block is empty or can be inlined
- # * contains no comments
- redef fun is_inlinable do
- if not super then return false
- if n_annotations != null and not n_annotations.is_inlinable then return false
- if n_block != null and not n_block.is_inlinable then return false
- if n_extern_calls != null and not n_extern_calls.is_inlinable then return false
- if n_extern_code_block != null and not n_extern_code_block.is_inlinable then return false
- if not collect_comments.is_empty then return false
- return true
- end
-end
-
-redef class AMainMethPropdef
- redef fun accept_pretty_printer(v) do
- v.visit n_block
- v.addn
- end
-end
-
-redef class ASignature
- redef fun accept_pretty_printer(v) do
- if not n_params.is_empty then
- v.consume "("
- v.visit_list n_params
- v.consume ")"
- end
-
- if n_type != null then
- v.consume ":"
- v.adds
- v.visit n_type
- end
- end
-end
-
-redef class AParam
- redef fun accept_pretty_printer(v) do
- v.visit n_id
-
- if n_type != null then
- v.consume ":"
- v.adds
- v.visit n_type
- end
-
- if n_dotdotdot != null then v.visit n_dotdotdot
- end
-end
-
-# Extern
-
-redef class AExternCalls
- redef fun accept_pretty_printer(v) do
- var can_inline = v.can_inline(self)
- v.visit n_kwimport
-
- if can_inline then
- v.adds
- v.visit_list n_extern_calls
- else
- v.addn
- v.addt
- v.addt
- v.visit_list n_extern_calls
- end
-
- v.adds
- end
-end
-
-redef class AFullPropExternCall
- redef fun accept_pretty_printer(v) do
- v.visit n_type
- v.visit n_dot
- v.visit n_methid
- end
-end
-
-redef class ALocalPropExternCall
- redef fun accept_pretty_printer(v) do v.visit n_methid
-end
-
-redef class AInitPropExternCall
- redef fun accept_pretty_printer(v) do v.visit n_type
-end
-
-redef class ACastAsExternCall
- redef fun accept_pretty_printer(v) do
- v.visit n_from_type
- v.visit n_dot
- v.visit n_kwas
- v.consume "("
- v.visit n_to_type
- v.consume ")"
- end
-end
-
-redef class AAsNullableExternCall
- redef fun accept_pretty_printer(v) do
- v.visit n_type
- v.consume "."
- v.visit n_kwas
- v.adds
- v.visit n_kwnullable
- end
-end
-
-redef class AAsNotNullableExternCall
- redef fun accept_pretty_printer(v) do
- v.visit n_type
- v.consume "."
- v.visit n_kwas
- v.adds
- v.visit n_kwnot
- v.adds
- v.visit n_kwnullable
- end
-end
-
-redef class AExternCodeBlock
- redef fun accept_pretty_printer(v) do
- if n_in_language != null then
- v.visit n_in_language
- v.adds
- end
-
- v.visit n_extern_code_segment
- end
-
- redef fun is_inlinable do
- if not super then return false
- return n_extern_code_segment.is_inlinable
- end
-end
-
-redef class AInLanguage
- redef fun accept_pretty_printer(v) do
- v.visit n_kwin
- v.adds
- v.visit n_string
- end
-end
-
-redef class TExternCodeSegment
- redef fun accept_pretty_printer(v) do
- var can_inline = v.can_inline(self)
-
- if can_inline then
- super
- else
- var text = text.substring(2, text.length - 4)
- var lines = text.r_trim.split("\n")
-
- if text.is_empty then
- v.add "`\{`\}"
- else
- v.add "`\{"
-
- if not lines.first.trim.is_empty then
- v.addn
- lines.first.l_trim
- v.indent += 1
- v.addt
- v.indent -= 1
- end
-
- for line in lines do
- v.add line.r_trim
- v.addn
- end
-
- v.addt
- v.add "`\}"
- end
-
- v.current_token = next_token
- end
- end
-
- redef fun is_inlinable do
- if not super then return false
- return location.line_start == location.line_end
- end
-end
-
-# Blocks
-
-redef class ABlockExpr
- redef fun accept_pretty_printer(v) do
- var before = v.indent
- var can_inline = v.can_inline(self)
-
- if can_inline and not n_expr.is_empty then
- v.visit n_expr.first
- v.finish_line
- else
- for nexpr in n_expr do
- var expr_inline = v.can_inline(nexpr)
- if not expr_inline and nexpr != n_expr.first then v.addn
- v.catch_up nexpr
- v.addt
- v.visit nexpr
- v.finish_line
- v.addn
- if not expr_inline and nexpr != n_expr.last then v.addn
- end
- end
-
- assert v.indent == before
- end
-
- redef fun is_inlinable do
- if not super then return false
- if not collect_comments.is_empty then return false
-
- if not n_expr.is_empty then
- if n_expr.length > 1 then return false
- if not n_expr.first.is_inlinable then return false
- end
-
- return true
- end
-end
-
-redef class AIfExpr
- redef fun accept_pretty_printer(v) do
- var before = v.indent
- var can_inline = v.can_inline(self)
- v.visit n_kwif
- v.adds
-
- if v.can_inline(n_expr) then
- v.visit n_expr
- v.adds
- else
- v.visit n_expr
- v.addn
- v.addt
- end
-
- # skip comments before `then` token
- while not v.current_token isa TKwthen do v.skip
- v.consume "then"
- var n_else = self.n_else
-
- if can_inline then
- v.adds
- if n_then != null then v.visit n_then
-
- if has_else(v) then
- n_else.force_inline = true
- v.adds
- v.consume "else"
- v.adds
- v.visit n_else
- else if n_then == null then
- v.add "end"
- end
-
- v.skip_to last_token.last_real_token_in_line
- else
- v.finish_line
- v.addn
- v.indent += 1
-
- if n_then != null then
- if n_then isa ABlockExpr then
- n_then.force_block = true
- v.visit n_then
- else
- v.addt
- v.visit n_then
- v.addn
- end
- end
-
- if has_else(v) then
- while not v.current_token isa TKwelse do
- v.consume v.current_token.text
- end
-
- v.indent -= 1
- v.addt
- v.consume "else"
-
- if n_else isa AIfExpr then
- n_else.force_block = true
- v.adds
- v.visit n_else
- else
- v.finish_line
- v.addn
- v.indent += 1
-
- if n_else isa ABlockExpr then
- n_else.force_block = true
- v.visit n_else
- else
- v.addt
- v.visit n_else
- v.addn
- end
-
- if last_token isa TKwend then
- v.catch_up last_token
- v.indent -= 1
- v.addt
- v.consume "end"
- else
- v.indent -= 1
- v.addt
- v.add "end"
- end
- end
- else
- if last_token.location >= v.current_token.location then v.catch_up last_token
- v.indent -= 1
- v.addt
- v.add "end"
- if v.current_token isa TKwend then v.skip
- end
- end
-
- assert v.indent == before
- end
-
- redef fun is_inlinable do
- if not super then return false
- if n_then != null and not n_then.is_inlinable then return false
- var n_else = self.n_else
- if (n_else isa ABlockExpr and not n_else.n_expr.is_empty) or
- (not n_else isa ABlockExpr and n_else != null) then
- return false
- end
- if not collect_comments.is_empty then return false
- return true
- end
-
- # Does this `if` statement contains a `else` part?
- private fun has_else(v: PrettyPrinterVisitor): Bool do
- var n_else = n_else
- if n_else == null then return false
- var n_kwelse = collect_kwelse
- if n_kwelse == null then return false
-
- if n_else isa ABlockExpr then
- var comments: Array[TComment]
-
- if n_then == null then
- comments = v.collect_comments(n_expr.last_token, n_else.last_token)
- else
- comments = v.collect_comments(n_then.last_token, n_else.last_token)
- end
-
- if not comments.is_empty then return true
- return not n_else.n_expr.is_empty
- end
-
- return true
- end
-
- # Lookup for `else` token in `self`.
- private fun collect_kwelse: nullable TKwelse do
- var token = first_token
-
- while token != last_token do
- if token isa TKwelse then return token
- token = token.next_token
- end
-
- return null
- end
-end
-
-# Used to factorize work on loops.
-private class ALoopHelper
- super AExpr
-
- fun loop_block: nullable ANode is abstract
- fun loop_label: nullable ANode is abstract
-
- fun visit_loop_block(v: PrettyPrinterVisitor) do
- var n_block = loop_block
- v.finish_line
- v.addn
- v.indent += 1
-
- if n_block isa ABlockExpr then
- n_block.force_block = true
- v.visit n_block
- v.catch_up n_block.n_kwend
- v.indent -= 1
- v.addt
- v.visit n_block.n_kwend
- else
- v.addt
- v.visit n_block
- v.addn
- v.indent -= 1
- v.addt
- v.add "end"
- end
-
- if loop_label != null then
- v.adds
- v.visit loop_label
- end
- end
-
- fun visit_loop_inline(v: PrettyPrinterVisitor) do
- var n_block = loop_block
- v.adds
-
- if n_block isa ABlockExpr then
- if n_block.n_expr.is_empty then
- v.visit n_block.n_kwend
- else
- v.visit n_block.n_expr.first
- v.current_token = n_block.n_kwend
- v.skip
- end
- else
- v.visit n_block
- if v.current_token isa TKwend then v.skip
- end
-
- if loop_label != null then
- v.adds
- v.visit loop_label
- end
- end
-
- redef fun is_inlinable do
- var n_block = loop_block
- if not super then return false
- if n_block isa ABlockExpr and not n_block.is_inlinable then return false
- if not collect_comments.is_empty then return false
- return true
- end
-end
-
-redef class ALoopExpr
- super ALoopHelper
-
- redef fun loop_block do return n_block
- redef fun loop_label do return n_label
-
- redef fun accept_pretty_printer(v) do
- var can_inline = v.can_inline(self)
- v.visit n_kwloop
- if can_inline then visit_loop_inline v else visit_loop_block v
- end
-end
-
-redef class AWhileExpr
- super ALoopHelper
-
- redef fun loop_block do return n_block
- redef fun loop_label do return n_label
-
- redef fun accept_pretty_printer(v) do
- var can_inline = v.can_inline(self)
- v.visit n_kwwhile
- v.adds
- v.visit n_expr
- v.adds
- v.visit n_kwdo
- if can_inline then visit_loop_inline v else visit_loop_block v
- end
-end
-
-redef class ADoExpr
- super ALoopHelper
-
- redef fun loop_block do return n_block
- redef fun loop_label do return n_label
-
- redef fun accept_pretty_printer(v) do
- var can_inline = v.can_inline(self)
- v.visit n_kwdo
- if can_inline then visit_loop_inline v else visit_loop_block v
- end
-end
-
-redef class AForExpr
- super ALoopHelper
-
- redef fun loop_block do return n_block
- redef fun loop_label do return n_label
-
- redef fun accept_pretty_printer(v) do
- var can_inline = v.can_inline(self)
- v.visit n_kwfor
- v.adds
-
- for n_id in n_ids do
- v.visit n_id
- if n_id != n_ids.last then v.add ", "
- end
-
- v.adds
- v.consume "in"
- v.adds
- v.visit n_expr
- v.adds
- v.visit n_kwdo
- if can_inline then visit_loop_inline v else visit_loop_block v
- end
-end
-
-redef class ABreakExpr
- redef fun accept_pretty_printer(v) do
- v.visit n_kwbreak
-
- if n_expr != null then
- v.adds
- v.visit n_expr
- end
-
- if n_label != null then
- v.adds
- v.visit n_label
- end
- end
-
- redef fun is_inlinable do return true
-end
-
-redef class AContinueExpr
- redef fun accept_pretty_printer(v) do
- v.visit n_kwcontinue
-
- if n_expr != null then
- v.adds
- v.visit n_expr
- end
-
- if n_label != null then
- v.adds
- v.visit n_label
- end
- end
-
- redef fun is_inlinable do return true
-end
-
-# Calls
-
-redef class ASendExpr
- redef fun is_inlinable do return true
-end
-
-redef class ACallExpr
- redef fun accept_pretty_printer(v) do
- var can_inline = v.can_inline(self)
- v.visit_recv n_expr
-
- if not n_expr isa AImplicitSelfExpr and not can_inline then
- v.addn
- v.addt
- v.addt
- end
-
- v.visit n_id
-
- if not n_args.n_exprs.is_empty then
- if is_stmt and n_args.n_exprs.length == 1 then
- v.adds
- if v.current_token isa TOpar then v.skip
- v.visit n_args.n_exprs.first
- if v.current_token isa TCpar then v.skip
- else
- if v.current_token isa TOpar then
- v.consume "("
- else
- v.adds
- end
-
- v.visit_list n_args.n_exprs
- if v.current_token isa TCpar then v.consume ")"
- end
- end
- end
-
- # Is the call alone on its line?
- fun is_stmt: Bool do return parent isa ABlockExpr
-end
-
-redef class ACallAssignExpr
- redef fun accept_pretty_printer(v) do
- v.visit_recv n_expr
- v.visit n_id
-
- if not n_args.n_exprs.is_empty then
- v.consume "("
- v.visit_list n_args.n_exprs
- v.consume ")"
- end
-
- v.adds
- v.consume "="
- v.adds
- v.visit n_value
- end
-end
-
-redef class ACallReassignExpr
- redef fun accept_pretty_printer(v) do
- v.visit_recv n_expr
- v.visit n_id
-
- if not n_args.n_exprs.is_empty then
- v.consume "("
- v.visit_list n_args.n_exprs
- v.consume ")"
- end
-
- v.adds
- v.visit n_assign_op
- v.adds
- v.visit n_value
- end
-end
-
-redef class ABraExpr
- redef fun accept_pretty_printer(v) do
- v.visit n_expr
-
- if not n_args.n_exprs.is_empty then
- v.consume "["
- v.visit_list n_args.n_exprs
- v.consume "]"
- end
- end
-end
-
-redef class ABraAssignExpr
- redef fun accept_pretty_printer(v) do
- v.visit n_expr
-
- if not n_args.n_exprs.is_empty then
- v.consume "["
- v.visit_list n_args.n_exprs
- v.consume "]"
- end
-
- v.adds
- v.visit n_assign
- v.adds
- v.visit n_value
- end
-end
-
-redef class ABraReassignExpr
- redef fun accept_pretty_printer(v) do
- v.visit n_expr
-
- if not n_args.n_exprs.is_empty then
- v.consume "["
- v.visit_list n_args.n_exprs
- v.consume "]"
- end
-
- v.adds
- v.visit n_assign_op
- v.adds
- v.visit n_value
- end
-end
-
-redef class AAssignMethid
- redef fun accept_pretty_printer(v) do
- v.visit n_id
- v.visit n_assign
- end
-end
-
-redef class ABraMethid
- redef fun accept_pretty_printer(v) do
- v.visit n_obra
- v.visit n_cbra
- end
-end
-
-redef class ABraassignMethid
- redef fun accept_pretty_printer(v) do
- v.visit n_obra
- v.visit n_cbra
- v.visit n_assign
- end
-end
-
-redef class AInitExpr
- redef fun accept_pretty_printer(v) do
- if not n_expr isa AImplicitSelfExpr then
- v.visit n_expr
- v.consume "."
- end
-
- v.visit n_kwinit
-
- if not n_args.n_exprs.is_empty then
- v.consume "("
- v.visit_list n_args.n_exprs
- v.consume ")"
- end
- end
-end
-
-redef class ANewExpr
- redef fun accept_pretty_printer(v) do
- var can_inline = v.can_inline(self)
- v.visit n_kwnew
- v.adds
- v.visit n_type
-
- if n_id != null then
- v.consume "."
-
- if not can_inline then
- v.addn
- v.addt
- v.addt
- end
-
- v.visit n_id
- end
-
- if not n_args.n_exprs.is_empty then
- v.consume "("
- v.visit_list n_args.n_exprs
- v.consume ")"
- end
- end
-
- redef fun is_inlinable do return true
-end
-
-# Attributes
-
-redef class AAttrExpr
- redef fun accept_pretty_printer(v) do
- v.visit_recv n_expr
- v.visit n_id
- end
-
- redef fun is_inlinable do return true
-end
-
-redef class AAttrAssignExpr
- redef fun accept_pretty_printer(v) do
- v.visit_recv n_expr
- v.visit n_id
- v.adds
- v.visit n_assign
- v.adds
- v.visit n_value
- end
-end
-
-redef class AAttrReassignExpr
- redef fun accept_pretty_printer(v) do
- v.visit_recv n_expr
- v.visit n_id
- v.adds
- v.visit n_assign_op
- v.adds
- v.visit n_value
- end
-end
-
-# Exprs
-
-redef class AVardeclExpr
- redef fun accept_pretty_printer(v) do
- v.visit n_kwvar
- v.adds
- v.visit n_id
-
- if n_type != null then
- v.consume ":"
- v.adds
- v.visit n_type
- end
-
- if n_expr != null then
- v.adds
- v.consume "="
- v.adds
- v.visit n_expr
- end
- end
-
- redef fun is_inlinable do return true
-end
-
-redef class AVarAssignExpr
- redef fun accept_pretty_printer(v) do
- v.visit n_id
- v.adds
- v.visit n_assign
- v.adds
- v.visit n_value
- end
-end
-
-redef class AAssertExpr
- redef fun accept_pretty_printer(v) do
- var can_inline = v.can_inline(self)
- v.visit n_kwassert
-
- if n_id != null then
- v.adds
- v.visit n_id
- v.consume ":"
- end
-
- v.adds
- v.visit n_expr
- var n_else = self.n_else
-
- if n_else != null then
- v.adds
- v.consume "else"
-
- if can_inline then
- v.adds
- v.visit n_else
- else
- v.addn
-
- if n_else isa ABlockExpr then
- v.indent += 1
- n_else.force_block = true
- v.visit n_else
- v.indent -= 1
- v.addt
- v.visit n_else.n_kwend
- else
- v.indent += 1
- v.addt
- v.visit n_else
- v.addn
- v.indent -= 1
- v.addt
- v.add "end"
- end
- end
- end
- end
-
- redef fun is_inlinable do
- if not super then return false
- if n_else != null and not n_else.is_inlinable then return false
- return true
- end
-end
-
-redef class AReturnExpr
- redef fun accept_pretty_printer(v) do
- v.visit n_kwreturn
-
- if n_expr != null then
- v.adds
- v.visit n_expr
- end
- end
-end
-
-redef class ASuperExpr
- redef fun accept_pretty_printer(v) do
- v.visit n_kwsuper
-
- if not n_args.n_exprs.is_empty then
- if is_stmt and n_args.n_exprs.length == 1 then
- v.adds
- if v.current_token isa TOpar then v.skip
- v.visit n_args.n_exprs.first
- if v.current_token isa TCpar then v.skip
- else
- if v.current_token isa TOpar then
- v.consume "("
- else
- v.adds
- end
-
- v.visit_list n_args.n_exprs
- if v.current_token isa TCpar then v.consume ")"
- end
- end
- end
-
- # Is the call alone on its line?
- fun is_stmt: Bool do return self.first_token.is_starting_line
-
- redef fun is_inlinable do return true
-end
-
-redef class AOnceExpr
- redef fun accept_pretty_printer(v) do
- v.visit n_kwonce
- v.adds
- v.visit n_expr
- end
-
- redef fun is_inlinable do return true
-end
-
-redef class AAbortExpr
- redef fun accept_pretty_printer(v) do v.visit n_kwabort
- redef fun is_inlinable do return true
-end
-
-redef class ANotExpr
- redef fun accept_pretty_printer(v) do
- v.visit n_kwnot
- v.adds
- v.visit n_expr
- end
-end
-
-redef class AAsCastExpr
- redef fun accept_pretty_printer(v) do
- v.visit n_expr
- v.consume "."
- v.visit n_kwas
- v.visit n_opar
- v.visit n_type
- v.visit n_cpar
- end
-end
-
-redef class AAsNotnullExpr
- redef fun accept_pretty_printer(v) do
- v.visit n_expr
- v.consume "."
- v.visit n_kwas
- v.visit n_opar
- v.visit n_kwnot
- v.adds
- v.visit n_kwnull
- v.visit n_cpar
- end
-end
-
-# Binops
-
-# Used to factorize work on Or, And, Implies and Binop expressions.
-private class ABinOpHelper
- super AExpr
-
- fun bin_expr1: AExpr is abstract
- fun bin_expr2: AExpr is abstract
-
- # Operator string
- fun bin_op: String is abstract
-
- redef fun accept_pretty_printer(v) do
- var can_inline = v.can_inline(self)
-
- if not can_inline then
- if (self isa ABinopExpr and bin_expr1 isa ABinopExpr) or
- (self isa AAndExpr and (bin_expr1 isa AAndExpr or bin_expr1 isa AOrExpr)) or
- (self isa AOrExpr and (bin_expr1 isa AAndExpr or bin_expr1 isa AOrExpr))
- then
- bin_expr1.force_block = true
- end
- end
-
- v.visit bin_expr1
- v.adds
- v.consume bin_op
-
- if can_inline then
- v.adds
- v.visit bin_expr2
- else
- v.addn
- v.addt
- v.addt
- v.visit bin_expr2
- end
- end
-end
-
-redef class AAndExpr
- super ABinOpHelper
-
- redef fun bin_expr1 do return n_expr
- redef fun bin_expr2 do return n_expr2
- redef fun bin_op do return "and"
-end
-
-redef class AOrExpr
- super ABinOpHelper
-
- redef fun bin_expr1 do return n_expr
- redef fun bin_expr2 do return n_expr2
- redef fun bin_op do return "or"
-end
-
-redef class AImpliesExpr
- super ABinOpHelper
-
- redef fun bin_expr1 do return n_expr
- redef fun bin_expr2 do return n_expr2
- redef fun bin_op do return "implies"
-end
-
-redef class ABinopExpr
- super ABinOpHelper
-
- redef fun bin_expr1 do return n_expr
- redef fun bin_expr2 do return n_expr2
-end
-
-redef class AEqExpr
- redef fun bin_op do return "=="
-end
-
-redef class AGeExpr
- redef fun bin_op do return ">="
-end
-
-redef class AGgExpr
- redef fun bin_op do return ">>"
-end
-
-redef class AGtExpr
- redef fun bin_op do return ">"
-end
-
-redef class ALeExpr
- redef fun bin_op do return "<="
-end
-
-redef class ALlExpr
- redef fun bin_op do return "<<"
-end
-
-redef class ALtExpr
- redef fun bin_op do return "<"
-end
-
-redef class AMinusExpr
- redef fun bin_op do return "-"
-end
-
-redef class ANeExpr
- redef fun bin_op do return "!="
-end
-
-redef class APercentExpr
- redef fun bin_op do return "%"
-end
-
-redef class APlusExpr
- redef fun bin_op do return "+"
-end
-
-redef class ASlashExpr
- redef fun bin_op do return "/"
-end
-
-redef class AStarExpr
- redef fun bin_op do return "*"
-end
-
-redef class AStarstarExpr
- redef fun bin_op do return "**"
-end
-
-redef class AStarshipExpr
- redef fun bin_op do return "<=>"
-end
-
-redef class AIsaExpr
- redef fun accept_pretty_printer(v) do
- v.visit n_expr
- v.adds
- v.consume "isa"
- v.adds
- v.visit n_type
- end
-end
-
-redef class AOrElseExpr
- redef fun accept_pretty_printer(v) do
- v.visit n_expr
- v.adds
- v.consume "or"
- v.adds
- v.consume "else"
- v.adds
- v.visit n_expr2
- end
-
- redef fun is_inlinable do return true
-end
-
-# Syntax
-
-redef class AUminusExpr
- redef fun accept_pretty_printer(v) do
- v.consume "-"
- v.visit n_expr
- end
-end
-
-redef class ANullExpr
- redef fun accept_pretty_printer(v) do v.visit n_kwnull
- redef fun is_inlinable do return true
-end
-
-redef class AParExpr
- redef fun accept_pretty_printer(v) do
- v.visit n_opar
- v.visit n_expr
- v.visit n_cpar
- end
-end
-
-redef class AArrayExpr
- redef fun accept_pretty_printer(v) do
- v.consume "["
- v.visit_list n_exprs.n_exprs
- v.consume "]"
- end
-end
-
-redef class ACrangeExpr
- redef fun accept_pretty_printer(v) do
- v.visit n_obra
- v.visit n_expr
- v.consume ".."
- v.visit n_expr2
- v.visit n_cbra
- end
-end
-
-redef class AOrangeExpr
- redef fun accept_pretty_printer(v) do
- v.visit n_obra
- v.visit n_expr
- v.consume ".."
- v.visit n_expr2
- v.visit n_cbra
- end
-end
-
-# Strings
-
-redef class AStringFormExpr
- redef fun accept_pretty_printer(v) do
- var can_inline = v.can_inline(self)
-
- if can_inline then
- v.visit n_string
- else
- var text = n_string.text
- var i = 0
-
- while i < text.length do
- v.add text[i].to_s
-
- if v.current_length >= v.max_size and i <= text.length - 3 then
- v.add "\" +"
- v.addn
- v.indent += 1
- v.addt
- v.indent -= 1
- v.add "\""
- end
-
- i += 1
- end
-
- v.current_token = n_string.next_token
- end
- end
-end
-
-redef class ASuperstringExpr
- redef fun accept_pretty_printer(v) do
- for n_expr in n_exprs do v.visit n_expr
- end
-
- redef fun must_be_inline do
- if super then return true
-
- if not n_exprs.is_empty then
- var first = n_exprs.first
- return first isa AStringFormExpr and first.n_string.text.has_prefix("\"\"\"")
- end
-
- return false
- end
-end
+import pretty
redef class ToolContext
var opt_dir = new OptionString("Working directory (default is '.nitpretty')", "--dir")
--- /dev/null
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+module pretty
+
+import template
+import toolcontext
+import modelbuilder
+import astutil
+
+# 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
+ 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 `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 n.must_be_inline then return true
+ if n.must_be_block then return false
+ # check length
+ if 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 != target do skip
+ end
+
+ # Visit `current_token`.
+ fun consume(token: String) do
+ assert current_token.text == 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
+ assert current_token.location <= token.location
+ 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 skip
+ 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.
+ var max_size = 80
+
+ # 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
+ 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 " "
+
+ fun visit_recv(n_expr: AExpr) do
+ if not n_expr isa AImplicitSelfExpr then
+ visit n_expr
+ consume "."
+ end
+ end
+end
+
+# Base framework redefs
+
+redef class ANodes[E]
+ private fun accept_pretty_printer(v: PrettyPrinterVisitor) do
+ for e in self do
+ var e_can_inline = v.can_inline(e)
+
+ if e != first then
+ if not e_can_inline then
+ v.add ","
+ v.addn
+ v.addt
+ v.addt
+ else
+ v.add ", "
+ end
+ end
+
+ v.visit e
+ end
+ end
+end
+
+redef class ANode
+ # Start visit of `self` using a `PrettyPrinterVisitor`
+ private fun accept_pretty_printer(v: PrettyPrinterVisitor) is abstract
+
+ # Collect the length (in `Char`) of the node.
+ private fun collect_length: Int is abstract
+
+ # Is `self` printable in one line?
+ private fun is_inlinable: Bool do return true
+
+ # Force `self` to be rendered as a block.
+ private var force_block = false
+
+ # Does `self` have to be rendered as a block?
+ private fun must_be_block: Bool do return force_block
+
+ # Force `self` to be rendered as a line.
+ private var force_inline = false
+
+ # Does `self` have be rendered as a line?
+ private fun must_be_inline: Bool do
+ if parent != null and parent.must_be_inline then return true
+ return force_inline
+ end
+
+ # Does `self` was written in one line before transformation?
+ private fun was_inline: Bool is abstract
+end
+
+redef class Token
+ redef fun accept_pretty_printer(v) do
+ v.add text.trim
+ v.current_token = next_token
+ end
+
+ redef fun collect_length do return text.length
+ redef fun is_inlinable do return true
+ redef fun was_inline do return true
+end
+
+redef class Prod
+ redef fun accept_pretty_printer(v) do v.visit first_token
+
+ # The token where the production really start (skipping ADoc).
+ private fun start_token: nullable Token do return first_token
+
+ # Collect all `TComment` contained in the production
+ # (between `start_token` and `end_token`).
+ private fun collect_comments: Array[TComment] do
+ var res = new Array[TComment]
+ if start_token == null or last_token == null then return res
+ var token = start_token
+
+ while token != last_token do
+ if token isa TComment then res.add token
+ token = token.next_token
+ end
+
+ return res
+ end
+
+ redef fun collect_length do
+ var res = 0
+ if start_token == null or last_token == null then return res
+ var token = start_token
+
+ while token != last_token do
+ res += token.text.length
+ token = token.next_token
+ end
+
+ res += token.text.length
+ return res
+ end
+
+ redef fun was_inline do
+ return first_token.location.line_start == last_token.location.line_end
+ end
+end
+
+# Comments
+
+redef class TComment
+ redef fun accept_pretty_printer(v) do
+ if is_adoc then
+ v.addt
+ super
+ v.addn
+ return
+ end
+
+ if is_licence then
+ super
+ v.addn
+ if is_last_in_group then v.addn
+ return
+ end
+
+ if is_orphan then
+ v.addn
+ v.addt
+ super
+ v.addn
+ v.addn
+ return
+ end
+
+ if is_inline then
+ if next_token isa TComment and is_first_in_group then v.addn
+ v.addt
+ super
+ v.addn
+ var prev_token = self.prev_token
+ if prev_token isa TComment and prev_token.is_inline and is_last_in_group then v.addn
+ return
+ end
+
+ super
+ end
+
+ # Is `self` part of an `ADoc`?
+ private fun is_adoc: Bool do return parent isa ADoc and parent.parent != null
+
+ # Is `self` part of a licence?
+ private fun is_licence: Bool do
+ var prev_token = self.prev_token
+
+ if prev_token == null then
+ return true
+ else if prev_token isa TComment then
+ return prev_token.is_licence
+ else
+ return false
+ end
+ end
+
+ # Is `self` starts and ends its line?
+ private fun is_inline: Bool do
+ return self == first_real_token_in_line and self == last_real_token_in_line
+ end
+
+ # Is `self` an orphan line (blank before and after)?
+ private fun is_orphan: Bool do
+ return prev_token isa TEol and
+ (prev_token.prev_token isa TEol or prev_token.prev_token isa TComment) and
+ next_token isa TEol
+ end
+
+ # Is `self` the first comment of a group of comment?
+ private fun is_first_in_group: Bool do return not prev_token isa TComment
+
+ # Is `self` the last comment of a group of comments?
+ private fun is_last_in_group: Bool do return not next_token isa TComment
+end
+
+redef class ADoc
+ redef fun accept_pretty_printer(v) do for comment in n_comment do v.visit comment
+ redef fun is_inlinable do return n_comment.length <= 1
+end
+
+# Annotations
+
+redef class AAnnotations
+ redef fun accept_pretty_printer(v) do
+ v.adds
+ v.consume "is"
+
+ if v.can_inline(self) then
+ v.adds
+ for n_item in n_items do
+ v.visit n_item
+ if n_item != n_items.last then
+ v.add ", "
+ end
+ end
+ v.finish_line
+ else if n_items.length > 1 then
+ v.addn
+ v.indent += 1
+
+ for n_item in n_items do
+ v.addt
+ v.visit n_item
+ v.finish_line
+ v.addn
+ end
+
+ v.indent -= 1
+ end
+ if not was_inline and v.current_token isa TKwend then v.skip
+ end
+
+ redef fun is_inlinable do
+ if not super then return false
+ for annot in n_items do if not annot.is_inlinable then return false
+ return true
+ end
+end
+
+redef class AAnnotation
+ redef fun accept_pretty_printer(v) do
+ v.visit n_atid
+ if not n_args.is_empty then
+ if n_opar == null then
+ v.adds
+ else
+ v.visit n_opar
+ end
+ v.visit_list n_args
+ v.visit n_cpar
+ end
+ end
+end
+
+redef class ATypeExpr
+ redef fun accept_pretty_printer(v) do v.visit n_type
+end
+
+# Modules
+
+redef class AModule
+ redef fun accept_pretty_printer(v) do
+ v.catch_up start_token
+ v.visit n_moduledecl
+
+ if not n_imports.is_empty then
+ v.addn
+
+ for n_import in n_imports do
+ v.catch_up n_import
+ v.visit n_import
+ end
+ end
+
+ if not n_extern_code_blocks.is_empty then
+ v.addn
+
+ for n_extern_block in n_extern_code_blocks do
+ v.catch_up n_extern_block
+ v.visit n_extern_block
+ v.addn
+ if n_extern_block != n_extern_code_blocks.last then v.addn
+ end
+
+ if not n_classdefs.is_empty then v.addn
+ end
+
+ if not n_classdefs.is_empty then
+ v.addn
+
+ for n_classdef in n_classdefs do
+ v.catch_up n_classdef
+ v.visit n_classdef
+ if n_classdef != n_classdefs.last then v.addn
+ end
+ end
+
+ assert v.indent == 0
+ end
+
+ # Skip doc if any.
+ redef fun start_token do
+ if n_moduledecl != null then return n_moduledecl.first_token
+ if not n_imports.is_empty then return n_imports.first.first_token
+ if not n_classdefs.is_empty then return n_classdefs.first.first_token
+ return first_token
+ end
+
+ redef fun is_inlinable do return false
+end
+
+redef class AModuledecl
+ redef fun accept_pretty_printer(v) do
+ v.visit n_doc
+ v.visit n_kwmodule
+ v.adds
+ v.visit n_name
+
+ if n_annotations != null then
+ var annot_inline = v.can_inline(n_annotations)
+ v.visit n_annotations
+
+ if not annot_inline then
+ if v.current_token isa TKwend then
+ v.consume "end"
+ v.finish_line
+ else
+ v.add "end"
+ end
+ end
+ end
+
+ v.finish_line
+ v.addn
+ end
+end
+
+redef class AModuleName
+ redef fun accept_pretty_printer(v) do
+ for path in n_path do
+ v.visit path
+ v.add "::"
+ end
+
+ v.visit n_id
+ end
+end
+
+redef class ANoImport
+ redef fun accept_pretty_printer(v) do
+ v.visit n_kwimport
+ v.adds
+ v.visit n_kwend
+ v.finish_line
+ v.addn
+ end
+end
+
+redef class AStdImport
+ redef fun accept_pretty_printer(v) do
+ if not n_visibility isa APublicVisibility then
+ v.visit n_visibility
+ v.adds
+ end
+
+ v.visit n_kwimport
+ v.adds
+ v.visit n_name
+ v.finish_line
+ v.addn
+ end
+end
+
+# Classes
+
+redef class AClassdef
+ redef fun accept_pretty_printer(v) do
+ for n_propdef in n_propdefs do
+ v.catch_up n_propdef
+
+ if n_propdef.n_doc != null or not v.can_inline(n_propdef) then
+ if n_propdef != n_propdefs.first then v.addn
+ v.visit n_propdef
+ if n_propdef != n_propdefs.last then v.addn
+ else
+ v.visit n_propdef
+ end
+ end
+ end
+end
+
+redef class AStdClassdef
+ redef fun accept_pretty_printer(v) do
+ v.visit n_doc
+ var can_inline = v.can_inline(self)
+
+ if not n_visibility isa APublicVisibility then
+ v.visit n_visibility
+ v.adds
+ end
+
+ if n_kwredef != null then
+ v.visit n_kwredef
+ v.adds
+ end
+
+ v.visit n_classkind
+ v.adds
+ v.visit n_id
+
+ if not n_formaldefs.is_empty then
+ v.consume "["
+ v.visit_list n_formaldefs
+ v.consume "]"
+ end
+
+ if n_extern_code_block != null then
+ v.adds
+ v.visit n_extern_code_block
+ end
+
+ if can_inline then
+ v.adds
+
+ if not n_superclasses.is_empty then
+ for n_superclass in n_superclasses do
+ v.visit n_superclass
+ v.adds
+ end
+ end
+ else
+ v.finish_line
+ v.addn
+ v.indent += 1
+
+ for n_superclass in n_superclasses do
+ v.catch_up n_superclass
+ v.addt
+ v.visit n_superclass
+ v.finish_line
+ v.addn
+ end
+
+ if not n_superclasses.is_empty and not n_propdefs.is_empty then
+ v.addn
+ end
+
+ super
+ v.catch_up n_kwend
+ v.indent -= 1
+ end
+
+ v.visit n_kwend
+ v.finish_line
+ v.addn
+ assert v.indent == 0
+ end
+
+ redef fun is_inlinable do
+ if not super then return false
+ if not n_propdefs.is_empty then return false
+ if n_superclasses.length > 1 then return false
+ if not collect_comments.is_empty then return false
+ return true
+ end
+
+ redef fun start_token do
+ if not n_visibility isa APublicVisibility then return n_visibility.first_token
+ if n_kwredef != null then return n_kwredef
+ return n_classkind.first_token
+ end
+end
+
+redef class AAbstractClasskind
+ redef fun accept_pretty_printer(v) do
+ v.visit n_kwabstract
+ v.adds
+ v.visit n_kwclass
+ end
+end
+
+redef class AExternClasskind
+ redef fun accept_pretty_printer(v) do
+ v.visit n_kwextern
+ v.adds
+ v.visit n_kwclass
+ end
+end
+
+redef class AFormaldef
+ redef fun accept_pretty_printer(v) do
+ v.visit n_id
+
+ if n_type != null then
+ v.consume ":"
+ v.adds
+ v.visit n_type
+ end
+ end
+end
+
+redef class AType
+ redef fun accept_pretty_printer(v) do
+ if n_kwnullable != null then
+ v.visit n_kwnullable
+ v.adds
+ end
+
+ v.visit n_id
+
+ if not n_types.is_empty then
+ v.consume "["
+ v.visit_list n_types
+ v.consume "]"
+ end
+ end
+end
+
+redef class ASuperclass
+ redef fun accept_pretty_printer(v) do
+ v.visit n_kwsuper
+ v.adds
+ v.visit n_type
+ end
+end
+
+# Properties
+
+redef class APropdef
+ redef fun accept_pretty_printer(v) do
+ v.visit n_doc
+ v.addt
+
+ if not n_visibility isa APublicVisibility then
+ v.visit n_visibility
+ v.adds
+ end
+
+ if n_kwredef != null then
+ v.visit n_kwredef
+ v.adds
+ end
+ end
+
+ redef fun start_token do
+ if n_doc == null then return super
+ return n_doc.last_token.next_token
+ end
+end
+
+redef class AAttrPropdef
+ redef fun accept_pretty_printer(v) do
+ super
+ v.visit n_kwvar
+ v.adds
+ v.visit n_id2
+
+ if n_type != null then
+ v.consume ":"
+ v.adds
+ v.visit n_type
+ end
+
+ if n_expr != null then
+ v.adds
+ v.consume "="
+ v.adds
+ v.visit n_expr
+ end
+
+ if n_annotations != null then v.visit n_annotations
+ v.finish_line
+ v.addn
+ end
+
+ redef fun first_token do
+ if n_doc != null then return n_doc.first_token
+ if not n_visibility isa APublicVisibility then return n_visibility.first_token
+ if n_kwredef != null then return n_kwredef
+ return n_kwvar
+ end
+
+ redef fun is_inlinable do return true
+end
+
+redef class ATypePropdef
+ redef fun accept_pretty_printer(v) do
+ super
+ v.visit n_kwtype
+ v.adds
+ v.visit n_id
+ v.consume ":"
+ v.adds
+ v.visit n_type
+ v.finish_line
+ v.addn
+ end
+
+ redef fun is_inlinable do return true
+end
+
+redef class AMethPropdef
+ redef fun accept_pretty_printer(v) do
+ # TODO: Handle extern annotations
+
+ var before = v.indent
+ var can_inline = v.can_inline(self)
+ super
+ if n_kwinit != null then v.visit n_kwinit
+ if n_kwmeth != null then v.visit n_kwmeth
+ if n_kwnew != null then v.visit n_kwnew
+
+ if not n_methid == null then
+ v.adds
+ v.visit n_methid
+ end
+
+ v.visit n_signature
+
+ if n_annotations != null then
+ v.visit n_annotations
+ else
+ v.adds
+ end
+
+ if n_extern_calls != null or n_extern_code_block != null then
+ if n_annotations != null then v.adds
+ if n_extern_calls != null then v.visit n_extern_calls
+ if n_extern_code_block != null then v.visit n_extern_code_block
+ end
+
+ var n_block = self.n_block
+
+ if n_block != null then
+ while not v.current_token isa TKwdo do v.skip
+ if n_annotations != null then
+ if v.can_inline(n_annotations) then
+ v.adds
+ else
+ v.addt
+ end
+ end
+ v.consume "do"
+
+ if can_inline then
+ v.adds
+
+ if n_block isa ABlockExpr then
+ if n_block.n_expr.is_empty then
+ v.visit n_block.n_kwend
+ else
+ v.visit n_block.n_expr.first
+ v.current_token = n_block.n_kwend
+ v.skip
+ end
+ else
+ v.visit n_block
+ if v.current_token isa TKwend then v.skip
+ end
+ else
+ v.finish_line
+ v.addn
+ v.indent += 1
+
+ if n_block isa ABlockExpr then
+ n_block.force_block = true
+ v.visit n_block
+ v.catch_up n_block.n_kwend
+ else
+ v.addt
+ v.visit n_block
+ v.addn
+ end
+
+ v.indent -= 1
+ v.addt
+ if n_block isa ABlockExpr then
+ v.visit n_block.n_kwend
+ else
+ v.add "end"
+ end
+ end
+ end
+
+ v.finish_line
+ v.addn
+ assert v.indent == before
+ end
+
+ # Can be inlined if:
+ # * block is empty or can be inlined
+ # * contains no comments
+ redef fun is_inlinable do
+ if not super then return false
+ if n_annotations != null and not n_annotations.is_inlinable then return false
+ if n_block != null and not n_block.is_inlinable then return false
+ if n_extern_calls != null and not n_extern_calls.is_inlinable then return false
+ if n_extern_code_block != null and not n_extern_code_block.is_inlinable then return false
+ if not collect_comments.is_empty then return false
+ return true
+ end
+end
+
+redef class AMainMethPropdef
+ redef fun accept_pretty_printer(v) do
+ v.visit n_block
+ v.addn
+ end
+end
+
+redef class ASignature
+ redef fun accept_pretty_printer(v) do
+ if not n_params.is_empty then
+ v.consume "("
+ v.visit_list n_params
+ v.consume ")"
+ end
+
+ if n_type != null then
+ v.consume ":"
+ v.adds
+ v.visit n_type
+ end
+ end
+end
+
+redef class AParam
+ redef fun accept_pretty_printer(v) do
+ v.visit n_id
+
+ if n_type != null then
+ v.consume ":"
+ v.adds
+ v.visit n_type
+ end
+
+ if n_dotdotdot != null then v.visit n_dotdotdot
+ end
+end
+
+# Extern
+
+redef class AExternCalls
+ redef fun accept_pretty_printer(v) do
+ var can_inline = v.can_inline(self)
+ v.visit n_kwimport
+
+ if can_inline then
+ v.adds
+ v.visit_list n_extern_calls
+ else
+ v.addn
+ v.addt
+ v.addt
+ v.visit_list n_extern_calls
+ end
+
+ v.adds
+ end
+end
+
+redef class AFullPropExternCall
+ redef fun accept_pretty_printer(v) do
+ v.visit n_type
+ v.visit n_dot
+ v.visit n_methid
+ end
+end
+
+redef class ALocalPropExternCall
+ redef fun accept_pretty_printer(v) do v.visit n_methid
+end
+
+redef class AInitPropExternCall
+ redef fun accept_pretty_printer(v) do v.visit n_type
+end
+
+redef class ACastAsExternCall
+ redef fun accept_pretty_printer(v) do
+ v.visit n_from_type
+ v.visit n_dot
+ v.visit n_kwas
+ v.consume "("
+ v.visit n_to_type
+ v.consume ")"
+ end
+end
+
+redef class AAsNullableExternCall
+ redef fun accept_pretty_printer(v) do
+ v.visit n_type
+ v.consume "."
+ v.visit n_kwas
+ v.adds
+ v.visit n_kwnullable
+ end
+end
+
+redef class AAsNotNullableExternCall
+ redef fun accept_pretty_printer(v) do
+ v.visit n_type
+ v.consume "."
+ v.visit n_kwas
+ v.adds
+ v.visit n_kwnot
+ v.adds
+ v.visit n_kwnullable
+ end
+end
+
+redef class AExternCodeBlock
+ redef fun accept_pretty_printer(v) do
+ if n_in_language != null then
+ v.visit n_in_language
+ v.adds
+ end
+
+ v.visit n_extern_code_segment
+ end
+
+ redef fun is_inlinable do
+ if not super then return false
+ return n_extern_code_segment.is_inlinable
+ end
+end
+
+redef class AInLanguage
+ redef fun accept_pretty_printer(v) do
+ v.visit n_kwin
+ v.adds
+ v.visit n_string
+ end
+end
+
+redef class TExternCodeSegment
+ redef fun accept_pretty_printer(v) do
+ var can_inline = v.can_inline(self)
+
+ if can_inline then
+ super
+ else
+ var text = text.substring(2, text.length - 4)
+ var lines = text.r_trim.split("\n")
+
+ if text.is_empty then
+ v.add "`\{`\}"
+ else
+ v.add "`\{"
+
+ if not lines.first.trim.is_empty then
+ v.addn
+ lines.first.l_trim
+ v.indent += 1
+ v.addt
+ v.indent -= 1
+ end
+
+ for line in lines do
+ v.add line.r_trim
+ v.addn
+ end
+
+ v.addt
+ v.add "`\}"
+ end
+
+ v.current_token = next_token
+ end
+ end
+
+ redef fun is_inlinable do
+ if not super then return false
+ return location.line_start == location.line_end
+ end
+end
+
+# Blocks
+
+redef class ABlockExpr
+ redef fun accept_pretty_printer(v) do
+ var before = v.indent
+ var can_inline = v.can_inline(self)
+
+ if can_inline and not n_expr.is_empty then
+ v.visit n_expr.first
+ v.finish_line
+ else
+ for nexpr in n_expr do
+ var expr_inline = v.can_inline(nexpr)
+ if not expr_inline and nexpr != n_expr.first then v.addn
+ v.catch_up nexpr
+ v.addt
+ v.visit nexpr
+ v.finish_line
+ v.addn
+ if not expr_inline and nexpr != n_expr.last then v.addn
+ end
+ end
+
+ assert v.indent == before
+ end
+
+ redef fun is_inlinable do
+ if not super then return false
+ if not collect_comments.is_empty then return false
+
+ if not n_expr.is_empty then
+ if n_expr.length > 1 then return false
+ if not n_expr.first.is_inlinable then return false
+ end
+
+ return true
+ end
+end
+
+redef class AIfExpr
+ redef fun accept_pretty_printer(v) do
+ var before = v.indent
+ var can_inline = v.can_inline(self)
+ v.visit n_kwif
+ v.adds
+
+ if v.can_inline(n_expr) then
+ v.visit n_expr
+ v.adds
+ else
+ v.visit n_expr
+ v.addn
+ v.addt
+ end
+
+ # skip comments before `then` token
+ while not v.current_token isa TKwthen do v.skip
+ v.consume "then"
+ var n_else = self.n_else
+
+ if can_inline then
+ v.adds
+ if n_then != null then v.visit n_then
+
+ if has_else(v) then
+ n_else.force_inline = true
+ v.adds
+ v.consume "else"
+ v.adds
+ v.visit n_else
+ else if n_then == null then
+ v.add "end"
+ end
+
+ v.skip_to last_token.last_real_token_in_line
+ else
+ v.finish_line
+ v.addn
+ v.indent += 1
+
+ if n_then != null then
+ if n_then isa ABlockExpr then
+ n_then.force_block = true
+ v.visit n_then
+ else
+ v.addt
+ v.visit n_then
+ v.addn
+ end
+ end
+
+ if has_else(v) then
+ while not v.current_token isa TKwelse do
+ v.consume v.current_token.text
+ end
+
+ v.indent -= 1
+ v.addt
+ v.consume "else"
+
+ if n_else isa AIfExpr then
+ n_else.force_block = true
+ v.adds
+ v.visit n_else
+ else
+ v.finish_line
+ v.addn
+ v.indent += 1
+
+ if n_else isa ABlockExpr then
+ n_else.force_block = true
+ v.visit n_else
+ else
+ v.addt
+ v.visit n_else
+ v.addn
+ end
+
+ if last_token isa TKwend then
+ v.catch_up last_token
+ v.indent -= 1
+ v.addt
+ v.consume "end"
+ else
+ v.indent -= 1
+ v.addt
+ v.add "end"
+ end
+ end
+ else
+ if last_token.location >= v.current_token.location then v.catch_up last_token
+ v.indent -= 1
+ v.addt
+ v.add "end"
+ if v.current_token isa TKwend then v.skip
+ end
+ end
+
+ assert v.indent == before
+ end
+
+ redef fun is_inlinable do
+ if not super then return false
+ if n_then != null and not n_then.is_inlinable then return false
+ var n_else = self.n_else
+ if (n_else isa ABlockExpr and not n_else.n_expr.is_empty) or
+ (not n_else isa ABlockExpr and n_else != null) then
+ return false
+ end
+ if not collect_comments.is_empty then return false
+ return true
+ end
+
+ # Does this `if` statement contains a `else` part?
+ private fun has_else(v: PrettyPrinterVisitor): Bool do
+ var n_else = n_else
+ if n_else == null then return false
+ var n_kwelse = collect_kwelse
+ if n_kwelse == null then return false
+
+ if n_else isa ABlockExpr then
+ var comments: Array[TComment]
+
+ if n_then == null then
+ comments = v.collect_comments(n_expr.last_token, n_else.last_token)
+ else
+ comments = v.collect_comments(n_then.last_token, n_else.last_token)
+ end
+
+ if not comments.is_empty then return true
+ return not n_else.n_expr.is_empty
+ end
+
+ return true
+ end
+
+ # Lookup for `else` token in `self`.
+ private fun collect_kwelse: nullable TKwelse do
+ var token = first_token
+
+ while token != last_token do
+ if token isa TKwelse then return token
+ token = token.next_token
+ end
+
+ return null
+ end
+end
+
+# Used to factorize work on loops.
+private class ALoopHelper
+ super AExpr
+
+ fun loop_block: nullable ANode is abstract
+ fun loop_label: nullable ANode is abstract
+
+ fun visit_loop_block(v: PrettyPrinterVisitor) do
+ var n_block = loop_block
+ v.finish_line
+ v.addn
+ v.indent += 1
+
+ if n_block isa ABlockExpr then
+ n_block.force_block = true
+ v.visit n_block
+ v.catch_up n_block.n_kwend
+ v.indent -= 1
+ v.addt
+ v.visit n_block.n_kwend
+ else
+ v.addt
+ v.visit n_block
+ v.addn
+ v.indent -= 1
+ v.addt
+ v.add "end"
+ end
+
+ if loop_label != null then
+ v.adds
+ v.visit loop_label
+ end
+ end
+
+ fun visit_loop_inline(v: PrettyPrinterVisitor) do
+ var n_block = loop_block
+ v.adds
+
+ if n_block isa ABlockExpr then
+ if n_block.n_expr.is_empty then
+ v.visit n_block.n_kwend
+ else
+ v.visit n_block.n_expr.first
+ v.current_token = n_block.n_kwend
+ v.skip
+ end
+ else
+ v.visit n_block
+ if v.current_token isa TKwend then v.skip
+ end
+
+ if loop_label != null then
+ v.adds
+ v.visit loop_label
+ end
+ end
+
+ redef fun is_inlinable do
+ var n_block = loop_block
+ if not super then return false
+ if n_block isa ABlockExpr and not n_block.is_inlinable then return false
+ if not collect_comments.is_empty then return false
+ return true
+ end
+end
+
+redef class ALoopExpr
+ super ALoopHelper
+
+ redef fun loop_block do return n_block
+ redef fun loop_label do return n_label
+
+ redef fun accept_pretty_printer(v) do
+ var can_inline = v.can_inline(self)
+ v.visit n_kwloop
+ if can_inline then visit_loop_inline v else visit_loop_block v
+ end
+end
+
+redef class AWhileExpr
+ super ALoopHelper
+
+ redef fun loop_block do return n_block
+ redef fun loop_label do return n_label
+
+ redef fun accept_pretty_printer(v) do
+ var can_inline = v.can_inline(self)
+ v.visit n_kwwhile
+ v.adds
+ v.visit n_expr
+ v.adds
+ v.visit n_kwdo
+ if can_inline then visit_loop_inline v else visit_loop_block v
+ end
+end
+
+redef class ADoExpr
+ super ALoopHelper
+
+ redef fun loop_block do return n_block
+ redef fun loop_label do return n_label
+
+ redef fun accept_pretty_printer(v) do
+ var can_inline = v.can_inline(self)
+ v.visit n_kwdo
+ if can_inline then visit_loop_inline v else visit_loop_block v
+ end
+end
+
+redef class AForExpr
+ super ALoopHelper
+
+ redef fun loop_block do return n_block
+ redef fun loop_label do return n_label
+
+ redef fun accept_pretty_printer(v) do
+ var can_inline = v.can_inline(self)
+ v.visit n_kwfor
+ v.adds
+
+ for n_id in n_ids do
+ v.visit n_id
+ if n_id != n_ids.last then v.add ", "
+ end
+
+ v.adds
+ v.consume "in"
+ v.adds
+ v.visit n_expr
+ v.adds
+ v.visit n_kwdo
+ if can_inline then visit_loop_inline v else visit_loop_block v
+ end
+end
+
+redef class ABreakExpr
+ redef fun accept_pretty_printer(v) do
+ v.visit n_kwbreak
+
+ if n_expr != null then
+ v.adds
+ v.visit n_expr
+ end
+
+ if n_label != null then
+ v.adds
+ v.visit n_label
+ end
+ end
+
+ redef fun is_inlinable do return true
+end
+
+redef class AContinueExpr
+ redef fun accept_pretty_printer(v) do
+ v.visit n_kwcontinue
+
+ if n_expr != null then
+ v.adds
+ v.visit n_expr
+ end
+
+ if n_label != null then
+ v.adds
+ v.visit n_label
+ end
+ end
+
+ redef fun is_inlinable do return true
+end
+
+# Calls
+
+redef class ASendExpr
+ redef fun is_inlinable do return true
+end
+
+redef class ACallExpr
+ redef fun accept_pretty_printer(v) do
+ var can_inline = v.can_inline(self)
+ v.visit_recv n_expr
+
+ if not n_expr isa AImplicitSelfExpr and not can_inline then
+ v.addn
+ v.addt
+ v.addt
+ end
+
+ v.visit n_id
+
+ if not n_args.n_exprs.is_empty then
+ if is_stmt and n_args.n_exprs.length == 1 then
+ v.adds
+ if v.current_token isa TOpar then v.skip
+ v.visit n_args.n_exprs.first
+ if v.current_token isa TCpar then v.skip
+ else
+ if v.current_token isa TOpar then
+ v.consume "("
+ else
+ v.adds
+ end
+
+ v.visit_list n_args.n_exprs
+ if v.current_token isa TCpar then v.consume ")"
+ end
+ end
+ end
+
+ # Is the call alone on its line?
+ fun is_stmt: Bool do return parent isa ABlockExpr
+end
+
+redef class ACallAssignExpr
+ redef fun accept_pretty_printer(v) do
+ v.visit_recv n_expr
+ v.visit n_id
+
+ if not n_args.n_exprs.is_empty then
+ v.consume "("
+ v.visit_list n_args.n_exprs
+ v.consume ")"
+ end
+
+ v.adds
+ v.consume "="
+ v.adds
+ v.visit n_value
+ end
+end
+
+redef class ACallReassignExpr
+ redef fun accept_pretty_printer(v) do
+ v.visit_recv n_expr
+ v.visit n_id
+
+ if not n_args.n_exprs.is_empty then
+ v.consume "("
+ v.visit_list n_args.n_exprs
+ v.consume ")"
+ end
+
+ v.adds
+ v.visit n_assign_op
+ v.adds
+ v.visit n_value
+ end
+end
+
+redef class ABraExpr
+ redef fun accept_pretty_printer(v) do
+ v.visit n_expr
+
+ if not n_args.n_exprs.is_empty then
+ v.consume "["
+ v.visit_list n_args.n_exprs
+ v.consume "]"
+ end
+ end
+end
+
+redef class ABraAssignExpr
+ redef fun accept_pretty_printer(v) do
+ v.visit n_expr
+
+ if not n_args.n_exprs.is_empty then
+ v.consume "["
+ v.visit_list n_args.n_exprs
+ v.consume "]"
+ end
+
+ v.adds
+ v.visit n_assign
+ v.adds
+ v.visit n_value
+ end
+end
+
+redef class ABraReassignExpr
+ redef fun accept_pretty_printer(v) do
+ v.visit n_expr
+
+ if not n_args.n_exprs.is_empty then
+ v.consume "["
+ v.visit_list n_args.n_exprs
+ v.consume "]"
+ end
+
+ v.adds
+ v.visit n_assign_op
+ v.adds
+ v.visit n_value
+ end
+end
+
+redef class AAssignMethid
+ redef fun accept_pretty_printer(v) do
+ v.visit n_id
+ v.visit n_assign
+ end
+end
+
+redef class ABraMethid
+ redef fun accept_pretty_printer(v) do
+ v.visit n_obra
+ v.visit n_cbra
+ end
+end
+
+redef class ABraassignMethid
+ redef fun accept_pretty_printer(v) do
+ v.visit n_obra
+ v.visit n_cbra
+ v.visit n_assign
+ end
+end
+
+redef class AInitExpr
+ redef fun accept_pretty_printer(v) do
+ if not n_expr isa AImplicitSelfExpr then
+ v.visit n_expr
+ v.consume "."
+ end
+
+ v.visit n_kwinit
+
+ if not n_args.n_exprs.is_empty then
+ v.consume "("
+ v.visit_list n_args.n_exprs
+ v.consume ")"
+ end
+ end
+end
+
+redef class ANewExpr
+ redef fun accept_pretty_printer(v) do
+ var can_inline = v.can_inline(self)
+ v.visit n_kwnew
+ v.adds
+ v.visit n_type
+
+ if n_id != null then
+ v.consume "."
+
+ if not can_inline then
+ v.addn
+ v.addt
+ v.addt
+ end
+
+ v.visit n_id
+ end
+
+ if not n_args.n_exprs.is_empty then
+ v.consume "("
+ v.visit_list n_args.n_exprs
+ v.consume ")"
+ end
+ end
+
+ redef fun is_inlinable do return true
+end
+
+# Attributes
+
+redef class AAttrExpr
+ redef fun accept_pretty_printer(v) do
+ v.visit_recv n_expr
+ v.visit n_id
+ end
+
+ redef fun is_inlinable do return true
+end
+
+redef class AAttrAssignExpr
+ redef fun accept_pretty_printer(v) do
+ v.visit_recv n_expr
+ v.visit n_id
+ v.adds
+ v.visit n_assign
+ v.adds
+ v.visit n_value
+ end
+end
+
+redef class AAttrReassignExpr
+ redef fun accept_pretty_printer(v) do
+ v.visit_recv n_expr
+ v.visit n_id
+ v.adds
+ v.visit n_assign_op
+ v.adds
+ v.visit n_value
+ end
+end
+
+# Exprs
+
+redef class AVardeclExpr
+ redef fun accept_pretty_printer(v) do
+ v.visit n_kwvar
+ v.adds
+ v.visit n_id
+
+ if n_type != null then
+ v.consume ":"
+ v.adds
+ v.visit n_type
+ end
+
+ if n_expr != null then
+ v.adds
+ v.consume "="
+ v.adds
+ v.visit n_expr
+ end
+ end
+
+ redef fun is_inlinable do return true
+end
+
+redef class AVarAssignExpr
+ redef fun accept_pretty_printer(v) do
+ v.visit n_id
+ v.adds
+ v.visit n_assign
+ v.adds
+ v.visit n_value
+ end
+end
+
+redef class AAssertExpr
+ redef fun accept_pretty_printer(v) do
+ var can_inline = v.can_inline(self)
+ v.visit n_kwassert
+
+ if n_id != null then
+ v.adds
+ v.visit n_id
+ v.consume ":"
+ end
+
+ v.adds
+ v.visit n_expr
+ var n_else = self.n_else
+
+ if n_else != null then
+ v.adds
+ v.consume "else"
+
+ if can_inline then
+ v.adds
+ v.visit n_else
+ else
+ v.addn
+
+ if n_else isa ABlockExpr then
+ v.indent += 1
+ n_else.force_block = true
+ v.visit n_else
+ v.indent -= 1
+ v.addt
+ v.visit n_else.n_kwend
+ else
+ v.indent += 1
+ v.addt
+ v.visit n_else
+ v.addn
+ v.indent -= 1
+ v.addt
+ v.add "end"
+ end
+ end
+ end
+ end
+
+ redef fun is_inlinable do
+ if not super then return false
+ if n_else != null and not n_else.is_inlinable then return false
+ return true
+ end
+end
+
+redef class AReturnExpr
+ redef fun accept_pretty_printer(v) do
+ v.visit n_kwreturn
+
+ if n_expr != null then
+ v.adds
+ v.visit n_expr
+ end
+ end
+end
+
+redef class ASuperExpr
+ redef fun accept_pretty_printer(v) do
+ v.visit n_kwsuper
+
+ if not n_args.n_exprs.is_empty then
+ if is_stmt and n_args.n_exprs.length == 1 then
+ v.adds
+ if v.current_token isa TOpar then v.skip
+ v.visit n_args.n_exprs.first
+ if v.current_token isa TCpar then v.skip
+ else
+ if v.current_token isa TOpar then
+ v.consume "("
+ else
+ v.adds
+ end
+
+ v.visit_list n_args.n_exprs
+ if v.current_token isa TCpar then v.consume ")"
+ end
+ end
+ end
+
+ # Is the call alone on its line?
+ fun is_stmt: Bool do return self.first_token.is_starting_line
+
+ redef fun is_inlinable do return true
+end
+
+redef class AOnceExpr
+ redef fun accept_pretty_printer(v) do
+ v.visit n_kwonce
+ v.adds
+ v.visit n_expr
+ end
+
+ redef fun is_inlinable do return true
+end
+
+redef class AAbortExpr
+ redef fun accept_pretty_printer(v) do v.visit n_kwabort
+ redef fun is_inlinable do return true
+end
+
+redef class ANotExpr
+ redef fun accept_pretty_printer(v) do
+ v.visit n_kwnot
+ v.adds
+ v.visit n_expr
+ end
+end
+
+redef class AAsCastExpr
+ redef fun accept_pretty_printer(v) do
+ v.visit n_expr
+ v.consume "."
+ v.visit n_kwas
+ v.visit n_opar
+ v.visit n_type
+ v.visit n_cpar
+ end
+end
+
+redef class AAsNotnullExpr
+ redef fun accept_pretty_printer(v) do
+ v.visit n_expr
+ v.consume "."
+ v.visit n_kwas
+ v.visit n_opar
+ v.visit n_kwnot
+ v.adds
+ v.visit n_kwnull
+ v.visit n_cpar
+ end
+end
+
+# Binops
+
+# Used to factorize work on Or, And, Implies and Binop expressions.
+private class ABinOpHelper
+ super AExpr
+
+ fun bin_expr1: AExpr is abstract
+ fun bin_expr2: AExpr is abstract
+
+ # Operator string
+ fun bin_op: String is abstract
+
+ redef fun accept_pretty_printer(v) do
+ var can_inline = v.can_inline(self)
+
+ if not can_inline then
+ if (self isa ABinopExpr and bin_expr1 isa ABinopExpr) or
+ (self isa AAndExpr and (bin_expr1 isa AAndExpr or bin_expr1 isa AOrExpr)) or
+ (self isa AOrExpr and (bin_expr1 isa AAndExpr or bin_expr1 isa AOrExpr))
+ then
+ bin_expr1.force_block = true
+ end
+ end
+
+ v.visit bin_expr1
+ v.adds
+ v.consume bin_op
+
+ if can_inline then
+ v.adds
+ v.visit bin_expr2
+ else
+ v.addn
+ v.addt
+ v.addt
+ v.visit bin_expr2
+ end
+ end
+end
+
+redef class AAndExpr
+ super ABinOpHelper
+
+ redef fun bin_expr1 do return n_expr
+ redef fun bin_expr2 do return n_expr2
+ redef fun bin_op do return "and"
+end
+
+redef class AOrExpr
+ super ABinOpHelper
+
+ redef fun bin_expr1 do return n_expr
+ redef fun bin_expr2 do return n_expr2
+ redef fun bin_op do return "or"
+end
+
+redef class AImpliesExpr
+ super ABinOpHelper
+
+ redef fun bin_expr1 do return n_expr
+ redef fun bin_expr2 do return n_expr2
+ redef fun bin_op do return "implies"
+end
+
+redef class ABinopExpr
+ super ABinOpHelper
+
+ redef fun bin_expr1 do return n_expr
+ redef fun bin_expr2 do return n_expr2
+end
+
+redef class AEqExpr
+ redef fun bin_op do return "=="
+end
+
+redef class AGeExpr
+ redef fun bin_op do return ">="
+end
+
+redef class AGgExpr
+ redef fun bin_op do return ">>"
+end
+
+redef class AGtExpr
+ redef fun bin_op do return ">"
+end
+
+redef class ALeExpr
+ redef fun bin_op do return "<="
+end
+
+redef class ALlExpr
+ redef fun bin_op do return "<<"
+end
+
+redef class ALtExpr
+ redef fun bin_op do return "<"
+end
+
+redef class AMinusExpr
+ redef fun bin_op do return "-"
+end
+
+redef class ANeExpr
+ redef fun bin_op do return "!="
+end
+
+redef class APercentExpr
+ redef fun bin_op do return "%"
+end
+
+redef class APlusExpr
+ redef fun bin_op do return "+"
+end
+
+redef class ASlashExpr
+ redef fun bin_op do return "/"
+end
+
+redef class AStarExpr
+ redef fun bin_op do return "*"
+end
+
+redef class AStarstarExpr
+ redef fun bin_op do return "**"
+end
+
+redef class AStarshipExpr
+ redef fun bin_op do return "<=>"
+end
+
+redef class AIsaExpr
+ redef fun accept_pretty_printer(v) do
+ v.visit n_expr
+ v.adds
+ v.consume "isa"
+ v.adds
+ v.visit n_type
+ end
+end
+
+redef class AOrElseExpr
+ redef fun accept_pretty_printer(v) do
+ v.visit n_expr
+ v.adds
+ v.consume "or"
+ v.adds
+ v.consume "else"
+ v.adds
+ v.visit n_expr2
+ end
+
+ redef fun is_inlinable do return true
+end
+
+# Syntax
+
+redef class AUminusExpr
+ redef fun accept_pretty_printer(v) do
+ v.consume "-"
+ v.visit n_expr
+ end
+end
+
+redef class ANullExpr
+ redef fun accept_pretty_printer(v) do v.visit n_kwnull
+ redef fun is_inlinable do return true
+end
+
+redef class AParExpr
+ redef fun accept_pretty_printer(v) do
+ v.visit n_opar
+ v.visit n_expr
+ v.visit n_cpar
+ end
+end
+
+redef class AArrayExpr
+ redef fun accept_pretty_printer(v) do
+ v.consume "["
+ v.visit_list n_exprs.n_exprs
+ v.consume "]"
+ end
+end
+
+redef class ACrangeExpr
+ redef fun accept_pretty_printer(v) do
+ v.visit n_obra
+ v.visit n_expr
+ v.consume ".."
+ v.visit n_expr2
+ v.visit n_cbra
+ end
+end
+
+redef class AOrangeExpr
+ redef fun accept_pretty_printer(v) do
+ v.visit n_obra
+ v.visit n_expr
+ v.consume ".."
+ v.visit n_expr2
+ v.visit n_cbra
+ end
+end
+
+# Strings
+
+redef class AStringFormExpr
+ redef fun accept_pretty_printer(v) do
+ var can_inline = v.can_inline(self)
+
+ if can_inline then
+ v.visit n_string
+ else
+ var text = n_string.text
+ var i = 0
+
+ while i < text.length do
+ v.add text[i].to_s
+
+ if v.current_length >= v.max_size and i <= text.length - 3 then
+ v.add "\" +"
+ v.addn
+ v.indent += 1
+ v.addt
+ v.indent -= 1
+ v.add "\""
+ end
+
+ i += 1
+ end
+
+ v.current_token = n_string.next_token
+ end
+ end
+end
+
+redef class ASuperstringExpr
+ redef fun accept_pretty_printer(v) do
+ for n_expr in n_exprs do v.visit n_expr
+ end
+
+ redef fun must_be_inline do
+ if super then return true
+
+ if not n_exprs.is_empty then
+ var first = n_exprs.first
+ return first isa AStringFormExpr and first.n_string.text.has_prefix("\"\"\"")
+ end
+
+ return false
+ end
+end