Merge: Some gammar improvements
[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 if n_opar == null then
620 v.adds
621 else
622 v.visit n_opar
623 end
624 v.visit_list n_args
625 v.visit n_cpar
626 end
627 end
628 end
629
630 redef class ATypeExpr
631 redef fun accept_pretty_printer(v) do v.visit n_type
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 v.visit n_id2
932
933 if n_type != null then
934 v.consume ":"
935 v.adds
936 v.visit n_type
937 end
938
939 if n_expr != null then
940 v.adds
941 v.consume "="
942 v.adds
943 v.visit n_expr
944 end
945
946 if n_annotations != null then v.visit n_annotations
947 v.finish_line
948 v.addn
949 end
950
951 redef fun first_token do
952 if n_doc != null then return n_doc.first_token
953 if not n_visibility isa APublicVisibility then return n_visibility.first_token
954 if n_kwredef != null then return n_kwredef
955 return n_kwvar
956 end
957
958 redef fun is_inlinable do return true
959 end
960
961 redef class ATypePropdef
962 redef fun accept_pretty_printer(v) do
963 super
964 v.visit n_kwtype
965 v.adds
966 v.visit n_id
967 v.consume ":"
968 v.adds
969 v.visit n_type
970 v.finish_line
971 v.addn
972 end
973
974 redef fun is_inlinable do return true
975 end
976
977 redef class AMethPropdef
978 redef fun accept_pretty_printer(v) do
979 # TODO: Handle extern annotations
980
981 var before = v.indent
982 var can_inline = v.can_inline(self)
983 super
984 if n_kwinit != null then v.visit n_kwinit
985 if n_kwmeth != null then v.visit n_kwmeth
986 if n_kwnew != null then v.visit n_kwnew
987
988 if not n_methid == null then
989 v.adds
990 v.visit n_methid
991 end
992
993 v.visit n_signature
994
995 if n_annotations != null then
996 v.visit n_annotations
997 else
998 v.adds
999 end
1000
1001 if n_extern_calls != null or n_extern_code_block != null then
1002 if n_annotations != null then v.adds
1003 if n_extern_calls != null then v.visit n_extern_calls
1004 if n_extern_code_block != null then v.visit n_extern_code_block
1005 end
1006
1007 var n_block = self.n_block
1008
1009 if n_block != null then
1010 while not v.current_token isa TKwdo do v.skip
1011 if n_annotations != null then
1012 if v.can_inline(n_annotations) then
1013 v.adds
1014 else
1015 v.addt
1016 end
1017 end
1018 v.consume "do"
1019
1020 if can_inline then
1021 v.adds
1022
1023 if n_block isa ABlockExpr then
1024 if n_block.n_expr.is_empty then
1025 v.visit n_block.n_kwend
1026 else
1027 v.visit n_block.n_expr.first
1028 v.current_token = n_block.n_kwend
1029 v.skip
1030 end
1031 else
1032 v.visit n_block
1033 if v.current_token isa TKwend then v.skip
1034 end
1035 else
1036 v.finish_line
1037 v.addn
1038 v.indent += 1
1039
1040 if n_block isa ABlockExpr then
1041 n_block.force_block = true
1042 v.visit n_block
1043 v.catch_up n_block.n_kwend
1044 else
1045 v.addt
1046 v.visit n_block
1047 v.addn
1048 end
1049
1050 v.indent -= 1
1051 v.addt
1052 if n_block isa ABlockExpr then
1053 v.visit n_block.n_kwend
1054 else
1055 v.add "end"
1056 end
1057 end
1058 end
1059
1060 v.finish_line
1061 v.addn
1062 assert v.indent == before
1063 end
1064
1065 # Can be inlined if:
1066 # * block is empty or can be inlined
1067 # * contains no comments
1068 redef fun is_inlinable do
1069 if not super then return false
1070 if n_annotations != null and not n_annotations.is_inlinable then return false
1071 if n_block != null and not n_block.is_inlinable then return false
1072 if n_extern_calls != null and not n_extern_calls.is_inlinable then return false
1073 if n_extern_code_block != null and not n_extern_code_block.is_inlinable then return false
1074 if not collect_comments.is_empty then return false
1075 return true
1076 end
1077 end
1078
1079 redef class AMainMethPropdef
1080 redef fun accept_pretty_printer(v) do
1081 v.visit n_block
1082 v.addn
1083 end
1084 end
1085
1086 redef class ASignature
1087 redef fun accept_pretty_printer(v) do
1088 if not n_params.is_empty then
1089 v.consume "("
1090 v.visit_list n_params
1091 v.consume ")"
1092 end
1093
1094 if n_type != null then
1095 v.consume ":"
1096 v.adds
1097 v.visit n_type
1098 end
1099 end
1100 end
1101
1102 redef class AParam
1103 redef fun accept_pretty_printer(v) do
1104 v.visit n_id
1105
1106 if n_type != null then
1107 v.consume ":"
1108 v.adds
1109 v.visit n_type
1110 end
1111
1112 if n_dotdotdot != null then v.visit n_dotdotdot
1113 end
1114 end
1115
1116 # Extern
1117
1118 redef class AExternCalls
1119 redef fun accept_pretty_printer(v) do
1120 var can_inline = v.can_inline(self)
1121 v.visit n_kwimport
1122
1123 if can_inline then
1124 v.adds
1125 v.visit_list n_extern_calls
1126 else
1127 v.addn
1128 v.addt
1129 v.addt
1130 v.visit_list n_extern_calls
1131 end
1132
1133 v.adds
1134 end
1135 end
1136
1137 redef class AFullPropExternCall
1138 redef fun accept_pretty_printer(v) do
1139 v.visit n_type
1140 v.visit n_dot
1141 v.visit n_methid
1142 end
1143 end
1144
1145 redef class ALocalPropExternCall
1146 redef fun accept_pretty_printer(v) do v.visit n_methid
1147 end
1148
1149 redef class AInitPropExternCall
1150 redef fun accept_pretty_printer(v) do v.visit n_type
1151 end
1152
1153 redef class ACastAsExternCall
1154 redef fun accept_pretty_printer(v) do
1155 v.visit n_from_type
1156 v.visit n_dot
1157 v.visit n_kwas
1158 v.consume "("
1159 v.visit n_to_type
1160 v.consume ")"
1161 end
1162 end
1163
1164 redef class AAsNullableExternCall
1165 redef fun accept_pretty_printer(v) do
1166 v.visit n_type
1167 v.consume "."
1168 v.visit n_kwas
1169 v.adds
1170 v.visit n_kwnullable
1171 end
1172 end
1173
1174 redef class AAsNotNullableExternCall
1175 redef fun accept_pretty_printer(v) do
1176 v.visit n_type
1177 v.consume "."
1178 v.visit n_kwas
1179 v.adds
1180 v.visit n_kwnot
1181 v.adds
1182 v.visit n_kwnullable
1183 end
1184 end
1185
1186 redef class AExternCodeBlock
1187 redef fun accept_pretty_printer(v) do
1188 if n_in_language != null then
1189 v.visit n_in_language
1190 v.adds
1191 end
1192
1193 v.visit n_extern_code_segment
1194 end
1195
1196 redef fun is_inlinable do
1197 if not super then return false
1198 return n_extern_code_segment.is_inlinable
1199 end
1200 end
1201
1202 redef class AInLanguage
1203 redef fun accept_pretty_printer(v) do
1204 v.visit n_kwin
1205 v.adds
1206 v.visit n_string
1207 end
1208 end
1209
1210 redef class TExternCodeSegment
1211 redef fun accept_pretty_printer(v) do
1212 var can_inline = v.can_inline(self)
1213
1214 if can_inline then
1215 super
1216 else
1217 var text = text.substring(2, text.length - 4)
1218 var lines = text.r_trim.split("\n")
1219
1220 if text.is_empty then
1221 v.add "`\{`\}"
1222 else
1223 v.add "`\{"
1224
1225 if not lines.first.trim.is_empty then
1226 v.addn
1227 lines.first.l_trim
1228 v.indent += 1
1229 v.addt
1230 v.indent -= 1
1231 end
1232
1233 for line in lines do
1234 v.add line.r_trim
1235 v.addn
1236 end
1237
1238 v.addt
1239 v.add "`\}"
1240 end
1241
1242 v.current_token = next_token
1243 end
1244 end
1245
1246 redef fun is_inlinable do
1247 if not super then return false
1248 return location.line_start == location.line_end
1249 end
1250 end
1251
1252 # Blocks
1253
1254 redef class ABlockExpr
1255 redef fun accept_pretty_printer(v) do
1256 var before = v.indent
1257 var can_inline = v.can_inline(self)
1258
1259 if can_inline and not n_expr.is_empty then
1260 v.visit n_expr.first
1261 v.finish_line
1262 else
1263 for nexpr in n_expr do
1264 var expr_inline = v.can_inline(nexpr)
1265 if not expr_inline and nexpr != n_expr.first then v.addn
1266 v.catch_up nexpr
1267 v.addt
1268 v.visit nexpr
1269 v.finish_line
1270 v.addn
1271 if not expr_inline and nexpr != n_expr.last then v.addn
1272 end
1273 end
1274
1275 assert v.indent == before
1276 end
1277
1278 redef fun is_inlinable do
1279 if not super then return false
1280 if not collect_comments.is_empty then return false
1281
1282 if not n_expr.is_empty then
1283 if n_expr.length > 1 then return false
1284 if not n_expr.first.is_inlinable then return false
1285 end
1286
1287 return true
1288 end
1289 end
1290
1291 redef class AIfExpr
1292 redef fun accept_pretty_printer(v) do
1293 var before = v.indent
1294 var can_inline = v.can_inline(self)
1295 v.visit n_kwif
1296 v.adds
1297
1298 if v.can_inline(n_expr) then
1299 v.visit n_expr
1300 v.adds
1301 else
1302 v.visit n_expr
1303 v.addn
1304 v.addt
1305 end
1306
1307 # skip comments before `then` token
1308 while not v.current_token isa TKwthen do v.skip
1309 v.consume "then"
1310 var n_else = self.n_else
1311
1312 if can_inline then
1313 v.adds
1314 if n_then != null then v.visit n_then
1315
1316 if has_else(v) then
1317 n_else.force_inline = true
1318 v.adds
1319 v.consume "else"
1320 v.adds
1321 v.visit n_else
1322 else if n_then == null then
1323 v.add "end"
1324 end
1325
1326 v.skip_to last_token.last_real_token_in_line
1327 else
1328 v.finish_line
1329 v.addn
1330 v.indent += 1
1331
1332 if n_then != null then
1333 if n_then isa ABlockExpr then
1334 n_then.force_block = true
1335 v.visit n_then
1336 else
1337 v.addt
1338 v.visit n_then
1339 v.addn
1340 end
1341 end
1342
1343 if has_else(v) then
1344 while not v.current_token isa TKwelse do
1345 v.consume v.current_token.text
1346 end
1347
1348 v.indent -= 1
1349 v.addt
1350 v.consume "else"
1351
1352 if n_else isa AIfExpr then
1353 n_else.force_block = true
1354 v.adds
1355 v.visit n_else
1356 else
1357 v.finish_line
1358 v.addn
1359 v.indent += 1
1360
1361 if n_else isa ABlockExpr then
1362 n_else.force_block = true
1363 v.visit n_else
1364 else
1365 v.addt
1366 v.visit n_else
1367 v.addn
1368 end
1369
1370 if last_token isa TKwend then
1371 v.catch_up last_token
1372 v.indent -= 1
1373 v.addt
1374 v.consume "end"
1375 else
1376 v.indent -= 1
1377 v.addt
1378 v.add "end"
1379 end
1380 end
1381 else
1382 if last_token.location >= v.current_token.location then v.catch_up last_token
1383 v.indent -= 1
1384 v.addt
1385 v.add "end"
1386 if v.current_token isa TKwend then v.skip
1387 end
1388 end
1389
1390 assert v.indent == before
1391 end
1392
1393 redef fun is_inlinable do
1394 if not super then return false
1395 if n_then != null and not n_then.is_inlinable then return false
1396 var n_else = self.n_else
1397 if (n_else isa ABlockExpr and not n_else.n_expr.is_empty) or
1398 (not n_else isa ABlockExpr and n_else != null) then
1399 return false
1400 end
1401 if not collect_comments.is_empty then return false
1402 return true
1403 end
1404
1405 # Does this `if` statement contains a `else` part?
1406 private fun has_else(v: PrettyPrinterVisitor): Bool do
1407 var n_else = n_else
1408 if n_else == null then return false
1409 var n_kwelse = collect_kwelse
1410 if n_kwelse == null then return false
1411
1412 if n_else isa ABlockExpr then
1413 var comments: Array[TComment]
1414
1415 if n_then == null then
1416 comments = v.collect_comments(n_expr.last_token, n_else.last_token)
1417 else
1418 comments = v.collect_comments(n_then.last_token, n_else.last_token)
1419 end
1420
1421 if not comments.is_empty then return true
1422 return not n_else.n_expr.is_empty
1423 end
1424
1425 return true
1426 end
1427
1428 # Lookup for `else` token in `self`.
1429 private fun collect_kwelse: nullable TKwelse do
1430 var token = first_token
1431
1432 while token != last_token do
1433 if token isa TKwelse then return token
1434 token = token.next_token
1435 end
1436
1437 return null
1438 end
1439 end
1440
1441 # Used to factorize work on loops.
1442 private class ALoopHelper
1443 super AExpr
1444
1445 fun loop_block: nullable ANode is abstract
1446 fun loop_label: nullable ANode is abstract
1447
1448 fun visit_loop_block(v: PrettyPrinterVisitor) do
1449 var n_block = loop_block
1450 v.finish_line
1451 v.addn
1452 v.indent += 1
1453
1454 if n_block isa ABlockExpr then
1455 n_block.force_block = true
1456 v.visit n_block
1457 v.catch_up n_block.n_kwend
1458 v.indent -= 1
1459 v.addt
1460 v.visit n_block.n_kwend
1461 else
1462 v.addt
1463 v.visit n_block
1464 v.addn
1465 v.indent -= 1
1466 v.addt
1467 v.add "end"
1468 end
1469
1470 if loop_label != null then
1471 v.adds
1472 v.visit loop_label
1473 end
1474 end
1475
1476 fun visit_loop_inline(v: PrettyPrinterVisitor) do
1477 var n_block = loop_block
1478 v.adds
1479
1480 if n_block isa ABlockExpr then
1481 if n_block.n_expr.is_empty then
1482 v.visit n_block.n_kwend
1483 else
1484 v.visit n_block.n_expr.first
1485 v.current_token = n_block.n_kwend
1486 v.skip
1487 end
1488 else
1489 v.visit n_block
1490 if v.current_token isa TKwend then v.skip
1491 end
1492
1493 if loop_label != null then
1494 v.adds
1495 v.visit loop_label
1496 end
1497 end
1498
1499 redef fun is_inlinable do
1500 var n_block = loop_block
1501 if not super then return false
1502 if n_block isa ABlockExpr and not n_block.is_inlinable then return false
1503 if not collect_comments.is_empty then return false
1504 return true
1505 end
1506 end
1507
1508 redef class ALoopExpr
1509 super ALoopHelper
1510
1511 redef fun loop_block do return n_block
1512 redef fun loop_label do return n_label
1513
1514 redef fun accept_pretty_printer(v) do
1515 var can_inline = v.can_inline(self)
1516 v.visit n_kwloop
1517 if can_inline then visit_loop_inline v else visit_loop_block v
1518 end
1519 end
1520
1521 redef class AWhileExpr
1522 super ALoopHelper
1523
1524 redef fun loop_block do return n_block
1525 redef fun loop_label do return n_label
1526
1527 redef fun accept_pretty_printer(v) do
1528 var can_inline = v.can_inline(self)
1529 v.visit n_kwwhile
1530 v.adds
1531 v.visit n_expr
1532 v.adds
1533 v.visit n_kwdo
1534 if can_inline then visit_loop_inline v else visit_loop_block v
1535 end
1536 end
1537
1538 redef class ADoExpr
1539 super ALoopHelper
1540
1541 redef fun loop_block do return n_block
1542 redef fun loop_label do return n_label
1543
1544 redef fun accept_pretty_printer(v) do
1545 var can_inline = v.can_inline(self)
1546 v.visit n_kwdo
1547 if can_inline then visit_loop_inline v else visit_loop_block v
1548 end
1549 end
1550
1551 redef class AForExpr
1552 super ALoopHelper
1553
1554 redef fun loop_block do return n_block
1555 redef fun loop_label do return n_label
1556
1557 redef fun accept_pretty_printer(v) do
1558 var can_inline = v.can_inline(self)
1559 v.visit n_kwfor
1560 v.adds
1561
1562 for n_id in n_ids do
1563 v.visit n_id
1564 if n_id != n_ids.last then v.add ", "
1565 end
1566
1567 v.adds
1568 v.consume "in"
1569 v.adds
1570 v.visit n_expr
1571 v.adds
1572 v.visit n_kwdo
1573 if can_inline then visit_loop_inline v else visit_loop_block v
1574 end
1575 end
1576
1577 redef class ABreakExpr
1578 redef fun accept_pretty_printer(v) do
1579 v.visit n_kwbreak
1580
1581 if n_expr != null then
1582 v.adds
1583 v.visit n_expr
1584 end
1585
1586 if n_label != null then
1587 v.adds
1588 v.visit n_label
1589 end
1590 end
1591
1592 redef fun is_inlinable do return true
1593 end
1594
1595 redef class AContinueExpr
1596 redef fun accept_pretty_printer(v) do
1597 v.visit n_kwcontinue
1598
1599 if n_expr != null then
1600 v.adds
1601 v.visit n_expr
1602 end
1603
1604 if n_label != null then
1605 v.adds
1606 v.visit n_label
1607 end
1608 end
1609
1610 redef fun is_inlinable do return true
1611 end
1612
1613 # Calls
1614
1615 redef class ASendExpr
1616 redef fun is_inlinable do return true
1617 end
1618
1619 redef class ACallExpr
1620 redef fun accept_pretty_printer(v) do
1621 var can_inline = v.can_inline(self)
1622 v.visit_recv n_expr
1623
1624 if not n_expr isa AImplicitSelfExpr and not can_inline then
1625 v.addn
1626 v.addt
1627 v.addt
1628 end
1629
1630 v.visit n_id
1631
1632 if not n_args.n_exprs.is_empty then
1633 if is_stmt and n_args.n_exprs.length == 1 then
1634 v.adds
1635 if v.current_token isa TOpar then v.skip
1636 v.visit n_args.n_exprs.first
1637 if v.current_token isa TCpar then v.skip
1638 else
1639 if v.current_token isa TOpar then
1640 v.consume "("
1641 else
1642 v.adds
1643 end
1644
1645 v.visit_list n_args.n_exprs
1646 if v.current_token isa TCpar then v.consume ")"
1647 end
1648 end
1649 end
1650
1651 # Is the call alone on its line?
1652 fun is_stmt: Bool do return parent isa ABlockExpr
1653 end
1654
1655 redef class ACallAssignExpr
1656 redef fun accept_pretty_printer(v) do
1657 v.visit_recv n_expr
1658 v.visit n_id
1659
1660 if not n_args.n_exprs.is_empty then
1661 v.consume "("
1662 v.visit_list n_args.n_exprs
1663 v.consume ")"
1664 end
1665
1666 v.adds
1667 v.consume "="
1668 v.adds
1669 v.visit n_value
1670 end
1671 end
1672
1673 redef class ACallReassignExpr
1674 redef fun accept_pretty_printer(v) do
1675 v.visit_recv n_expr
1676 v.visit n_id
1677
1678 if not n_args.n_exprs.is_empty then
1679 v.consume "("
1680 v.visit_list n_args.n_exprs
1681 v.consume ")"
1682 end
1683
1684 v.adds
1685 v.visit n_assign_op
1686 v.adds
1687 v.visit n_value
1688 end
1689 end
1690
1691 redef class ABraExpr
1692 redef fun accept_pretty_printer(v) do
1693 v.visit n_expr
1694
1695 if not n_args.n_exprs.is_empty then
1696 v.consume "["
1697 v.visit_list n_args.n_exprs
1698 v.consume "]"
1699 end
1700 end
1701 end
1702
1703 redef class ABraAssignExpr
1704 redef fun accept_pretty_printer(v) do
1705 v.visit n_expr
1706
1707 if not n_args.n_exprs.is_empty then
1708 v.consume "["
1709 v.visit_list n_args.n_exprs
1710 v.consume "]"
1711 end
1712
1713 v.adds
1714 v.visit n_assign
1715 v.adds
1716 v.visit n_value
1717 end
1718 end
1719
1720 redef class ABraReassignExpr
1721 redef fun accept_pretty_printer(v) do
1722 v.visit n_expr
1723
1724 if not n_args.n_exprs.is_empty then
1725 v.consume "["
1726 v.visit_list n_args.n_exprs
1727 v.consume "]"
1728 end
1729
1730 v.adds
1731 v.visit n_assign_op
1732 v.adds
1733 v.visit n_value
1734 end
1735 end
1736
1737 redef class AAssignMethid
1738 redef fun accept_pretty_printer(v) do
1739 v.visit n_id
1740 v.visit n_assign
1741 end
1742 end
1743
1744 redef class ABraMethid
1745 redef fun accept_pretty_printer(v) do
1746 v.visit n_obra
1747 v.visit n_cbra
1748 end
1749 end
1750
1751 redef class ABraassignMethid
1752 redef fun accept_pretty_printer(v) do
1753 v.visit n_obra
1754 v.visit n_cbra
1755 v.visit n_assign
1756 end
1757 end
1758
1759 redef class AInitExpr
1760 redef fun accept_pretty_printer(v) do
1761 if not n_expr isa AImplicitSelfExpr then
1762 v.visit n_expr
1763 v.consume "."
1764 end
1765
1766 v.visit n_kwinit
1767
1768 if not n_args.n_exprs.is_empty then
1769 v.consume "("
1770 v.visit_list n_args.n_exprs
1771 v.consume ")"
1772 end
1773 end
1774 end
1775
1776 redef class ANewExpr
1777 redef fun accept_pretty_printer(v) do
1778 var can_inline = v.can_inline(self)
1779 v.visit n_kwnew
1780 v.adds
1781 v.visit n_type
1782
1783 if n_id != null then
1784 v.consume "."
1785
1786 if not can_inline then
1787 v.addn
1788 v.addt
1789 v.addt
1790 end
1791
1792 v.visit n_id
1793 end
1794
1795 if not n_args.n_exprs.is_empty then
1796 v.consume "("
1797 v.visit_list n_args.n_exprs
1798 v.consume ")"
1799 end
1800 end
1801
1802 redef fun is_inlinable do return true
1803 end
1804
1805 # Attributes
1806
1807 redef class AAttrExpr
1808 redef fun accept_pretty_printer(v) do
1809 v.visit_recv n_expr
1810 v.visit n_id
1811 end
1812
1813 redef fun is_inlinable do return true
1814 end
1815
1816 redef class AAttrAssignExpr
1817 redef fun accept_pretty_printer(v) do
1818 v.visit_recv n_expr
1819 v.visit n_id
1820 v.adds
1821 v.visit n_assign
1822 v.adds
1823 v.visit n_value
1824 end
1825 end
1826
1827 redef class AAttrReassignExpr
1828 redef fun accept_pretty_printer(v) do
1829 v.visit_recv n_expr
1830 v.visit n_id
1831 v.adds
1832 v.visit n_assign_op
1833 v.adds
1834 v.visit n_value
1835 end
1836 end
1837
1838 # Exprs
1839
1840 redef class AVardeclExpr
1841 redef fun accept_pretty_printer(v) do
1842 v.visit n_kwvar
1843 v.adds
1844 v.visit n_id
1845
1846 if n_type != null then
1847 v.consume ":"
1848 v.adds
1849 v.visit n_type
1850 end
1851
1852 if n_expr != null then
1853 v.adds
1854 v.consume "="
1855 v.adds
1856 v.visit n_expr
1857 end
1858 end
1859
1860 redef fun is_inlinable do return true
1861 end
1862
1863 redef class AVarAssignExpr
1864 redef fun accept_pretty_printer(v) do
1865 v.visit n_id
1866 v.adds
1867 v.visit n_assign
1868 v.adds
1869 v.visit n_value
1870 end
1871 end
1872
1873 redef class AAssertExpr
1874 redef fun accept_pretty_printer(v) do
1875 var can_inline = v.can_inline(self)
1876 v.visit n_kwassert
1877
1878 if n_id != null then
1879 v.adds
1880 v.visit n_id
1881 v.consume ":"
1882 end
1883
1884 v.adds
1885 v.visit n_expr
1886 var n_else = self.n_else
1887
1888 if n_else != null then
1889 v.adds
1890 v.consume "else"
1891
1892 if can_inline then
1893 v.adds
1894 v.visit n_else
1895 else
1896 v.addn
1897
1898 if n_else isa ABlockExpr then
1899 v.indent += 1
1900 n_else.force_block = true
1901 v.visit n_else
1902 v.indent -= 1
1903 v.addt
1904 v.visit n_else.n_kwend
1905 else
1906 v.indent += 1
1907 v.addt
1908 v.visit n_else
1909 v.addn
1910 v.indent -= 1
1911 v.addt
1912 v.add "end"
1913 end
1914 end
1915 end
1916 end
1917
1918 redef fun is_inlinable do
1919 if not super then return false
1920 if n_else != null and not n_else.is_inlinable then return false
1921 return true
1922 end
1923 end
1924
1925 redef class AReturnExpr
1926 redef fun accept_pretty_printer(v) do
1927 v.visit n_kwreturn
1928
1929 if n_expr != null then
1930 v.adds
1931 v.visit n_expr
1932 end
1933 end
1934 end
1935
1936 redef class ASuperExpr
1937 redef fun accept_pretty_printer(v) do
1938 v.visit n_kwsuper
1939
1940 if not n_args.n_exprs.is_empty then
1941 if is_stmt and n_args.n_exprs.length == 1 then
1942 v.adds
1943 if v.current_token isa TOpar then v.skip
1944 v.visit n_args.n_exprs.first
1945 if v.current_token isa TCpar then v.skip
1946 else
1947 if v.current_token isa TOpar then
1948 v.consume "("
1949 else
1950 v.adds
1951 end
1952
1953 v.visit_list n_args.n_exprs
1954 if v.current_token isa TCpar then v.consume ")"
1955 end
1956 end
1957 end
1958
1959 # Is the call alone on its line?
1960 fun is_stmt: Bool do return self.first_token.is_starting_line
1961
1962 redef fun is_inlinable do return true
1963 end
1964
1965 redef class AOnceExpr
1966 redef fun accept_pretty_printer(v) do
1967 v.visit n_kwonce
1968 v.adds
1969 v.visit n_expr
1970 end
1971
1972 redef fun is_inlinable do return true
1973 end
1974
1975 redef class AAbortExpr
1976 redef fun accept_pretty_printer(v) do v.visit n_kwabort
1977 redef fun is_inlinable do return true
1978 end
1979
1980 redef class ANotExpr
1981 redef fun accept_pretty_printer(v) do
1982 v.visit n_kwnot
1983 v.adds
1984 v.visit n_expr
1985 end
1986 end
1987
1988 redef class AAsCastExpr
1989 redef fun accept_pretty_printer(v) do
1990 v.visit n_expr
1991 v.consume "."
1992 v.visit n_kwas
1993 v.visit n_opar
1994 v.visit n_type
1995 v.visit n_cpar
1996 end
1997 end
1998
1999 redef class AAsNotnullExpr
2000 redef fun accept_pretty_printer(v) do
2001 v.visit n_expr
2002 v.consume "."
2003 v.visit n_kwas
2004 v.visit n_opar
2005 v.visit n_kwnot
2006 v.adds
2007 v.visit n_kwnull
2008 v.visit n_cpar
2009 end
2010 end
2011
2012 # Binops
2013
2014 # Used to factorize work on Or, And, Implies and Binop expressions.
2015 private class ABinOpHelper
2016 super AExpr
2017
2018 fun bin_expr1: AExpr is abstract
2019 fun bin_expr2: AExpr is abstract
2020
2021 # Operator string
2022 fun bin_op: String is abstract
2023
2024 redef fun accept_pretty_printer(v) do
2025 var can_inline = v.can_inline(self)
2026
2027 if not can_inline then
2028 if (self isa ABinopExpr and bin_expr1 isa ABinopExpr) or
2029 (self isa AAndExpr and (bin_expr1 isa AAndExpr or bin_expr1 isa AOrExpr)) or
2030 (self isa AOrExpr and (bin_expr1 isa AAndExpr or bin_expr1 isa AOrExpr))
2031 then
2032 bin_expr1.force_block = true
2033 end
2034 end
2035
2036 v.visit bin_expr1
2037 v.adds
2038 v.consume bin_op
2039
2040 if can_inline then
2041 v.adds
2042 v.visit bin_expr2
2043 else
2044 v.addn
2045 v.addt
2046 v.addt
2047 v.visit bin_expr2
2048 end
2049 end
2050 end
2051
2052 redef class AAndExpr
2053 super ABinOpHelper
2054
2055 redef fun bin_expr1 do return n_expr
2056 redef fun bin_expr2 do return n_expr2
2057 redef fun bin_op do return "and"
2058 end
2059
2060 redef class AOrExpr
2061 super ABinOpHelper
2062
2063 redef fun bin_expr1 do return n_expr
2064 redef fun bin_expr2 do return n_expr2
2065 redef fun bin_op do return "or"
2066 end
2067
2068 redef class AImpliesExpr
2069 super ABinOpHelper
2070
2071 redef fun bin_expr1 do return n_expr
2072 redef fun bin_expr2 do return n_expr2
2073 redef fun bin_op do return "implies"
2074 end
2075
2076 redef class ABinopExpr
2077 super ABinOpHelper
2078
2079 redef fun bin_expr1 do return n_expr
2080 redef fun bin_expr2 do return n_expr2
2081 end
2082
2083 redef class AEqExpr
2084 redef fun bin_op do return "=="
2085 end
2086
2087 redef class AGeExpr
2088 redef fun bin_op do return ">="
2089 end
2090
2091 redef class AGgExpr
2092 redef fun bin_op do return ">>"
2093 end
2094
2095 redef class AGtExpr
2096 redef fun bin_op do return ">"
2097 end
2098
2099 redef class ALeExpr
2100 redef fun bin_op do return "<="
2101 end
2102
2103 redef class ALlExpr
2104 redef fun bin_op do return "<<"
2105 end
2106
2107 redef class ALtExpr
2108 redef fun bin_op do return "<"
2109 end
2110
2111 redef class AMinusExpr
2112 redef fun bin_op do return "-"
2113 end
2114
2115 redef class ANeExpr
2116 redef fun bin_op do return "!="
2117 end
2118
2119 redef class APercentExpr
2120 redef fun bin_op do return "%"
2121 end
2122
2123 redef class APlusExpr
2124 redef fun bin_op do return "+"
2125 end
2126
2127 redef class ASlashExpr
2128 redef fun bin_op do return "/"
2129 end
2130
2131 redef class AStarExpr
2132 redef fun bin_op do return "*"
2133 end
2134
2135 redef class AStarstarExpr
2136 redef fun bin_op do return "**"
2137 end
2138
2139 redef class AStarshipExpr
2140 redef fun bin_op do return "<=>"
2141 end
2142
2143 redef class AIsaExpr
2144 redef fun accept_pretty_printer(v) do
2145 v.visit n_expr
2146 v.adds
2147 v.consume "isa"
2148 v.adds
2149 v.visit n_type
2150 end
2151 end
2152
2153 redef class AOrElseExpr
2154 redef fun accept_pretty_printer(v) do
2155 v.visit n_expr
2156 v.adds
2157 v.consume "or"
2158 v.adds
2159 v.consume "else"
2160 v.adds
2161 v.visit n_expr2
2162 end
2163
2164 redef fun is_inlinable do return true
2165 end
2166
2167 # Syntax
2168
2169 redef class AUminusExpr
2170 redef fun accept_pretty_printer(v) do
2171 v.consume "-"
2172 v.visit n_expr
2173 end
2174 end
2175
2176 redef class ANullExpr
2177 redef fun accept_pretty_printer(v) do v.visit n_kwnull
2178 redef fun is_inlinable do return true
2179 end
2180
2181 redef class AParExpr
2182 redef fun accept_pretty_printer(v) do
2183 v.visit n_opar
2184 v.visit n_expr
2185 v.visit n_cpar
2186 end
2187 end
2188
2189 redef class AArrayExpr
2190 redef fun accept_pretty_printer(v) do
2191 v.consume "["
2192 v.visit_list n_exprs.n_exprs
2193 v.consume "]"
2194 end
2195 end
2196
2197 redef class ACrangeExpr
2198 redef fun accept_pretty_printer(v) do
2199 v.visit n_obra
2200 v.visit n_expr
2201 v.consume ".."
2202 v.visit n_expr2
2203 v.visit n_cbra
2204 end
2205 end
2206
2207 redef class AOrangeExpr
2208 redef fun accept_pretty_printer(v) do
2209 v.visit n_obra
2210 v.visit n_expr
2211 v.consume ".."
2212 v.visit n_expr2
2213 v.visit n_cbra
2214 end
2215 end
2216
2217 # Strings
2218
2219 redef class AStringFormExpr
2220 redef fun accept_pretty_printer(v) do
2221 var can_inline = v.can_inline(self)
2222
2223 if can_inline then
2224 v.visit n_string
2225 else
2226 var text = n_string.text
2227 var i = 0
2228
2229 while i < text.length do
2230 v.add text[i].to_s
2231
2232 if v.current_length >= v.max_size and i <= text.length - 3 then
2233 v.add "\" +"
2234 v.addn
2235 v.indent += 1
2236 v.addt
2237 v.indent -= 1
2238 v.add "\""
2239 end
2240
2241 i += 1
2242 end
2243
2244 v.current_token = n_string.next_token
2245 end
2246 end
2247 end
2248
2249 redef class ASuperstringExpr
2250 redef fun accept_pretty_printer(v) do
2251 for n_expr in n_exprs do v.visit n_expr
2252 end
2253
2254 redef fun must_be_inline do
2255 if super then return true
2256
2257 if not n_exprs.is_empty then
2258 var first = n_exprs.first
2259 return first isa AStringFormExpr and first.n_string.text.has_prefix("\"\"\"")
2260 end
2261
2262 return false
2263 end
2264 end
2265
2266 redef class ToolContext
2267 var opt_dir = new OptionString("Working directory (default is '.nitpretty')", "--dir")
2268
2269 var opt_output = new OptionString("Output name (default is pretty.nit)", "-o",
2270 "--output")
2271
2272 var opt_diff = new OptionBool("Show diff between source and output", "--diff")
2273
2274 var opt_meld = new OptionBool("Show diff between source and output using meld",
2275 "--meld")
2276
2277 var opt_check = new OptionBool("Check format of Nit source files", "--check")
2278 end
2279
2280 # Return result from diff between `file1` and `file2`.
2281 private fun diff(file1, file2: String): String do
2282 var p = new IProcess("diff", "-u", file1, file2)
2283 var res = p.read_all
2284 p.wait
2285 p.close
2286 return res
2287 end
2288
2289 # process options
2290 var toolcontext = new ToolContext
2291
2292 toolcontext.option_context.
2293 add_option(toolcontext.opt_dir, toolcontext.opt_output, toolcontext.opt_diff,
2294 toolcontext.opt_meld, toolcontext.opt_check)
2295
2296 toolcontext.tooldescription = "Usage: nitpretty [OPTION]... <file.nit>\n" +
2297 "Pretty print Nit code from Nit source files."
2298
2299 toolcontext.process_options args
2300 var arguments = toolcontext.option_context.rest
2301 # build model
2302 var model = new Model
2303 var mbuilder = new ModelBuilder(model, toolcontext)
2304 var mmodules = mbuilder.parse(arguments)
2305 mbuilder.run_phases
2306
2307 if mmodules.is_empty then
2308 print "Error: no module to pretty print"
2309 return
2310 end
2311
2312 if not toolcontext.opt_check.value and mmodules.length > 1 then
2313 print "Error: only --check option allow multiple modules"
2314 return
2315 end
2316
2317 var dir = toolcontext.opt_dir.value or else ".nitpretty"
2318 if not dir.file_exists then dir.mkdir
2319 var v = new PrettyPrinterVisitor
2320
2321 for mmodule in mmodules do
2322 if not mbuilder.mmodule2nmodule.has_key(mmodule) then
2323 print " Error: no source file for module {mmodule}"
2324 return
2325 end
2326
2327 var nmodule = mbuilder.mmodule2nmodule[mmodule]
2328 var file = "{dir}/{mmodule.name}.nit"
2329 var tpl = v.pretty_nmodule(nmodule)
2330 tpl.write_to_file file
2331
2332 if toolcontext.opt_check.value then
2333 var res = diff(nmodule.location.file.filename, file)
2334
2335 if not res.is_empty then
2336 print "Wrong formating for module {nmodule.location.file.filename}"
2337 toolcontext.info(res, 1)
2338
2339 if toolcontext.opt_meld.value then
2340 sys.system "meld {nmodule.location.file.filename} {file}"
2341 end
2342 else
2343 toolcontext.info("[OK] {nmodule.location.file.filename}", 1)
2344 end
2345 else
2346 # write to file
2347 var out = toolcontext.opt_output.value
2348 if out != null then sys.system "cp {file} {out}"
2349
2350 # open in meld
2351 if toolcontext.opt_meld.value then
2352 sys.system "meld {arguments.first} {file}"
2353 return
2354 end
2355
2356 # show diff
2357 if toolcontext.opt_diff.value then
2358 var res = diff(arguments.first, file)
2359 if not res.is_empty then print res
2360 return
2361 end
2362
2363 # show pretty
2364 if not toolcontext.opt_quiet.value then tpl.write_to sys.stdout
2365 end
2366 end