From: Alexandre Terrasa Date: Mon, 4 Aug 2014 00:07:32 +0000 (-0400) Subject: src: Introduce nitpretty, a pretty printer for Nit X-Git-Tag: v0.6.9~53^2~2 X-Git-Url: http://nitlanguage.org src: Introduce nitpretty, a pretty printer for Nit Signed-off-by: Alexandre Terrasa --- diff --git a/.gitignore b/.gitignore index 8ed1a4e..65ac23a 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ EIFGENs .nit_compile* .nitunit +.nitpretty *.orig bin/nit* doc/stdlib diff --git a/src/nitpretty.nit b/src/nitpretty.nit new file mode 100644 index 0000000..d49f60d --- /dev/null +++ b/src/nitpretty.nit @@ -0,0 +1,2459 @@ +# 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. + +# `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 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 + +redef class ToolContext + var opt_dir = new OptionString("Working directory (default is '.nitpretty')", "--dir") + + var opt_output = new OptionString("Output name (default is pretty.nit)", "-o", + "--output") + + var opt_diff = new OptionBool("Show diff between source and output", "--diff") + + var opt_meld = new OptionBool("Show diff between source and output using meld", + "--meld") + + 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 res = p.read_all + p.wait + p.close + return res +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) + +toolcontext.tooldescription = "Usage: nitpretty [OPTION]... \n" + + "Pretty print Nit code from Nit source files." + +toolcontext.process_options args +var arguments = toolcontext.option_context.rest +# build model +var model = new Model +var mbuilder = new ModelBuilder(model, toolcontext) +var mmodules = mbuilder.parse(arguments) +mbuilder.run_phases + +if mmodules.is_empty then + print "Error: no module to pretty print" + return +end + +if not toolcontext.opt_check.value and mmodules.length > 1 then + print "Error: only --check option allow multiple modules" + return +end + +var dir = toolcontext.opt_dir.value or else ".nitpretty" +if not dir.file_exists then dir.mkdir +var v = new PrettyPrinterVisitor + +for mmodule in mmodules do + if not mbuilder.mmodule2nmodule.has_key(mmodule) 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 + + if toolcontext.opt_check.value then + var res = diff(nmodule.location.file.filename, file) + + if not res.is_empty then + print "Wrong formating for module {nmodule.location.file.filename}" + toolcontext.info(res, 1) + + if toolcontext.opt_meld.value then + sys.system "meld {nmodule.location.file.filename} {file}" + end + else + toolcontext.info("[OK] {nmodule.location.file.filename}", 1) + end + else + # write to file + var out = toolcontext.opt_output.value + if out != null then sys.system "cp {file} {out}" + + # open in meld + if toolcontext.opt_meld.value then + sys.system "meld {arguments.first} {file}" + return + end + + # show diff + if toolcontext.opt_diff.value then + var res = diff(arguments.first, file) + if not res.is_empty then print res + return + end + + # show pretty + if not toolcontext.opt_quiet.value then tpl.write_to sys.stdout + end +end