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