src: Introduce nitpretty, a pretty printer for Nit
[nit.git] / src / nitpretty.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15 # `nitpretty` is a tool able to pretty print Nit files.
16 #
17 # Usage:
18 #
19 # nitpretty source.nit
20 #
21 # Main options:
22 #
23 # * `-o res.nit` output result into `res.nit`
24 # * `--diff` show diff between `source` and `res`
25 # * `--meld` open diff with `meld`
26 # * `--check` check the format of multiple source files
27 # * `--check --meld` perform `--check` and open `meld` for each difference
28 #
29 # ## Specification
30 #
31 # The specification of the pretty printing is described here.
32 #
33 # * Default indentation level is one `'\t'` character and
34 # is increased by one for each indentation level.
35 # * Default line max-size is 80.
36 #
37 # ### Comments
38 #
39 # There is many categories of comments:
40 #
41 # `Licence comments` are attached to the top of the file
42 # no blank line before, one after.
43 #
44 # # This is a licence comment
45 #
46 # # Documentation for module `foo`
47 # module foo
48 #
49 # `ADoc` are documentation comments attached to a `AModule`, `AClassdef`, `APropdef`.
50 #
51 # They are printed before the definition with a blank line before and no after
52 # at the same indentation level than the definition.
53 #
54 # # Documentation for module `foo`
55 # module foo
56 #
57 # # Documentation for class `Bar`
58 # class Bar
59 # # Documentation for method `baz`
60 # fun baz do end
61 # end
62 #
63 # `Block comments` are comments composed of one or more line rattached to nothing.
64 # They are displayed with one blank line before and after at current indent level.
65 #
66 # <blank>
67 # # block
68 # # comment
69 # <blank>
70 #
71 # `Attached comments` are comments attached to a production.
72 # They are printed as this.
73 #
74 # fun foo do # attached comment
75 # end
76 #
77 # `nitpretty` automatically remove multiple blanks between comments:
78 #
79 # # Licence
80 # # ...
81 # <blank>
82 # # Block comment
83 #
84 # ### Inlining
85 #
86 # Productions are automatically inlined when possible.
87 #
88 # Conditions:
89 #
90 # * the production must be syntactically inlinable
91 # * the inlined production length is less than `PrettyPrinterVisitor::max-size`
92 # * the production do not contains any comments
93 #
94 # ### Modules
95 #
96 # * There is a blank between the module declaration and its imports
97 # * There is no blank between imports and only one after
98 # * There is a blank between each extern block definition
99 # * There is a blank between each class definition
100 # * There is no blank line at the end of the module
101 #
102 # # Documentation for module `foo`
103 # module foo
104 #
105 # import a
106 # # import b
107 # import c
108 #
109 # # Documentation for class `Bar`
110 # class Bar end
111 #
112 # class Baz end # not a `ADoc` comment
113 #
114 #
115 # ### Classes
116 #
117 # * There is no blank between the class definition and its super-classes declarations
118 # * There is no blank between two inlined property definition
119 # * There is a blank between each block definition
120 # * There no blank line at the end of the class definition
121 #
122 # # Documentation for class `Bar`
123 # class Bar end
124 #
125 # class Baz
126 # super Bar
127 #
128 # fun a is abstract
129 # private fun b do end
130 #
131 # fun c do
132 # # ...
133 # end
134 # end
135 #
136 # Generic types have no espace after or before brackets and are separated by a comma and a space:
137 #
138 # class A[E: Type1, F: Type1] do end
139 #
140 # ### Blocks
141 #
142 # * Inlined productions have no blank lines between them
143 # * Block productions have a blank before and after
144 #
145 # var a = 10
146 # var b = 0
147 #
148 # if a > b then
149 # # is positive
150 # print "positive"
151 # end
152 #
153 # print "end"
154 #
155 # ### Calls and Binary Ops
156 #
157 # Arguments are always printed separated with a comma and a space:
158 #
159 # foo(a, b, c)
160 #
161 # Binary ops are always printed wrapped with spaces:
162 #
163 # var c = 1 + 2
164 #
165 # Calls and binary ops can be splitted to fit the `max-size` constraint.
166 # Breaking priority is given to arguments declaration after the comma.
167 #
168 # return foo("aaaaaaaaaaaaaaaaaaaaaaaaaa", "bbbbbbbbbbbbbbbbbbbbbbbbbbb",
169 # "cccccccccccccccccccccccccc")
170 #
171 # Binary ops can also be broken to fit the `max-size` limit:
172 #
173 # return "aaaaaaaaaaaaaaaaaaaaaaaaaa" + "bbbbbbbbbbbbbbbbbbbbbbbbbbb" +
174 # "cccccccccccccccccccccccccc"
175 #
176 module nitpretty
177
178 import template
179 import toolcontext
180 import modelbuilder
181 import astutil
182
183 # The `PrettyPrinterVisitor` is used to visit a node and pretty print it.
184 #
185 # The main method here is `visit` that performs the pretty printing of a `ANode`.
186 #
187 # Because some tokens like `TComment` are not stored in the AST,
188 # we visit the AST like traditionnal visitor and also maintain a
189 # pointer to the `current_token` (actually the next one to be printed).
190 #
191 # Visited productions are in charges to move the token pointer using methods such as:
192 #
193 # * `consume`: print `current_token` and move to the next one
194 # * `skip`: move to the next token without printing the current one
195 # * `skip_to`: move to a specified token skipping all the tokens before
196 # * `catch_up`: consume all the tokens between `current_token` and a specified token
197 # * `finish_line`: consume all the tokens between `current_token` and the end of line
198 class PrettyPrinterVisitor
199 # Pretty print `n`.
200 fun pretty(n: ANode): Template do
201 clear
202 n.parentize_tokens
203
204 if n isa Prod then
205 current_token = n.first_token
206 visit n
207 else if n isa Token then
208 var p = n.parent
209
210 while p != null and not p isa Prod do
211 p = p.parent
212 end
213
214 current_token = p.first_token
215 visit p
216 end
217
218 return tpl.as(not null)
219 end
220
221 # Pretty print the whole `nmodule` with comments before and after.
222 fun pretty_nmodule(nmodule: AModule): Template do
223 clear
224 nmodule.parentize_tokens
225 current_token = nmodule.location.file.first_token
226 visit nmodule
227 catch_up nmodule.location.file.last_token
228 tpl.add "\n"
229 return tpl.as(not null)
230 end
231
232 # Prepare `self` for a new visit.
233 private fun clear do
234 tpl = new Template
235 current_token = null
236 indent = 0
237 current_length = 0
238 previous_length = 0
239 wait_addn = 0
240 end
241
242 # Visit `n` if not null.
243 fun visit(n: nullable ANode) do
244 if n == null then return
245 n.accept_pretty_printer self
246 end
247
248 # Visit a list of `Anode`.
249 fun visit_list(n: nullable ANodes[ANode]) do
250 if n == null then return
251 n.accept_pretty_printer self
252 end
253
254 # Is the node inlinable and can fit on the line.
255 fun can_inline(n: nullable ANode): Bool do
256 if n == null then return true
257 if n.must_be_inline then return true
258 if n.must_be_block then return false
259 # check length
260 if n.collect_length + current_length > max_size then return false
261 # check block is inlinable
262 return n.is_inlinable
263 end
264
265 # Collect all `TComment` between `from` and `to`.
266 fun collect_comments(from: nullable ANode, to: nullable ANode): Array[TComment] do
267 var res = new Array[TComment]
268 if from isa Prod then from = from.first_token
269 if to isa Prod then to = to.first_token
270 if from == null or to == null then return res
271
272 while from != to do
273 if from isa TComment then res.add from
274 from = from.as(Token).next_token
275 end
276
277 return res
278 end
279
280 # Token under cursor.
281 #
282 # This is the next token to visit.
283 var current_token: nullable Token = null
284
285 # Skip the `current_token`.
286 fun skip do current_token = current_token.next_token
287
288 # Skip `current_token` until the end of line.
289 fun skip_line do current_token = current_token.last_real_token_in_line
290
291 # Skip `current_token` until `target` is reached.
292 fun skip_to(target: nullable Token) do
293 if target == null then return
294 while current_token != target do skip
295 end
296
297 # Visit `current_token`.
298 fun consume(token: String) do
299 assert current_token.text == token
300 visit current_token
301 end
302
303 # Is there token to visit between `current_token` and `target`?
304 fun need_catch_up(target: nullable Token): Bool do
305 if target == null then return false
306 return current_token != target
307 end
308
309 # Visit all tokens between `current_token` and `target`.
310 fun catch_up(target: nullable ANode) do
311 if target == null then return
312 if current_token == null then return
313 var token: Token
314 if target isa Token then
315 token = target
316 else if target isa Prod then
317 token = target.first_token.as(not null)
318 else
319 abort
320 end
321 assert current_token.location <= token.location
322 while current_token != token do visit current_token
323 end
324
325 # Visit all tokens between `current_token` and the end of line.
326 fun finish_line do
327 if current_token isa TComment then
328 adds
329 visit current_token
330 end
331
332 while current_token isa TEol do skip
333 end
334
335 # The template under construction.
336 private var tpl: nullable Template = null
337
338 # Current indent level.
339 var indent = 0
340
341 # Size of a tabulation in spaces.
342 var tab_size = 8
343
344 # Max line size.
345 var max_size = 80
346
347 # Length of the current line.
348 var current_length = 0
349
350 # Length of the previous line.
351 var previous_length = 0
352
353 # Is the last line a blank line?
354 fun last_line_is_blank: Bool do return previous_length == 0
355
356 # Add `t` to current template.
357 fun add(t: String) do
358 if t.is_empty then return
359 while wait_addn > 0 do
360 tpl.add "\n"
361 wait_addn -= 1
362 end
363 tpl.add t
364 current_length += t.length
365 end
366
367 # Add a `'\n'`.
368 fun addn do
369 if current_length == 0 and last_line_is_blank then return
370 previous_length = current_length
371 current_length = 0
372 wait_addn += 1
373 end
374
375 # End of line chars are stored until another char is added.
376 # This avoid empty files with only a '`\n`'.
377 private var wait_addn = 0
378
379 # Add `'\t' * indent`.
380 fun addt do add "\t" * indent
381
382 # Add a space.
383 fun adds do add " "
384
385 fun visit_recv(n_expr: AExpr) do
386 if not n_expr isa AImplicitSelfExpr then
387 visit n_expr
388 consume "."
389 end
390 end
391 end
392
393 # Base framework redefs
394
395 redef class ANodes[E]
396 private fun accept_pretty_printer(v: PrettyPrinterVisitor) do
397 for e in self do
398 var e_can_inline = v.can_inline(e)
399
400 if e != first then
401 if not e_can_inline then
402 v.add ","
403 v.addn
404 v.addt
405 v.addt
406 else
407 v.add ", "
408 end
409 end
410
411 v.visit e
412 end
413 end
414 end
415
416 redef class ANode
417 # Start visit of `self` using a `PrettyPrinterVisitor`
418 private fun accept_pretty_printer(v: PrettyPrinterVisitor) is abstract
419
420 # Collect the length (in `Char`) of the node.
421 private fun collect_length: Int is abstract
422
423 # Is `self` printable in one line?
424 private fun is_inlinable: Bool do return true
425
426 # Force `self` to be rendered as a block.
427 private var force_block = false
428
429 # Does `self` have to be rendered as a block?
430 private fun must_be_block: Bool do return force_block
431
432 # Force `self` to be rendered as a line.
433 private var force_inline = false
434
435 # Does `self` have be rendered as a line?
436 private fun must_be_inline: Bool do
437 if parent != null and parent.must_be_inline then return true
438 return force_inline
439 end
440
441 # Does `self` was written in one line before transformation?
442 private fun was_inline: Bool is abstract
443 end
444
445 redef class Token
446 redef fun accept_pretty_printer(v) do
447 v.add text.trim
448 v.current_token = next_token
449 end
450
451 redef fun collect_length do return text.length
452 redef fun is_inlinable do return true
453 redef fun was_inline do return true
454 end
455
456 redef class Prod
457 redef fun accept_pretty_printer(v) do v.visit first_token
458
459 # The token where the production really start (skipping ADoc).
460 private fun start_token: nullable Token do return first_token
461
462 # Collect all `TComment` contained in the production
463 # (between `start_token` and `end_token`).
464 private fun collect_comments: Array[TComment] do
465 var res = new Array[TComment]
466 if start_token == null or last_token == null then return res
467 var token = start_token
468
469 while token != last_token do
470 if token isa TComment then res.add token
471 token = token.next_token
472 end
473
474 return res
475 end
476
477 redef fun collect_length do
478 var res = 0
479 if start_token == null or last_token == null then return res
480 var token = start_token
481
482 while token != last_token do
483 res += token.text.length
484 token = token.next_token
485 end
486
487 res += token.text.length
488 return res
489 end
490
491 redef fun was_inline do
492 return first_token.location.line_start == last_token.location.line_end
493 end
494 end
495
496 # Comments
497
498 redef class TComment
499 redef fun accept_pretty_printer(v) do
500 if is_adoc then
501 v.addt
502 super
503 v.addn
504 return
505 end
506
507 if is_licence then
508 super
509 v.addn
510 if is_last_in_group then v.addn
511 return
512 end
513
514 if is_orphan then
515 v.addn
516 v.addt
517 super
518 v.addn
519 v.addn
520 return
521 end
522
523 if is_inline then
524 if next_token isa TComment and is_first_in_group then v.addn
525 v.addt
526 super
527 v.addn
528 var prev_token = self.prev_token
529 if prev_token isa TComment and prev_token.is_inline and is_last_in_group then v.addn
530 return
531 end
532
533 super
534 end
535
536 # Is `self` part of an `ADoc`?
537 private fun is_adoc: Bool do return parent isa ADoc and parent.parent != null
538
539 # Is `self` part of a licence?
540 private fun is_licence: Bool do
541 var prev_token = self.prev_token
542
543 if prev_token == null then
544 return true
545 else if prev_token isa TComment then
546 return prev_token.is_licence
547 else
548 return false
549 end
550 end
551
552 # Is `self` starts and ends its line?
553 private fun is_inline: Bool do
554 return self == first_real_token_in_line and self == last_real_token_in_line
555 end
556
557 # Is `self` an orphan line (blank before and after)?
558 private fun is_orphan: Bool do
559 return prev_token isa TEol and
560 (prev_token.prev_token isa TEol or prev_token.prev_token isa TComment) and
561 next_token isa TEol
562 end
563
564 # Is `self` the first comment of a group of comment?
565 private fun is_first_in_group: Bool do return not prev_token isa TComment
566
567 # Is `self` the last comment of a group of comments?
568 private fun is_last_in_group: Bool do return not next_token isa TComment
569 end
570
571 redef class ADoc
572 redef fun accept_pretty_printer(v) do for comment in n_comment do v.visit comment
573 redef fun is_inlinable do return n_comment.length <= 1
574 end
575
576 # Annotations
577
578 redef class AAnnotations
579 redef fun accept_pretty_printer(v) do
580 v.adds
581 v.consume "is"
582
583 if v.can_inline(self) then
584 v.adds
585 for n_item in n_items do
586 v.visit n_item
587 if n_item != n_items.last then
588 v.add ", "
589 end
590 end
591 v.finish_line
592 else if n_items.length > 1 then
593 v.addn
594 v.indent += 1
595
596 for n_item in n_items do
597 v.addt
598 v.visit n_item
599 v.finish_line
600 v.addn
601 end
602
603 v.indent -= 1
604 end
605 if not was_inline and v.current_token isa TKwend then v.skip
606 end
607
608 redef fun is_inlinable do
609 if not super then return false
610 for annot in n_items do if not annot.is_inlinable then return false
611 return true
612 end
613 end
614
615 redef class AAnnotation
616 redef fun accept_pretty_printer(v) do
617 v.visit n_atid
618 if not n_args.is_empty then
619 v.visit n_opar
620 v.visit_list n_args
621 v.visit n_cpar
622 end
623 end
624 end
625
626 redef class ATypeAtArg
627 redef fun accept_pretty_printer(v) do v.visit n_type
628 end
629
630 redef class AExprAtArg
631 redef fun accept_pretty_printer(v) do v.visit n_expr
632 end
633
634 # Modules
635
636 redef class AModule
637 redef fun accept_pretty_printer(v) do
638 v.catch_up start_token
639 v.visit n_moduledecl
640
641 if not n_imports.is_empty then
642 v.addn
643
644 for n_import in n_imports do
645 v.catch_up n_import
646 v.visit n_import
647 end
648 end
649
650 if not n_extern_code_blocks.is_empty then
651 v.addn
652
653 for n_extern_block in n_extern_code_blocks do
654 v.catch_up n_extern_block
655 v.visit n_extern_block
656 v.addn
657 if n_extern_block != n_extern_code_blocks.last then v.addn
658 end
659
660 if not n_classdefs.is_empty then v.addn
661 end
662
663 if not n_classdefs.is_empty then
664 v.addn
665
666 for n_classdef in n_classdefs do
667 v.catch_up n_classdef
668 v.visit n_classdef
669 if n_classdef != n_classdefs.last then v.addn
670 end
671 end
672
673 assert v.indent == 0
674 end
675
676 # Skip doc if any.
677 redef fun start_token do
678 if n_moduledecl != null then return n_moduledecl.first_token
679 if not n_imports.is_empty then return n_imports.first.first_token
680 if not n_classdefs.is_empty then return n_classdefs.first.first_token
681 return first_token
682 end
683
684 redef fun is_inlinable do return false
685 end
686
687 redef class AModuledecl
688 redef fun accept_pretty_printer(v) do
689 v.visit n_doc
690 v.visit n_kwmodule
691 v.adds
692 v.visit n_name
693
694 if n_annotations != null then
695 var annot_inline = v.can_inline(n_annotations)
696 v.visit n_annotations
697
698 if not annot_inline then
699 if v.current_token isa TKwend then
700 v.consume "end"
701 v.finish_line
702 else
703 v.add "end"
704 end
705 end
706 end
707
708 v.finish_line
709 v.addn
710 end
711 end
712
713 redef class AModuleName
714 redef fun accept_pretty_printer(v) do
715 for path in n_path do
716 v.visit path
717 v.add "::"
718 end
719
720 v.visit n_id
721 end
722 end
723
724 redef class ANoImport
725 redef fun accept_pretty_printer(v) do
726 v.visit n_kwimport
727 v.adds
728 v.visit n_kwend
729 v.finish_line
730 v.addn
731 end
732 end
733
734 redef class AStdImport
735 redef fun accept_pretty_printer(v) do
736 if not n_visibility isa APublicVisibility then
737 v.visit n_visibility
738 v.adds
739 end
740
741 v.visit n_kwimport
742 v.adds
743 v.visit n_name
744 v.finish_line
745 v.addn
746 end
747 end
748
749 # Classes
750
751 redef class AClassdef
752 redef fun accept_pretty_printer(v) do
753 for n_propdef in n_propdefs do
754 v.catch_up n_propdef
755
756 if n_propdef.n_doc != null or not v.can_inline(n_propdef) then
757 if n_propdef != n_propdefs.first then v.addn
758 v.visit n_propdef
759 if n_propdef != n_propdefs.last then v.addn
760 else
761 v.visit n_propdef
762 end
763 end
764 end
765 end
766
767 redef class AStdClassdef
768 redef fun accept_pretty_printer(v) do
769 v.visit n_doc
770 var can_inline = v.can_inline(self)
771
772 if not n_visibility isa APublicVisibility then
773 v.visit n_visibility
774 v.adds
775 end
776
777 if n_kwredef != null then
778 v.visit n_kwredef
779 v.adds
780 end
781
782 v.visit n_classkind
783 v.adds
784 v.visit n_id
785
786 if not n_formaldefs.is_empty then
787 v.consume "["
788 v.visit_list n_formaldefs
789 v.consume "]"
790 end
791
792 if n_extern_code_block != null then
793 v.adds
794 v.visit n_extern_code_block
795 end
796
797 if can_inline then
798 v.adds
799
800 if not n_superclasses.is_empty then
801 for n_superclass in n_superclasses do
802 v.visit n_superclass
803 v.adds
804 end
805 end
806 else
807 v.finish_line
808 v.addn
809 v.indent += 1
810
811 for n_superclass in n_superclasses do
812 v.catch_up n_superclass
813 v.addt
814 v.visit n_superclass
815 v.finish_line
816 v.addn
817 end
818
819 if not n_superclasses.is_empty and not n_propdefs.is_empty then
820 v.addn
821 end
822
823 super
824 v.catch_up n_kwend
825 v.indent -= 1
826 end
827
828 v.visit n_kwend
829 v.finish_line
830 v.addn
831 assert v.indent == 0
832 end
833
834 redef fun is_inlinable do
835 if not super then return false
836 if not n_propdefs.is_empty then return false
837 if n_superclasses.length > 1 then return false
838 if not collect_comments.is_empty then return false
839 return true
840 end
841
842 redef fun start_token do
843 if not n_visibility isa APublicVisibility then return n_visibility.first_token
844 if n_kwredef != null then return n_kwredef
845 return n_classkind.first_token
846 end
847 end
848
849 redef class AAbstractClasskind
850 redef fun accept_pretty_printer(v) do
851 v.visit n_kwabstract
852 v.adds
853 v.visit n_kwclass
854 end
855 end
856
857 redef class AExternClasskind
858 redef fun accept_pretty_printer(v) do
859 v.visit n_kwextern
860 v.adds
861 v.visit n_kwclass
862 end
863 end
864
865 redef class AFormaldef
866 redef fun accept_pretty_printer(v) do
867 v.visit n_id
868
869 if n_type != null then
870 v.consume ":"
871 v.adds
872 v.visit n_type
873 end
874 end
875 end
876
877 redef class AType
878 redef fun accept_pretty_printer(v) do
879 if n_kwnullable != null then
880 v.visit n_kwnullable
881 v.adds
882 end
883
884 v.visit n_id
885
886 if not n_types.is_empty then
887 v.consume "["
888 v.visit_list n_types
889 v.consume "]"
890 end
891 end
892 end
893
894 redef class ASuperclass
895 redef fun accept_pretty_printer(v) do
896 v.visit n_kwsuper
897 v.adds
898 v.visit n_type
899 end
900 end
901
902 # Properties
903
904 redef class APropdef
905 redef fun accept_pretty_printer(v) do
906 v.visit n_doc
907 v.addt
908
909 if not n_visibility isa APublicVisibility then
910 v.visit n_visibility
911 v.adds
912 end
913
914 if n_kwredef != null then
915 v.visit n_kwredef
916 v.adds
917 end
918 end
919
920 redef fun start_token do
921 if n_doc == null then return super
922 return n_doc.last_token.next_token
923 end
924 end
925
926 redef class AAttrPropdef
927 redef fun accept_pretty_printer(v) do
928 super
929 v.visit n_kwvar
930 v.adds
931 if n_id != null then v.visit n_id
932 if n_id2 != null then v.visit n_id2
933
934 if n_type != null then
935 v.consume ":"
936 v.adds
937 v.visit n_type
938 end
939
940 if n_readable != null then
941 v.adds
942 v.visit n_readable
943 end
944
945 if n_writable != null then
946 v.adds
947 v.visit n_writable
948 end
949
950 if n_expr != null then
951 v.adds
952 v.consume "="
953 v.adds
954 v.visit n_expr
955 end
956
957 if n_annotations != null then v.visit n_annotations
958 v.finish_line
959 v.addn
960 end
961
962 redef fun first_token do
963 if n_doc != null then return n_doc.first_token
964 if not n_visibility isa APublicVisibility then return n_visibility.first_token
965 if n_kwredef != null then return n_kwredef
966 return n_kwvar
967 end
968
969 redef fun is_inlinable do return true
970 end
971
972 redef class ATypePropdef
973 redef fun accept_pretty_printer(v) do
974 super
975 v.visit n_kwtype
976 v.adds
977 v.visit n_id
978 v.consume ":"
979 v.adds
980 v.visit n_type
981 v.finish_line
982 v.addn
983 end
984
985 redef fun is_inlinable do return true
986 end
987
988 redef class AMethPropdef
989 redef fun accept_pretty_printer(v) do
990 super
991 if n_kwinit != null then v.visit n_kwinit
992 if n_kwmeth != null then v.visit n_kwmeth
993 if n_kwnew != null then v.visit n_kwnew
994
995 if not n_methid == null then
996 v.adds
997 v.visit n_methid
998 end
999
1000 v.visit n_signature
1001
1002 if n_annotations != null then
1003 v.visit n_annotations
1004 else
1005 v.adds
1006 end
1007 end
1008
1009 # Can be inlined if:
1010 # * block is empty or can be inlined
1011 # * contains no comments
1012 redef fun is_inlinable do
1013 if not super then return false
1014 if n_annotations != null and not n_annotations.is_inlinable then return false
1015 if n_block != null and not n_block.is_inlinable then return false
1016 if not collect_comments.is_empty then return false
1017 return true
1018 end
1019 end
1020
1021 redef class AMainMethPropdef
1022 redef fun accept_pretty_printer(v) do
1023 v.visit n_block
1024 v.addn
1025 end
1026 end
1027
1028 redef class ADeferredMethPropdef
1029 redef fun accept_pretty_printer(v) do
1030 super
1031 if n_annotations == null then
1032 while not v.current_token isa TKwis do v.skip
1033 v.consume "is"
1034 v.adds
1035 while not v.current_token isa TKwabstract do v.skip
1036 v.consume "abstract"
1037 end
1038 v.finish_line
1039 v.addn
1040 end
1041 end
1042
1043 redef class AExternPropdef
1044 redef fun accept_pretty_printer(v) do
1045 super
1046 while v.current_token isa TEol do v.skip
1047
1048 if v.current_token isa TKwis then
1049 v.consume "is"
1050 v.adds
1051 end
1052
1053 if v.current_token isa TKwextern then
1054 v.consume "extern"
1055 v.adds
1056 end
1057
1058 if n_extern != null then v.visit n_extern
1059 if n_extern_calls != null then v.visit n_extern_calls
1060 if n_extern_code_block != null then v.visit n_extern_code_block
1061 v.finish_line
1062 v.addn
1063 end
1064
1065 redef fun is_inlinable do
1066 if not super then return false
1067 if n_block != null and not n_block.is_inlinable then return false
1068 if n_extern_calls != null and not n_extern_calls.is_inlinable then return false
1069 if n_extern_code_block != null and not n_extern_code_block.is_inlinable then return false
1070 return true
1071 end
1072
1073 redef fun must_be_inline do
1074 if super then return true
1075 return n_extern != null
1076 end
1077 end
1078
1079 redef class AInternMethPropdef
1080 redef fun accept_pretty_printer(v) do
1081 super
1082 v.consume "is"
1083 v.adds
1084 v.consume "intern"
1085 v.finish_line
1086 v.addn
1087 end
1088 end
1089
1090 redef class AConcreteMethPropdef
1091 redef fun accept_pretty_printer(v) do
1092 var before = v.indent
1093 var can_inline = v.can_inline(self)
1094 super
1095 var n_block = self.n_block
1096
1097 if n_block != null then
1098 while not v.current_token isa TKwdo do v.skip
1099 if n_annotations != null then
1100 if v.can_inline(n_annotations) then
1101 v.adds
1102 else
1103 v.addt
1104 end
1105 end
1106 v.consume "do"
1107
1108 if can_inline then
1109 v.adds
1110
1111 if n_block isa ABlockExpr then
1112 if n_block.n_expr.is_empty then
1113 v.visit n_block.n_kwend
1114 else
1115 v.visit n_block.n_expr.first
1116 v.current_token = n_block.n_kwend
1117 v.skip
1118 end
1119 else
1120 v.visit n_block
1121 if v.current_token isa TKwend then v.skip
1122 end
1123 else
1124 v.finish_line
1125 v.addn
1126 v.indent += 1
1127
1128 if n_block isa ABlockExpr then
1129 n_block.force_block = true
1130 v.visit n_block
1131 v.catch_up n_block.n_kwend
1132 else
1133 v.addt
1134 v.visit n_block
1135 v.addn
1136 end
1137
1138 v.indent -= 1
1139 v.addt
1140 if n_block isa ABlockExpr then
1141 v.visit n_block.n_kwend
1142 else
1143 v.add "end"
1144 end
1145 end
1146 end
1147
1148 v.finish_line
1149 v.addn
1150 assert v.indent == before
1151 end
1152 end
1153
1154 redef class ASignature
1155 redef fun accept_pretty_printer(v) do
1156 if not n_params.is_empty then
1157 v.consume "("
1158 v.visit_list n_params
1159 v.consume ")"
1160 end
1161
1162 if n_type != null then
1163 v.consume ":"
1164 v.adds
1165 v.visit n_type
1166 end
1167 end
1168 end
1169
1170 redef class AParam
1171 redef fun accept_pretty_printer(v) do
1172 v.visit n_id
1173
1174 if n_type != null then
1175 v.consume ":"
1176 v.adds
1177 v.visit n_type
1178 end
1179
1180 if n_dotdotdot != null then v.visit n_dotdotdot
1181 end
1182 end
1183
1184 # Extern
1185
1186 redef class AExternCalls
1187 redef fun accept_pretty_printer(v) do
1188 var can_inline = v.can_inline(self)
1189 v.visit n_kwimport
1190
1191 if can_inline then
1192 v.adds
1193 v.visit_list n_extern_calls
1194 else
1195 v.addn
1196 v.addt
1197 v.addt
1198 v.visit_list n_extern_calls
1199 end
1200
1201 v.adds
1202 end
1203 end
1204
1205 redef class AFullPropExternCall
1206 redef fun accept_pretty_printer(v) do
1207 v.visit n_type
1208 v.visit n_dot
1209 v.visit n_methid
1210 end
1211 end
1212
1213 redef class ALocalPropExternCall
1214 redef fun accept_pretty_printer(v) do v.visit n_methid
1215 end
1216
1217 redef class AInitPropExternCall
1218 redef fun accept_pretty_printer(v) do v.visit n_type
1219 end
1220
1221 redef class ACastAsExternCall
1222 redef fun accept_pretty_printer(v) do
1223 v.visit n_from_type
1224 v.visit n_dot
1225 v.visit n_kwas
1226 v.consume "("
1227 v.visit n_to_type
1228 v.consume ")"
1229 end
1230 end
1231
1232 redef class AAsNullableExternCall
1233 redef fun accept_pretty_printer(v) do
1234 v.visit n_type
1235 v.consume "."
1236 v.visit n_kwas
1237 v.adds
1238 v.visit n_kwnullable
1239 end
1240 end
1241
1242 redef class AAsNotNullableExternCall
1243 redef fun accept_pretty_printer(v) do
1244 v.visit n_type
1245 v.consume "."
1246 v.visit n_kwas
1247 v.adds
1248 v.visit n_kwnot
1249 v.adds
1250 v.visit n_kwnullable
1251 end
1252 end
1253
1254 redef class AExternCodeBlock
1255 redef fun accept_pretty_printer(v) do
1256 if n_in_language != null then
1257 v.visit n_in_language
1258 v.adds
1259 end
1260
1261 v.visit n_extern_code_segment
1262 end
1263
1264 redef fun is_inlinable do
1265 if not super then return false
1266 return n_extern_code_segment.is_inlinable
1267 end
1268 end
1269
1270 redef class AInLanguage
1271 redef fun accept_pretty_printer(v) do
1272 v.visit n_kwin
1273 v.adds
1274 v.visit n_string
1275 end
1276 end
1277
1278 redef class TExternCodeSegment
1279 redef fun accept_pretty_printer(v) do
1280 var can_inline = v.can_inline(self)
1281
1282 if can_inline then
1283 super
1284 else
1285 var text = text.substring(2, text.length - 4)
1286 var lines = text.r_trim.split("\n")
1287
1288 if text.is_empty then
1289 v.add "`\{`\}"
1290 else
1291 v.add "`\{"
1292
1293 if not lines.first.trim.is_empty then
1294 v.addn
1295 lines.first.l_trim
1296 v.indent += 1
1297 v.addt
1298 v.indent -= 1
1299 end
1300
1301 for line in lines do
1302 v.add line.r_trim
1303 v.addn
1304 end
1305
1306 v.addt
1307 v.add "`\}"
1308 end
1309
1310 v.current_token = next_token
1311 end
1312 end
1313
1314 redef fun is_inlinable do
1315 if not super then return false
1316 return location.line_start == location.line_end
1317 end
1318 end
1319
1320 # Blocks
1321
1322 redef class ABlockExpr
1323 redef fun accept_pretty_printer(v) do
1324 var before = v.indent
1325 var can_inline = v.can_inline(self)
1326
1327 if can_inline and not n_expr.is_empty then
1328 v.visit n_expr.first
1329 v.finish_line
1330 else
1331 for nexpr in n_expr do
1332 var expr_inline = v.can_inline(nexpr)
1333 if not expr_inline and nexpr != n_expr.first then v.addn
1334 v.catch_up nexpr
1335 v.addt
1336 v.visit nexpr
1337 v.finish_line
1338 v.addn
1339 if not expr_inline and nexpr != n_expr.last then v.addn
1340 end
1341 end
1342
1343 assert v.indent == before
1344 end
1345
1346 redef fun is_inlinable do
1347 if not super then return false
1348 if not collect_comments.is_empty then return false
1349
1350 if not n_expr.is_empty then
1351 if n_expr.length > 1 then return false
1352 if not n_expr.first.is_inlinable then return false
1353 end
1354
1355 return true
1356 end
1357 end
1358
1359 redef class AIfExpr
1360 redef fun accept_pretty_printer(v) do
1361 var before = v.indent
1362 var can_inline = v.can_inline(self)
1363 v.visit n_kwif
1364 v.adds
1365
1366 if v.can_inline(n_expr) then
1367 v.visit n_expr
1368 v.adds
1369 else
1370 v.visit n_expr
1371 v.addn
1372 v.addt
1373 end
1374
1375 # skip comments before `then` token
1376 while not v.current_token isa TKwthen do v.skip
1377 v.consume "then"
1378 var n_else = self.n_else
1379
1380 if can_inline then
1381 v.adds
1382 if n_then != null then v.visit n_then
1383
1384 if has_else(v) then
1385 n_else.force_inline = true
1386 v.adds
1387 v.consume "else"
1388 v.adds
1389 v.visit n_else
1390 else if n_then == null then
1391 v.add "end"
1392 end
1393
1394 v.skip_to last_token.last_real_token_in_line
1395 else
1396 v.finish_line
1397 v.addn
1398 v.indent += 1
1399
1400 if n_then != null then
1401 if n_then isa ABlockExpr then
1402 n_then.force_block = true
1403 v.visit n_then
1404 else
1405 v.addt
1406 v.visit n_then
1407 v.addn
1408 end
1409 end
1410
1411 if has_else(v) then
1412 while not v.current_token isa TKwelse do
1413 v.consume v.current_token.text
1414 end
1415
1416 v.indent -= 1
1417 v.addt
1418 v.consume "else"
1419
1420 if n_else isa AIfExpr then
1421 n_else.force_block = true
1422 v.adds
1423 v.visit n_else
1424 else
1425 v.finish_line
1426 v.addn
1427 v.indent += 1
1428
1429 if n_else isa ABlockExpr then
1430 n_else.force_block = true
1431 v.visit n_else
1432 else
1433 v.addt
1434 v.visit n_else
1435 v.addn
1436 end
1437
1438 if last_token isa TKwend then
1439 v.catch_up last_token
1440 v.indent -= 1
1441 v.addt
1442 v.consume "end"
1443 else
1444 v.indent -= 1
1445 v.addt
1446 v.add "end"
1447 end
1448 end
1449 else
1450 if last_token.location >= v.current_token.location then v.catch_up last_token
1451 v.indent -= 1
1452 v.addt
1453 v.add "end"
1454 if v.current_token isa TKwend then v.skip
1455 end
1456 end
1457
1458 assert v.indent == before
1459 end
1460
1461 redef fun is_inlinable do
1462 if not super then return false
1463 if n_then != null and not n_then.is_inlinable then return false
1464 var n_else = self.n_else
1465 if (n_else isa ABlockExpr and not n_else.n_expr.is_empty) or
1466 (not n_else isa ABlockExpr and n_else != null) then
1467 return false
1468 end
1469 if not collect_comments.is_empty then return false
1470 return true
1471 end
1472
1473 # Does this `if` statement contains a `else` part?
1474 private fun has_else(v: PrettyPrinterVisitor): Bool do
1475 var n_else = n_else
1476 if n_else == null then return false
1477 var n_kwelse = collect_kwelse
1478 if n_kwelse == null then return false
1479
1480 if n_else isa ABlockExpr then
1481 var comments: Array[TComment]
1482
1483 if n_then == null then
1484 comments = v.collect_comments(n_expr.last_token, n_else.last_token)
1485 else
1486 comments = v.collect_comments(n_then.last_token, n_else.last_token)
1487 end
1488
1489 if not comments.is_empty then return true
1490 return not n_else.n_expr.is_empty
1491 end
1492
1493 return true
1494 end
1495
1496 # Lookup for `else` token in `self`.
1497 private fun collect_kwelse: nullable TKwelse do
1498 var token = first_token
1499
1500 while token != last_token do
1501 if token isa TKwelse then return token
1502 token = token.next_token
1503 end
1504
1505 return null
1506 end
1507 end
1508
1509 # Used to factorize work on loops.
1510 private class ALoopHelper
1511 super AExpr
1512
1513 fun loop_block: nullable ANode is abstract
1514 fun loop_label: nullable ANode is abstract
1515
1516 fun visit_loop_block(v: PrettyPrinterVisitor) do
1517 var n_block = loop_block
1518 v.finish_line
1519 v.addn
1520 v.indent += 1
1521
1522 if n_block isa ABlockExpr then
1523 n_block.force_block = true
1524 v.visit n_block
1525 v.catch_up n_block.n_kwend
1526 v.indent -= 1
1527 v.addt
1528 v.visit n_block.n_kwend
1529 else
1530 v.addt
1531 v.visit n_block
1532 v.addn
1533 v.indent -= 1
1534 v.addt
1535 v.add "end"
1536 end
1537
1538 if loop_label != null then
1539 v.adds
1540 v.visit loop_label
1541 end
1542 end
1543
1544 fun visit_loop_inline(v: PrettyPrinterVisitor) do
1545 var n_block = loop_block
1546 v.adds
1547
1548 if n_block isa ABlockExpr then
1549 if n_block.n_expr.is_empty then
1550 v.visit n_block.n_kwend
1551 else
1552 v.visit n_block.n_expr.first
1553 v.current_token = n_block.n_kwend
1554 v.skip
1555 end
1556 else
1557 v.visit n_block
1558 if v.current_token isa TKwend then v.skip
1559 end
1560
1561 if loop_label != null then
1562 v.adds
1563 v.visit loop_label
1564 end
1565 end
1566
1567 redef fun is_inlinable do
1568 var n_block = loop_block
1569 if not super then return false
1570 if n_block isa ABlockExpr and not n_block.is_inlinable then return false
1571 if not collect_comments.is_empty then return false
1572 return true
1573 end
1574 end
1575
1576 redef class ALoopExpr
1577 super ALoopHelper
1578
1579 redef fun loop_block do return n_block
1580 redef fun loop_label do return n_label
1581
1582 redef fun accept_pretty_printer(v) do
1583 var can_inline = v.can_inline(self)
1584 v.visit n_kwloop
1585 if can_inline then visit_loop_inline v else visit_loop_block v
1586 end
1587 end
1588
1589 redef class AWhileExpr
1590 super ALoopHelper
1591
1592 redef fun loop_block do return n_block
1593 redef fun loop_label do return n_label
1594
1595 redef fun accept_pretty_printer(v) do
1596 var can_inline = v.can_inline(self)
1597 v.visit n_kwwhile
1598 v.adds
1599 v.visit n_expr
1600 v.adds
1601 v.visit n_kwdo
1602 if can_inline then visit_loop_inline v else visit_loop_block v
1603 end
1604 end
1605
1606 redef class ADoExpr
1607 super ALoopHelper
1608
1609 redef fun loop_block do return n_block
1610 redef fun loop_label do return n_label
1611
1612 redef fun accept_pretty_printer(v) do
1613 var can_inline = v.can_inline(self)
1614 v.visit n_kwdo
1615 if can_inline then visit_loop_inline v else visit_loop_block v
1616 end
1617 end
1618
1619 redef class AForExpr
1620 super ALoopHelper
1621
1622 redef fun loop_block do return n_block
1623 redef fun loop_label do return n_label
1624
1625 redef fun accept_pretty_printer(v) do
1626 var can_inline = v.can_inline(self)
1627 v.visit n_kwfor
1628 v.adds
1629
1630 for n_id in n_ids do
1631 v.visit n_id
1632 if n_id != n_ids.last then v.add ", "
1633 end
1634
1635 v.adds
1636 v.consume "in"
1637 v.adds
1638 v.visit n_expr
1639 v.adds
1640 v.visit n_kwdo
1641 if can_inline then visit_loop_inline v else visit_loop_block v
1642 end
1643 end
1644
1645 redef class ABreakExpr
1646 redef fun accept_pretty_printer(v) do
1647 v.visit n_kwbreak
1648
1649 if n_expr != null then
1650 v.adds
1651 v.visit n_expr
1652 end
1653
1654 if n_label != null then
1655 v.adds
1656 v.visit n_label
1657 end
1658 end
1659
1660 redef fun is_inlinable do return true
1661 end
1662
1663 redef class AContinueExpr
1664 redef fun accept_pretty_printer(v) do
1665 v.visit n_kwcontinue
1666
1667 if n_expr != null then
1668 v.adds
1669 v.visit n_expr
1670 end
1671
1672 if n_label != null then
1673 v.adds
1674 v.visit n_label
1675 end
1676 end
1677
1678 redef fun is_inlinable do return true
1679 end
1680
1681 # Calls
1682
1683 redef class ASendExpr
1684 redef fun is_inlinable do return true
1685 end
1686
1687 redef class ACallExpr
1688 redef fun accept_pretty_printer(v) do
1689 var can_inline = v.can_inline(self)
1690 v.visit_recv n_expr
1691
1692 if not n_expr isa AImplicitSelfExpr and not can_inline then
1693 v.addn
1694 v.addt
1695 v.addt
1696 end
1697
1698 v.visit n_id
1699
1700 if not n_args.n_exprs.is_empty then
1701 if is_stmt and n_args.n_exprs.length == 1 then
1702 v.adds
1703 if v.current_token isa TOpar then v.skip
1704 v.visit n_args.n_exprs.first
1705 if v.current_token isa TCpar then v.skip
1706 else
1707 if v.current_token isa TOpar then
1708 v.consume "("
1709 else
1710 v.adds
1711 end
1712
1713 v.visit_list n_args.n_exprs
1714 if v.current_token isa TCpar then v.consume ")"
1715 end
1716 end
1717 end
1718
1719 # Is the call alone on its line?
1720 fun is_stmt: Bool do return parent isa ABlockExpr
1721 end
1722
1723 redef class ACallAssignExpr
1724 redef fun accept_pretty_printer(v) do
1725 v.visit_recv n_expr
1726 v.visit n_id
1727
1728 if not n_args.n_exprs.is_empty then
1729 v.consume "("
1730 v.visit_list n_args.n_exprs
1731 v.consume ")"
1732 end
1733
1734 v.adds
1735 v.consume "="
1736 v.adds
1737 v.visit n_value
1738 end
1739 end
1740
1741 redef class ACallReassignExpr
1742 redef fun accept_pretty_printer(v) do
1743 v.visit_recv n_expr
1744 v.visit n_id
1745
1746 if not n_args.n_exprs.is_empty then
1747 v.consume "("
1748 v.visit_list n_args.n_exprs
1749 v.consume ")"
1750 end
1751
1752 v.adds
1753 v.visit n_assign_op
1754 v.adds
1755 v.visit n_value
1756 end
1757 end
1758
1759 redef class ABraExpr
1760 redef fun accept_pretty_printer(v) do
1761 v.visit n_expr
1762
1763 if not n_args.n_exprs.is_empty then
1764 v.consume "["
1765 v.visit_list n_args.n_exprs
1766 v.consume "]"
1767 end
1768 end
1769 end
1770
1771 redef class ABraAssignExpr
1772 redef fun accept_pretty_printer(v) do
1773 v.visit n_expr
1774
1775 if not n_args.n_exprs.is_empty then
1776 v.consume "["
1777 v.visit_list n_args.n_exprs
1778 v.consume "]"
1779 end
1780
1781 v.adds
1782 v.visit n_assign
1783 v.adds
1784 v.visit n_value
1785 end
1786 end
1787
1788 redef class ABraReassignExpr
1789 redef fun accept_pretty_printer(v) do
1790 v.visit n_expr
1791
1792 if not n_args.n_exprs.is_empty then
1793 v.consume "["
1794 v.visit_list n_args.n_exprs
1795 v.consume "]"
1796 end
1797
1798 v.adds
1799 v.visit n_assign_op
1800 v.adds
1801 v.visit n_value
1802 end
1803 end
1804
1805 redef class AAssignMethid
1806 redef fun accept_pretty_printer(v) do
1807 v.visit n_id
1808 v.visit n_assign
1809 end
1810 end
1811
1812 redef class ABraMethid
1813 redef fun accept_pretty_printer(v) do
1814 v.visit n_obra
1815 v.visit n_cbra
1816 end
1817 end
1818
1819 redef class ABraassignMethid
1820 redef fun accept_pretty_printer(v) do
1821 v.visit n_obra
1822 v.visit n_cbra
1823 v.visit n_assign
1824 end
1825 end
1826
1827 redef class AInitExpr
1828 redef fun accept_pretty_printer(v) do
1829 if not n_expr isa AImplicitSelfExpr then
1830 v.visit n_expr
1831 v.consume "."
1832 end
1833
1834 v.visit n_kwinit
1835
1836 if not n_args.n_exprs.is_empty then
1837 v.consume "("
1838 v.visit_list n_args.n_exprs
1839 v.consume ")"
1840 end
1841 end
1842 end
1843
1844 redef class ANewExpr
1845 redef fun accept_pretty_printer(v) do
1846 var can_inline = v.can_inline(self)
1847 v.visit n_kwnew
1848 v.adds
1849 v.visit n_type
1850
1851 if n_id != null then
1852 v.consume "."
1853
1854 if not can_inline then
1855 v.addn
1856 v.addt
1857 v.addt
1858 end
1859
1860 v.visit n_id
1861 end
1862
1863 if not n_args.n_exprs.is_empty then
1864 v.consume "("
1865 v.visit_list n_args.n_exprs
1866 v.consume ")"
1867 end
1868 end
1869
1870 redef fun is_inlinable do return true
1871 end
1872
1873 # Attributes
1874
1875 redef class AAttrExpr
1876 redef fun accept_pretty_printer(v) do
1877 v.visit_recv n_expr
1878 v.visit n_id
1879 end
1880
1881 redef fun is_inlinable do return true
1882 end
1883
1884 redef class AAttrAssignExpr
1885 redef fun accept_pretty_printer(v) do
1886 v.visit_recv n_expr
1887 v.visit n_id
1888 v.adds
1889 v.visit n_assign
1890 v.adds
1891 v.visit n_value
1892 end
1893 end
1894
1895 redef class AAttrReassignExpr
1896 redef fun accept_pretty_printer(v) do
1897 v.visit_recv n_expr
1898 v.visit n_id
1899 v.adds
1900 v.visit n_assign_op
1901 v.adds
1902 v.visit n_value
1903 end
1904 end
1905
1906 redef class AAble
1907 redef fun accept_pretty_printer(v) do
1908 if n_kwredef != null then
1909 v.visit n_kwredef
1910 v.adds
1911 end
1912
1913 if not n_visibility isa APublicVisibility then
1914 v.visit n_visibility
1915 v.adds
1916 end
1917 end
1918 end
1919
1920 redef class AReadAble
1921 redef fun accept_pretty_printer(v) do
1922 super
1923 v.visit n_kwreadable
1924 end
1925 end
1926
1927 redef class AWriteAble
1928 redef fun accept_pretty_printer(v) do
1929 super
1930 v.visit n_kwwritable
1931 end
1932 end
1933
1934 # Exprs
1935
1936 redef class AVardeclExpr
1937 redef fun accept_pretty_printer(v) do
1938 v.visit n_kwvar
1939 v.adds
1940 v.visit n_id
1941
1942 if n_type != null then
1943 v.consume ":"
1944 v.adds
1945 v.visit n_type
1946 end
1947
1948 if n_expr != null then
1949 v.adds
1950 v.consume "="
1951 v.adds
1952 v.visit n_expr
1953 end
1954 end
1955
1956 redef fun is_inlinable do return true
1957 end
1958
1959 redef class AVarAssignExpr
1960 redef fun accept_pretty_printer(v) do
1961 v.visit n_id
1962 v.adds
1963 v.visit n_assign
1964 v.adds
1965 v.visit n_value
1966 end
1967 end
1968
1969 redef class AAssertExpr
1970 redef fun accept_pretty_printer(v) do
1971 var can_inline = v.can_inline(self)
1972 v.visit n_kwassert
1973
1974 if n_id != null then
1975 v.adds
1976 v.visit n_id
1977 v.consume ":"
1978 end
1979
1980 v.adds
1981 v.visit n_expr
1982 var n_else = self.n_else
1983
1984 if n_else != null then
1985 v.adds
1986 v.consume "else"
1987
1988 if can_inline then
1989 v.adds
1990 v.visit n_else
1991 else
1992 v.addn
1993
1994 if n_else isa ABlockExpr then
1995 v.indent += 1
1996 n_else.force_block = true
1997 v.visit n_else
1998 v.indent -= 1
1999 v.addt
2000 v.visit n_else.n_kwend
2001 else
2002 v.indent += 1
2003 v.addt
2004 v.visit n_else
2005 v.addn
2006 v.indent -= 1
2007 v.addt
2008 v.add "end"
2009 end
2010 end
2011 end
2012 end
2013
2014 redef fun is_inlinable do
2015 if not super then return false
2016 if n_else != null and not n_else.is_inlinable then return false
2017 return true
2018 end
2019 end
2020
2021 redef class AReturnExpr
2022 redef fun accept_pretty_printer(v) do
2023 v.visit n_kwreturn
2024
2025 if n_expr != null then
2026 v.adds
2027 v.visit n_expr
2028 end
2029 end
2030 end
2031
2032 redef class ASuperExpr
2033 redef fun accept_pretty_printer(v) do
2034 v.visit n_kwsuper
2035
2036 if not n_args.n_exprs.is_empty then
2037 if is_stmt and n_args.n_exprs.length == 1 then
2038 v.adds
2039 if v.current_token isa TOpar then v.skip
2040 v.visit n_args.n_exprs.first
2041 if v.current_token isa TCpar then v.skip
2042 else
2043 if v.current_token isa TOpar then
2044 v.consume "("
2045 else
2046 v.adds
2047 end
2048
2049 v.visit_list n_args.n_exprs
2050 if v.current_token isa TCpar then v.consume ")"
2051 end
2052 end
2053 end
2054
2055 # Is the call alone on its line?
2056 fun is_stmt: Bool do return self.first_token.is_starting_line
2057
2058 redef fun is_inlinable do return true
2059 end
2060
2061 redef class AOnceExpr
2062 redef fun accept_pretty_printer(v) do
2063 v.visit n_kwonce
2064 v.adds
2065 v.visit n_expr
2066 end
2067
2068 redef fun is_inlinable do return true
2069 end
2070
2071 redef class AAbortExpr
2072 redef fun accept_pretty_printer(v) do v.visit n_kwabort
2073 redef fun is_inlinable do return true
2074 end
2075
2076 redef class ANotExpr
2077 redef fun accept_pretty_printer(v) do
2078 v.visit n_kwnot
2079 v.adds
2080 v.visit n_expr
2081 end
2082 end
2083
2084 redef class AAsCastExpr
2085 redef fun accept_pretty_printer(v) do
2086 v.visit n_expr
2087 v.consume "."
2088 v.visit n_kwas
2089 v.visit n_opar
2090 v.visit n_type
2091 v.visit n_cpar
2092 end
2093 end
2094
2095 redef class AAsNotnullExpr
2096 redef fun accept_pretty_printer(v) do
2097 v.visit n_expr
2098 v.consume "."
2099 v.visit n_kwas
2100 v.visit n_opar
2101 v.visit n_kwnot
2102 v.adds
2103 v.visit n_kwnull
2104 v.visit n_cpar
2105 end
2106 end
2107
2108 # Binops
2109
2110 # Used to factorize work on Or, And, Implies and Binop expressions.
2111 private class ABinOpHelper
2112 super AExpr
2113
2114 fun bin_expr1: AExpr is abstract
2115 fun bin_expr2: AExpr is abstract
2116
2117 # Operator string
2118 fun bin_op: String is abstract
2119
2120 redef fun accept_pretty_printer(v) do
2121 var can_inline = v.can_inline(self)
2122
2123 if not can_inline then
2124 if (self isa ABinopExpr and bin_expr1 isa ABinopExpr) or
2125 (self isa AAndExpr and (bin_expr1 isa AAndExpr or bin_expr1 isa AOrExpr)) or
2126 (self isa AOrExpr and (bin_expr1 isa AAndExpr or bin_expr1 isa AOrExpr))
2127 then
2128 bin_expr1.force_block = true
2129 end
2130 end
2131
2132 v.visit bin_expr1
2133 v.adds
2134 v.consume bin_op
2135
2136 if can_inline then
2137 v.adds
2138 v.visit bin_expr2
2139 else
2140 v.addn
2141 v.addt
2142 v.addt
2143 v.visit bin_expr2
2144 end
2145 end
2146 end
2147
2148 redef class AAndExpr
2149 super ABinOpHelper
2150
2151 redef fun bin_expr1 do return n_expr
2152 redef fun bin_expr2 do return n_expr2
2153 redef fun bin_op do return "and"
2154 end
2155
2156 redef class AOrExpr
2157 super ABinOpHelper
2158
2159 redef fun bin_expr1 do return n_expr
2160 redef fun bin_expr2 do return n_expr2
2161 redef fun bin_op do return "or"
2162 end
2163
2164 redef class AImpliesExpr
2165 super ABinOpHelper
2166
2167 redef fun bin_expr1 do return n_expr
2168 redef fun bin_expr2 do return n_expr2
2169 redef fun bin_op do return "implies"
2170 end
2171
2172 redef class ABinopExpr
2173 super ABinOpHelper
2174
2175 redef fun bin_expr1 do return n_expr
2176 redef fun bin_expr2 do return n_expr2
2177 end
2178
2179 redef class AEqExpr
2180 redef fun bin_op do return "=="
2181 end
2182
2183 redef class AGeExpr
2184 redef fun bin_op do return ">="
2185 end
2186
2187 redef class AGgExpr
2188 redef fun bin_op do return ">>"
2189 end
2190
2191 redef class AGtExpr
2192 redef fun bin_op do return ">"
2193 end
2194
2195 redef class ALeExpr
2196 redef fun bin_op do return "<="
2197 end
2198
2199 redef class ALlExpr
2200 redef fun bin_op do return "<<"
2201 end
2202
2203 redef class ALtExpr
2204 redef fun bin_op do return "<"
2205 end
2206
2207 redef class AMinusExpr
2208 redef fun bin_op do return "-"
2209 end
2210
2211 redef class ANeExpr
2212 redef fun bin_op do return "!="
2213 end
2214
2215 redef class APercentExpr
2216 redef fun bin_op do return "%"
2217 end
2218
2219 redef class APlusExpr
2220 redef fun bin_op do return "+"
2221 end
2222
2223 redef class ASlashExpr
2224 redef fun bin_op do return "/"
2225 end
2226
2227 redef class AStarExpr
2228 redef fun bin_op do return "*"
2229 end
2230
2231 redef class AStarshipExpr
2232 redef fun bin_op do return "<=>"
2233 end
2234
2235 redef class AIsaExpr
2236 redef fun accept_pretty_printer(v) do
2237 v.visit n_expr
2238 v.adds
2239 v.consume "isa"
2240 v.adds
2241 v.visit n_type
2242 end
2243 end
2244
2245 redef class AOrElseExpr
2246 redef fun accept_pretty_printer(v) do
2247 v.visit n_expr
2248 v.adds
2249 v.consume "or"
2250 v.adds
2251 v.consume "else"
2252 v.adds
2253 v.visit n_expr2
2254 end
2255
2256 redef fun is_inlinable do return true
2257 end
2258
2259 # Syntax
2260
2261 redef class AUminusExpr
2262 redef fun accept_pretty_printer(v) do
2263 v.consume "-"
2264 v.visit n_expr
2265 end
2266 end
2267
2268 redef class ANullExpr
2269 redef fun accept_pretty_printer(v) do v.visit n_kwnull
2270 redef fun is_inlinable do return true
2271 end
2272
2273 redef class AParExpr
2274 redef fun accept_pretty_printer(v) do
2275 v.visit n_opar
2276 v.visit n_expr
2277 v.visit n_cpar
2278 end
2279 end
2280
2281 redef class AArrayExpr
2282 redef fun accept_pretty_printer(v) do
2283 v.consume "["
2284 v.visit_list n_exprs.n_exprs
2285 v.consume "]"
2286 end
2287 end
2288
2289 redef class ACrangeExpr
2290 redef fun accept_pretty_printer(v) do
2291 v.visit n_obra
2292 v.visit n_expr
2293 v.consume ".."
2294 v.visit n_expr2
2295 v.visit n_cbra
2296 end
2297 end
2298
2299 redef class AOrangeExpr
2300 redef fun accept_pretty_printer(v) do
2301 v.visit n_obra
2302 v.visit n_expr
2303 v.consume ".."
2304 v.visit n_expr2
2305 v.visit n_cbra
2306 end
2307 end
2308
2309 # Strings
2310
2311 redef class AStringFormExpr
2312 redef fun accept_pretty_printer(v) do
2313 var can_inline = v.can_inline(self)
2314
2315 if can_inline then
2316 v.visit n_string
2317 else
2318 var text = n_string.text
2319 var i = 0
2320
2321 while i < text.length do
2322 v.add text[i].to_s
2323
2324 if v.current_length >= v.max_size and i <= text.length - 3 then
2325 v.add "\" +"
2326 v.addn
2327 v.indent += 1
2328 v.addt
2329 v.indent -= 1
2330 v.add "\""
2331 end
2332
2333 i += 1
2334 end
2335
2336 v.current_token = n_string.next_token
2337 end
2338 end
2339 end
2340
2341 redef class ASuperstringExpr
2342 redef fun accept_pretty_printer(v) do
2343 var force_inline = self.force_inline
2344 for n_expr in n_exprs do v.visit n_expr
2345 end
2346
2347 redef fun must_be_inline do
2348 if super then return true
2349
2350 if not n_exprs.is_empty then
2351 var first = n_exprs.first
2352 return first isa AStringFormExpr and first.n_string.text.has_prefix("\"\"\"")
2353 end
2354
2355 return false
2356 end
2357 end
2358
2359 redef class ToolContext
2360 var opt_dir = new OptionString("Working directory (default is '.nitpretty')", "--dir")
2361
2362 var opt_output = new OptionString("Output name (default is pretty.nit)", "-o",
2363 "--output")
2364
2365 var opt_diff = new OptionBool("Show diff between source and output", "--diff")
2366
2367 var opt_meld = new OptionBool("Show diff between source and output using meld",
2368 "--meld")
2369
2370 var opt_check = new OptionBool("Check format of Nit source files", "--check")
2371 end
2372
2373 # Return result from diff between `file1` and `file2`.
2374 private fun diff(file1, file2: String): String do
2375 var p = new IProcess("diff", "-u", file1, file2)
2376 var res = p.read_all
2377 p.wait
2378 p.close
2379 return res
2380 end
2381
2382 # process options
2383 var toolcontext = new ToolContext
2384
2385 toolcontext.option_context.
2386 add_option(toolcontext.opt_dir, toolcontext.opt_output, toolcontext.opt_diff,
2387 toolcontext.opt_meld, toolcontext.opt_check)
2388
2389 toolcontext.tooldescription = "Usage: nitpretty [OPTION]... <file.nit>\n" +
2390 "Pretty print Nit code from Nit source files."
2391
2392 toolcontext.process_options args
2393 var arguments = toolcontext.option_context.rest
2394 # build model
2395 var model = new Model
2396 var mbuilder = new ModelBuilder(model, toolcontext)
2397 var mmodules = mbuilder.parse(arguments)
2398 mbuilder.run_phases
2399
2400 if mmodules.is_empty then
2401 print "Error: no module to pretty print"
2402 return
2403 end
2404
2405 if not toolcontext.opt_check.value and mmodules.length > 1 then
2406 print "Error: only --check option allow multiple modules"
2407 return
2408 end
2409
2410 var dir = toolcontext.opt_dir.value or else ".nitpretty"
2411 if not dir.file_exists then dir.mkdir
2412 var v = new PrettyPrinterVisitor
2413
2414 for mmodule in mmodules do
2415 if not mbuilder.mmodule2nmodule.has_key(mmodule) then
2416 print " Error: no source file for module {mmodule}"
2417 return
2418 end
2419
2420 var nmodule = mbuilder.mmodule2nmodule[mmodule]
2421 var file = "{dir}/{mmodule.name}.nit"
2422 var tpl = v.pretty_nmodule(nmodule)
2423 tpl.write_to_file file
2424
2425 if toolcontext.opt_check.value then
2426 var res = diff(nmodule.location.file.filename, file)
2427
2428 if not res.is_empty then
2429 print "Wrong formating for module {nmodule.location.file.filename}"
2430 toolcontext.info(res, 1)
2431
2432 if toolcontext.opt_meld.value then
2433 sys.system "meld {nmodule.location.file.filename} {file}"
2434 end
2435 else
2436 toolcontext.info("[OK] {nmodule.location.file.filename}", 1)
2437 end
2438 else
2439 # write to file
2440 var out = toolcontext.opt_output.value
2441 if out != null then sys.system "cp {file} {out}"
2442
2443 # open in meld
2444 if toolcontext.opt_meld.value then
2445 sys.system "meld {arguments.first} {file}"
2446 return
2447 end
2448
2449 # show diff
2450 if toolcontext.opt_diff.value then
2451 var res = diff(arguments.first, file)
2452 if not res.is_empty then print res
2453 return
2454 end
2455
2456 # show pretty
2457 if not toolcontext.opt_quiet.value then tpl.write_to sys.stdout
2458 end
2459 end