X-Git-Url: http://nitlanguage.org diff --git a/src/nitpretty.nit b/src/nitpretty.nit index 99834a8..17cc649 100644 --- a/src/nitpretty.nit +++ b/src/nitpretty.nit @@ -14,2369 +14,55 @@ # `nitpretty` is a tool able to pretty print Nit files. # -# Usage: -# -# nitpretty source.nit -# -# Main options: -# -# * `-o res.nit` output result into `res.nit` -# * `--diff` show diff between `source` and `res` -# * `--meld` open diff with `meld` -# * `--check` check the format of multiple source files -# * `--check --meld` perform `--check` and open `meld` for each difference -# -# ## Specification -# -# The specification of the pretty printing is described here. -# -# * Default indentation level is one `'\t'` character and -# is increased by one for each indentation level. -# * Default line max-size is 80. -# -# ### Comments -# -# There is many categories of comments: -# -# `Licence comments` are attached to the top of the file -# no blank line before, one after. -# -# # This is a licence comment -# -# # Documentation for module `foo` -# module foo -# -# `ADoc` are documentation comments attached to a `AModule`, `AClassdef`, `APropdef`. -# -# They are printed before the definition with a blank line before and no after -# at the same indentation level than the definition. -# -# # Documentation for module `foo` -# module foo -# -# # Documentation for class `Bar` -# class Bar -# # Documentation for method `baz` -# fun baz do end -# end -# -# `Block comments` are comments composed of one or more line rattached to nothing. -# They are displayed with one blank line before and after at current indent level. -# -# -# # block -# # comment -# -# -# `Attached comments` are comments attached to a production. -# They are printed as this. -# -# fun foo do # attached comment -# end -# -# `nitpretty` automatically remove multiple blanks between comments: -# -# # Licence -# # ... -# -# # Block comment -# -# ### Inlining -# -# Productions are automatically inlined when possible. -# -# Conditions: -# -# * the production must be syntactically inlinable -# * the inlined production length is less than `PrettyPrinterVisitor::max-size` -# * the production do not contains any comments -# -# ### Modules -# -# * There is a blank between the module declaration and its imports -# * There is no blank between imports and only one after -# * There is a blank between each extern block definition -# * There is a blank between each class definition -# * There is no blank line at the end of the module -# -# # Documentation for module `foo` -# module foo -# -# import a -# # import b -# import c -# -# # Documentation for class `Bar` -# class Bar end -# -# class Baz end # not a `ADoc` comment -# -# -# ### Classes -# -# * There is no blank between the class definition and its super-classes declarations -# * There is no blank between two inlined property definition -# * There is a blank between each block definition -# * There no blank line at the end of the class definition -# -# # Documentation for class `Bar` -# class Bar end -# -# class Baz -# super Bar -# -# fun a is abstract -# private fun b do end -# -# fun c do -# # ... -# end -# end -# -# Generic types have no espace after or before brackets and are separated by a comma and a space: -# -# class A[E: Type1, F: Type1] do end -# -# ### Blocks -# -# * Inlined productions have no blank lines between them -# * Block productions have a blank before and after -# -# var a = 10 -# var b = 0 -# -# if a > b then -# # is positive -# print "positive" -# end -# -# print "end" -# -# ### Calls and Binary Ops -# -# Arguments are always printed separated with a comma and a space: -# -# foo(a, b, c) -# -# Binary ops are always printed wrapped with spaces: -# -# var c = 1 + 2 -# -# Calls and binary ops can be splitted to fit the `max-size` constraint. -# Breaking priority is given to arguments declaration after the comma. -# -# return foo("aaaaaaaaaaaaaaaaaaaaaaaaaa", "bbbbbbbbbbbbbbbbbbbbbbbbbbb", -# "cccccccccccccccccccccccccc") -# -# Binary ops can also be broken to fit the `max-size` limit: -# -# return "aaaaaaaaaaaaaaaaaaaaaaaaaa" + "bbbbbbbbbbbbbbbbbbbbbbbbbbb" + -# "cccccccccccccccccccccccccc" -# -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 - v.visit n_opar - v.visit_list n_args - v.visit n_cpar - end - end -end - -redef class ATypeAtArg - redef fun accept_pretty_printer(v) do v.visit n_type -end - -redef class AExprAtArg - redef fun accept_pretty_printer(v) do v.visit n_expr -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 - if n_id != null then v.visit n_id - if n_id2 != null then v.visit n_id2 - - if n_type != null then - v.consume ":" - v.adds - v.visit n_type - end - - if n_readable != null then - v.adds - v.visit n_readable - end - - if n_writable != null then - v.adds - v.visit n_writable - 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 - 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 - 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 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 ADeferredMethPropdef - redef fun accept_pretty_printer(v) do - super - if n_annotations == null then - while not v.current_token isa TKwis do v.skip - v.consume "is" - v.adds - while not v.current_token isa TKwabstract do v.skip - v.consume "abstract" - end - v.finish_line - v.addn - end -end - -redef class AExternPropdef - redef fun accept_pretty_printer(v) do - super - while v.current_token isa TEol do v.skip - - if v.current_token isa TKwis then - v.consume "is" - v.adds - end - - if v.current_token isa TKwextern then - v.consume "extern" - v.adds - end - - if n_extern != null then v.visit n_extern - if n_extern_calls != null then v.visit n_extern_calls - if n_extern_code_block != null then v.visit n_extern_code_block - v.finish_line - v.addn - end - - redef fun is_inlinable do - if not super 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 - return true - end - - redef fun must_be_inline do - if super then return true - return n_extern != null - end -end - -redef class AInternMethPropdef - redef fun accept_pretty_printer(v) do - super - v.consume "is" - v.adds - v.consume "intern" - v.finish_line - v.addn - end -end - -redef class AConcreteMethPropdef - redef fun accept_pretty_printer(v) do - var before = v.indent - var can_inline = v.can_inline(self) - super - 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 -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 - -redef class AAble - redef fun accept_pretty_printer(v) do - if n_kwredef != null then - v.visit n_kwredef - v.adds - end - - if not n_visibility isa APublicVisibility then - v.visit n_visibility - v.adds - end - end -end - -redef class AReadAble - redef fun accept_pretty_printer(v) do - super - v.visit n_kwreadable - end -end - -redef class AWriteAble - redef fun accept_pretty_printer(v) do - super - v.visit n_kwwritable - 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 - var force_inline = self.force_inline - 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 +# See `man nitpretty` for more infos. +import pretty redef class ToolContext + # The working directory used to store temp files. var opt_dir = new OptionString("Working directory (default is '.nitpretty')", "--dir") + # Output pretty printed code with this filename. var opt_output = new OptionString("Output name (default is pretty.nit)", "-o", "--output") + # Show diff between source and pretty printed code. var opt_diff = new OptionBool("Show diff between source and output", "--diff") + # Show diff between source and pretty printed code using meld. var opt_meld = new OptionBool("Show diff between source and output using meld", "--meld") + # --line-width + var opt_line_width = new OptionInt("Maximum length of lines (use 0 to disable automatic line breaks)", 80, "--line-width") + + # --no-inline + var opt_no_inline = new OptionBool("Disable automatic one-liners", "--no-inline") + + # Break too long string literals. + var opt_break_str = new OptionBool("Break too long string literals", "--break-strings") + + # Force `do` on the same line as the method signature. + var opt_inline_do = new OptionBool("Force do keyword on the same line as the method signature", + "--inline-do") + + # Force formatting on empty lines. + # + # By default empty lines are kept as they were typed in the file. + # When enabling this option, `nitpretty` will decide where to break lines + # and will put empty lines to separate properties and code blocks. + var opt_skip_empty = new OptionBool("Force formatting of empty lines", "--skip-empty") + + # Check formatting instead of pretty printing. + # + # This option creates a temporary pretty printed file then checks if the + # output of the diff command on the source file and the pretty printed one is + # empty. var opt_check = new OptionBool("Check format of Nit source files", "--check") end # Return result from diff between `file1` and `file2`. private fun diff(file1, file2: String): String do - var p = new IProcess("diff", "-u", file1, file2) + var p = new ProcessReader("diff", "-u", file1, file2) var res = p.read_all p.wait p.close @@ -2386,9 +72,12 @@ end # process options var toolcontext = new ToolContext -toolcontext.option_context. - add_option(toolcontext.opt_dir, toolcontext.opt_output, toolcontext.opt_diff, - toolcontext.opt_meld, toolcontext.opt_check) +var opts = toolcontext.option_context +opts.add_option(toolcontext.opt_dir, toolcontext.opt_output) +opts.add_option(toolcontext.opt_diff, toolcontext.opt_meld, toolcontext.opt_check) +opts.add_option(toolcontext.opt_line_width, toolcontext.opt_break_str, toolcontext.opt_inline_do) +opts.add_option(toolcontext.opt_no_inline) +opts.add_option(toolcontext.opt_skip_empty) toolcontext.tooldescription = "Usage: nitpretty [OPTION]... \n" + "Pretty print Nit code from Nit source files." @@ -2398,7 +87,7 @@ var arguments = toolcontext.option_context.rest # build model var model = new Model var mbuilder = new ModelBuilder(model, toolcontext) -var mmodules = mbuilder.parse(arguments) +var mmodules = mbuilder.parse_full(arguments) mbuilder.run_phases if mmodules.is_empty then @@ -2415,13 +104,26 @@ var dir = toolcontext.opt_dir.value or else ".nitpretty" if not dir.file_exists then dir.mkdir var v = new PrettyPrinterVisitor +v.max_size = toolcontext.opt_line_width.value +if toolcontext.opt_break_str.value then + v.break_strings = true +end +if toolcontext.opt_inline_do.value then + v.inline_do = true +end +if toolcontext.opt_skip_empty.value then + v.skip_empty = true +end +if toolcontext.opt_no_inline.value then + v.no_inline = true +end + for mmodule in mmodules do - if not mbuilder.mmodule2nmodule.has_key(mmodule) then + var nmodule = mbuilder.mmodule2node(mmodule) + if nmodule == null then print " Error: no source file for module {mmodule}" return end - - var nmodule = mbuilder.mmodule2nmodule[mmodule] var file = "{dir}/{mmodule.name}.nit" var tpl = v.pretty_nmodule(nmodule) tpl.write_to_file file