From: Alexandre Terrasa Date: Tue, 9 Dec 2014 22:47:55 +0000 (-0500) Subject: nitpretty: splits nitpretty into two modules X-Git-Tag: v0.7~63^2~3 X-Git-Url: http://nitlanguage.org nitpretty: splits nitpretty into two modules * module `src/pretty.nit`: the lib * module `src/nitpretty.nit`: the tool Fixes #836 Signed-off-by: Alexandre Terrasa --- diff --git a/src/nitpretty.nit b/src/nitpretty.nit index 141ef1b..c583b53 100644 --- a/src/nitpretty.nit +++ b/src/nitpretty.nit @@ -200,2093 +200,7 @@ # ~~~ 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") diff --git a/src/pretty.nit b/src/pretty.nit new file mode 100644 index 0000000..3b3cb90 --- /dev/null +++ b/src/pretty.nit @@ -0,0 +1,2103 @@ +# 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