• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2016 Google Inc. All Rights Reserved.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.google.turbine.tree;
18 
19 import com.google.common.base.Joiner;
20 import com.google.common.base.Strings;
21 import com.google.common.collect.ImmutableList;
22 import com.google.common.collect.ImmutableSet;
23 import com.google.errorprone.annotations.CanIgnoreReturnValue;
24 import com.google.turbine.model.TurbineTyKind;
25 import com.google.turbine.tree.Tree.Anno;
26 import com.google.turbine.tree.Tree.ClassLiteral;
27 import com.google.turbine.tree.Tree.Ident;
28 import com.google.turbine.tree.Tree.ModDecl;
29 import com.google.turbine.tree.Tree.ModDirective;
30 import com.google.turbine.tree.Tree.ModExports;
31 import com.google.turbine.tree.Tree.ModOpens;
32 import com.google.turbine.tree.Tree.ModProvides;
33 import com.google.turbine.tree.Tree.ModRequires;
34 import com.google.turbine.tree.Tree.ModUses;
35 import java.util.ArrayList;
36 import java.util.Collections;
37 import java.util.List;
38 import org.jspecify.nullness.Nullable;
39 
40 /** A pretty-printer for {@link Tree}s. */
41 public class Pretty implements Tree.Visitor<@Nullable Void, @Nullable Void> {
42 
pretty(Tree tree)43   static String pretty(Tree tree) {
44     Pretty pretty = new Pretty();
45     tree.accept(pretty, null);
46     return pretty.sb.toString();
47   }
48 
49   private final StringBuilder sb = new StringBuilder();
50   int indent = 0;
51   boolean newLine = false;
52 
printLine()53   void printLine() {
54     append('\n');
55     newLine = true;
56   }
57 
printLine(String line)58   void printLine(String line) {
59     if (!newLine) {
60       append('\n');
61     }
62     append(line).append('\n');
63     newLine = true;
64   }
65 
66   @CanIgnoreReturnValue
append(char c)67   Pretty append(char c) {
68     if (c == '\n') {
69       newLine = true;
70     } else if (newLine) {
71       sb.append(Strings.repeat(" ", indent * 2));
72       newLine = false;
73     }
74     sb.append(c);
75     return this;
76   }
77 
78   @CanIgnoreReturnValue
append(String s)79   Pretty append(String s) {
80     if (newLine) {
81       sb.append(Strings.repeat(" ", indent * 2));
82       newLine = false;
83     }
84     sb.append(s);
85     return this;
86   }
87 
88   @Override
visitIdent(Ident ident, @Nullable Void input)89   public @Nullable Void visitIdent(Ident ident, @Nullable Void input) {
90     sb.append(ident.value());
91     return null;
92   }
93 
94   @Override
visitWildTy(Tree.WildTy wildTy, @Nullable Void input)95   public @Nullable Void visitWildTy(Tree.WildTy wildTy, @Nullable Void input) {
96     printAnnos(wildTy.annos());
97     append('?');
98     if (wildTy.lower().isPresent()) {
99       append(" super ");
100       wildTy.lower().get().accept(this, null);
101     }
102     if (wildTy.upper().isPresent()) {
103       append(" extends ");
104       wildTy.upper().get().accept(this, null);
105     }
106     return null;
107   }
108 
109   @Override
visitArrTy(Tree.ArrTy arrTy, @Nullable Void input)110   public @Nullable Void visitArrTy(Tree.ArrTy arrTy, @Nullable Void input) {
111     arrTy.elem().accept(this, null);
112     if (!arrTy.annos().isEmpty()) {
113       append(' ');
114       printAnnos(arrTy.annos());
115     }
116     append("[]");
117     return null;
118   }
119 
120   @Override
visitPrimTy(Tree.PrimTy primTy, @Nullable Void input)121   public @Nullable Void visitPrimTy(Tree.PrimTy primTy, @Nullable Void input) {
122     append(primTy.tykind().toString());
123     return null;
124   }
125 
126   @Override
visitVoidTy(Tree.VoidTy voidTy, @Nullable Void input)127   public @Nullable Void visitVoidTy(Tree.VoidTy voidTy, @Nullable Void input) {
128     append("void");
129     return null;
130   }
131 
132   @Override
visitClassTy(Tree.ClassTy classTy, @Nullable Void input)133   public @Nullable Void visitClassTy(Tree.ClassTy classTy, @Nullable Void input) {
134     if (classTy.base().isPresent()) {
135       classTy.base().get().accept(this, null);
136       append('.');
137     }
138     printAnnos(classTy.annos());
139     append(classTy.name().value());
140     if (!classTy.tyargs().isEmpty()) {
141       append('<');
142       boolean first = true;
143       for (Tree t : classTy.tyargs()) {
144         if (!first) {
145           append(", ");
146         }
147         t.accept(this, null);
148         first = false;
149       }
150       append('>');
151     }
152     return null;
153   }
154 
155   @Override
visitLiteral(Tree.Literal literal, @Nullable Void input)156   public @Nullable Void visitLiteral(Tree.Literal literal, @Nullable Void input) {
157     append(literal.value().toString());
158     return null;
159   }
160 
161   @Override
visitParen(Tree.Paren paren, @Nullable Void input)162   public @Nullable Void visitParen(Tree.Paren paren, @Nullable Void input) {
163     paren.expr().accept(this, null);
164     return null;
165   }
166 
167   @Override
visitTypeCast(Tree.TypeCast typeCast, @Nullable Void input)168   public @Nullable Void visitTypeCast(Tree.TypeCast typeCast, @Nullable Void input) {
169     append('(');
170     typeCast.ty().accept(this, null);
171     append(") ");
172     typeCast.expr().accept(this, null);
173     return null;
174   }
175 
176   @Override
visitUnary(Tree.Unary unary, @Nullable Void input)177   public @Nullable Void visitUnary(Tree.Unary unary, @Nullable Void input) {
178     switch (unary.op()) {
179       case POST_INCR:
180       case POST_DECR:
181         unary.expr().accept(this, null);
182         append(unary.op().toString());
183         break;
184       case PRE_INCR:
185       case PRE_DECR:
186       case UNARY_PLUS:
187       case NEG:
188       case NOT:
189       case BITWISE_COMP:
190         append(unary.op().toString());
191         unary.expr().accept(this, null);
192         break;
193       default:
194         throw new AssertionError(unary.op().name());
195     }
196     return null;
197   }
198 
199   @Override
visitBinary(Tree.Binary binary, @Nullable Void input)200   public @Nullable Void visitBinary(Tree.Binary binary, @Nullable Void input) {
201     append('(');
202     boolean first = true;
203     for (Tree child : binary.children()) {
204       if (!first) {
205         append(" ").append(binary.op().toString()).append(" ");
206       }
207       child.accept(this, null);
208       first = false;
209     }
210     append(')');
211     return null;
212   }
213 
214   @Override
visitConstVarName(Tree.ConstVarName constVarName, @Nullable Void input)215   public @Nullable Void visitConstVarName(Tree.ConstVarName constVarName, @Nullable Void input) {
216     append(Joiner.on('.').join(constVarName.name()));
217     return null;
218   }
219 
220   @Override
visitClassLiteral(ClassLiteral classLiteral, @Nullable Void input)221   public @Nullable Void visitClassLiteral(ClassLiteral classLiteral, @Nullable Void input) {
222     classLiteral.type().accept(this, input);
223     append(".class");
224     return null;
225   }
226 
227   @Override
visitAssign(Tree.Assign assign, @Nullable Void input)228   public @Nullable Void visitAssign(Tree.Assign assign, @Nullable Void input) {
229     append(assign.name().value()).append(" = ");
230     assign.expr().accept(this, null);
231     return null;
232   }
233 
234   @Override
visitConditional(Tree.Conditional conditional, @Nullable Void input)235   public @Nullable Void visitConditional(Tree.Conditional conditional, @Nullable Void input) {
236     append("(");
237     conditional.cond().accept(this, null);
238     append(" ? ");
239     conditional.iftrue().accept(this, null);
240     append(" : ");
241     conditional.iffalse().accept(this, null);
242     append(")");
243     return null;
244   }
245 
246   @Override
visitArrayInit(Tree.ArrayInit arrayInit, @Nullable Void input)247   public @Nullable Void visitArrayInit(Tree.ArrayInit arrayInit, @Nullable Void input) {
248     append('{');
249     boolean first = true;
250     for (Tree.Expression e : arrayInit.exprs()) {
251       if (!first) {
252         append(", ");
253       }
254       e.accept(this, null);
255       first = false;
256     }
257     append('}');
258     return null;
259   }
260 
261   @Override
visitCompUnit(Tree.CompUnit compUnit, @Nullable Void input)262   public @Nullable Void visitCompUnit(Tree.CompUnit compUnit, @Nullable Void input) {
263     if (compUnit.pkg().isPresent()) {
264       compUnit.pkg().get().accept(this, null);
265       printLine();
266     }
267     for (Tree.ImportDecl i : compUnit.imports()) {
268       i.accept(this, null);
269     }
270     if (compUnit.mod().isPresent()) {
271       printLine();
272       compUnit.mod().get().accept(this, null);
273     }
274     for (Tree.TyDecl decl : compUnit.decls()) {
275       printLine();
276       decl.accept(this, null);
277     }
278     return null;
279   }
280 
281   @Override
visitImportDecl(Tree.ImportDecl importDecl, @Nullable Void input)282   public @Nullable Void visitImportDecl(Tree.ImportDecl importDecl, @Nullable Void input) {
283     append("import ");
284     if (importDecl.stat()) {
285       append("static ");
286     }
287     append(Joiner.on('.').join(importDecl.type()));
288     if (importDecl.wild()) {
289       append(".*");
290     }
291     append(";").append('\n');
292     return null;
293   }
294 
295   @Override
visitVarDecl(Tree.VarDecl varDecl, @Nullable Void input)296   public @Nullable Void visitVarDecl(Tree.VarDecl varDecl, @Nullable Void input) {
297     printVarDecl(varDecl);
298     append(';');
299     return null;
300   }
301 
printVarDecl(Tree.VarDecl varDecl)302   private void printVarDecl(Tree.VarDecl varDecl) {
303     printAnnos(varDecl.annos());
304     printModifiers(varDecl.mods());
305     varDecl.ty().accept(this, null);
306     append(' ').append(varDecl.name().value());
307     if (varDecl.init().isPresent()) {
308       append(" = ");
309       varDecl.init().get().accept(this, null);
310     }
311   }
312 
printAnnos(ImmutableList<Anno> annos)313   private void printAnnos(ImmutableList<Anno> annos) {
314     for (Tree.Anno anno : annos) {
315       anno.accept(this, null);
316       append(' ');
317     }
318   }
319 
320   @Override
visitMethDecl(Tree.MethDecl methDecl, @Nullable Void input)321   public @Nullable Void visitMethDecl(Tree.MethDecl methDecl, @Nullable Void input) {
322     for (Tree.Anno anno : methDecl.annos()) {
323       anno.accept(this, null);
324       printLine();
325     }
326     printModifiers(methDecl.mods());
327     if (!methDecl.typarams().isEmpty()) {
328       append('<');
329       boolean first = true;
330       for (Tree.TyParam t : methDecl.typarams()) {
331         if (!first) {
332           append(", ");
333         }
334         t.accept(this, null);
335         first = false;
336       }
337       append('>');
338       append(' ');
339     }
340     if (methDecl.ret().isPresent()) {
341       methDecl.ret().get().accept(this, null);
342       append(' ');
343     }
344     append(methDecl.name().value());
345     append('(');
346     boolean first = true;
347     for (Tree.VarDecl param : methDecl.params()) {
348       if (!first) {
349         append(", ");
350       }
351       printVarDecl(param);
352       first = false;
353     }
354     append(')');
355     if (!methDecl.exntys().isEmpty()) {
356       append(" throws ");
357       first = true;
358       for (Tree.Type e : methDecl.exntys()) {
359         if (!first) {
360           append(", ");
361         }
362         e.accept(this, null);
363         first = false;
364       }
365     }
366     if (methDecl.defaultValue().isPresent()) {
367       append(" default ");
368       methDecl.defaultValue().get().accept(this, null);
369       append(";");
370     } else if (methDecl.mods().contains(TurbineModifier.ABSTRACT)
371         || methDecl.mods().contains(TurbineModifier.NATIVE)) {
372       append(";");
373     } else {
374       append(" {}");
375     }
376     return null;
377   }
378 
379   @Override
visitAnno(Tree.Anno anno, @Nullable Void input)380   public @Nullable Void visitAnno(Tree.Anno anno, @Nullable Void input) {
381     append('@');
382     append(Joiner.on('.').join(anno.name()));
383     if (!anno.args().isEmpty()) {
384       append('(');
385       boolean first = true;
386       for (Tree.Expression e : anno.args()) {
387         if (!first) {
388           append(", ");
389         }
390         e.accept(this, null);
391         first = false;
392       }
393       append(')');
394     }
395     return null;
396   }
397 
398   @Override
visitTyDecl(Tree.TyDecl tyDecl, @Nullable Void input)399   public @Nullable Void visitTyDecl(Tree.TyDecl tyDecl, @Nullable Void input) {
400     for (Tree.Anno anno : tyDecl.annos()) {
401       anno.accept(this, null);
402       printLine();
403     }
404     printModifiers(tyDecl.mods());
405     switch (tyDecl.tykind()) {
406       case CLASS:
407         append("class");
408         break;
409       case INTERFACE:
410         append("interface");
411         break;
412       case ENUM:
413         append("enum");
414         break;
415       case ANNOTATION:
416         append("@interface");
417         break;
418       case RECORD:
419         append("record");
420         break;
421     }
422     append(' ').append(tyDecl.name().value());
423     if (!tyDecl.typarams().isEmpty()) {
424       append('<');
425       boolean first = true;
426       for (Tree.TyParam t : tyDecl.typarams()) {
427         if (!first) {
428           append(", ");
429         }
430         t.accept(this, null);
431         first = false;
432       }
433       append('>');
434     }
435     if (tyDecl.tykind().equals(TurbineTyKind.RECORD)) {
436       append("(");
437       boolean first = true;
438       for (Tree.VarDecl c : tyDecl.components()) {
439         if (!first) {
440           append(", ");
441         }
442         printVarDecl(c);
443         first = false;
444       }
445       append(")");
446     }
447     if (tyDecl.xtnds().isPresent()) {
448       append(" extends ");
449       tyDecl.xtnds().get().accept(this, null);
450     }
451     if (!tyDecl.impls().isEmpty()) {
452       append(" implements ");
453       boolean first = true;
454       for (Tree.ClassTy t : tyDecl.impls()) {
455         if (!first) {
456           append(", ");
457         }
458         t.accept(this, null);
459         first = false;
460       }
461     }
462     if (!tyDecl.permits().isEmpty()) {
463       append(" permits ");
464       boolean first = true;
465       for (Tree.ClassTy t : tyDecl.permits()) {
466         if (!first) {
467           append(", ");
468         }
469         t.accept(this, null);
470         first = false;
471       }
472     }
473     append(" {").append('\n');
474     indent++;
475     switch (tyDecl.tykind()) {
476       case ENUM:
477         {
478           List<Tree> nonConsts = new ArrayList<>();
479           for (Tree t : tyDecl.members()) {
480             if (t instanceof Tree.VarDecl) {
481               Tree.VarDecl decl = (Tree.VarDecl) t;
482               if (decl.mods().contains(TurbineModifier.ACC_ENUM)) {
483                 append(decl.name().value()).append(',').append('\n');
484                 continue;
485               }
486             }
487             nonConsts.add(t);
488           }
489           printLine(";");
490           boolean first = true;
491           for (Tree t : nonConsts) {
492             if (!first) {
493               printLine();
494             }
495             t.accept(this, null);
496             first = false;
497           }
498           break;
499         }
500       default:
501         {
502           boolean first = true;
503           for (Tree t : tyDecl.members()) {
504             if (!first) {
505               printLine();
506             }
507             t.accept(this, null);
508             first = false;
509           }
510           break;
511         }
512     }
513     indent--;
514     printLine("}");
515     return null;
516   }
517 
printModifiers(ImmutableSet<TurbineModifier> mods)518   private void printModifiers(ImmutableSet<TurbineModifier> mods) {
519     List<TurbineModifier> modifiers = new ArrayList<>(mods);
520     Collections.sort(modifiers);
521     for (TurbineModifier mod : modifiers) {
522       switch (mod) {
523         case PRIVATE:
524         case PROTECTED:
525         case PUBLIC:
526         case ABSTRACT:
527         case FINAL:
528         case STATIC:
529         case VOLATILE:
530         case SYNCHRONIZED:
531         case STRICTFP:
532         case NATIVE:
533         case TRANSIENT:
534         case DEFAULT:
535         case TRANSITIVE:
536         case SEALED:
537         case NON_SEALED:
538           append(mod.toString()).append(' ');
539           break;
540         case ACC_SUPER:
541         case VARARGS:
542         case INTERFACE:
543         case ACC_ENUM:
544         case ACC_ANNOTATION:
545         case ACC_SYNTHETIC:
546         case ACC_BRIDGE:
547         case COMPACT_CTOR:
548           break;
549       }
550     }
551   }
552 
553   @Override
visitTyParam(Tree.TyParam tyParam, @Nullable Void input)554   public @Nullable Void visitTyParam(Tree.TyParam tyParam, @Nullable Void input) {
555     printAnnos(tyParam.annos());
556     append(tyParam.name().value());
557     if (!tyParam.bounds().isEmpty()) {
558       append(" extends ");
559       boolean first = true;
560       for (Tree bound : tyParam.bounds()) {
561         if (!first) {
562           append(" & ");
563         }
564         bound.accept(this, null);
565         first = false;
566       }
567     }
568     return null;
569   }
570 
571   @Override
visitPkgDecl(Tree.PkgDecl pkgDecl, @Nullable Void input)572   public @Nullable Void visitPkgDecl(Tree.PkgDecl pkgDecl, @Nullable Void input) {
573     for (Tree.Anno anno : pkgDecl.annos()) {
574       anno.accept(this, null);
575       printLine();
576     }
577     append("package ").append(Joiner.on('.').join(pkgDecl.name())).append(';');
578     return null;
579   }
580 
581   @Override
visitModDecl(ModDecl modDecl, @Nullable Void input)582   public @Nullable Void visitModDecl(ModDecl modDecl, @Nullable Void input) {
583     for (Tree.Anno anno : modDecl.annos()) {
584       anno.accept(this, null);
585       printLine();
586     }
587     if (modDecl.open()) {
588       append("open ");
589     }
590     append("module ").append(modDecl.moduleName()).append(" {");
591     indent++;
592     append('\n');
593     for (ModDirective directive : modDecl.directives()) {
594       directive.accept(this, null);
595     }
596     indent--;
597     append("}\n");
598     return null;
599   }
600 
601   @Override
visitModRequires(ModRequires modRequires, @Nullable Void input)602   public @Nullable Void visitModRequires(ModRequires modRequires, @Nullable Void input) {
603     append("requires ");
604     printModifiers(modRequires.mods());
605     append(modRequires.moduleName());
606     append(";");
607     append('\n');
608     return null;
609   }
610 
611   @Override
visitModExports(ModExports modExports, @Nullable Void input)612   public @Nullable Void visitModExports(ModExports modExports, @Nullable Void input) {
613     append("exports ");
614     append(modExports.packageName().replace('/', '.'));
615     if (!modExports.moduleNames().isEmpty()) {
616       append(" to").append('\n');
617       indent += 2;
618       boolean first = true;
619       for (String moduleName : modExports.moduleNames()) {
620         if (!first) {
621           append(',').append('\n');
622         }
623         append(moduleName);
624         first = false;
625       }
626       indent -= 2;
627     }
628     append(";");
629     append('\n');
630     return null;
631   }
632 
633   @Override
visitModOpens(ModOpens modOpens, @Nullable Void input)634   public @Nullable Void visitModOpens(ModOpens modOpens, @Nullable Void input) {
635     append("opens ");
636     append(modOpens.packageName().replace('/', '.'));
637     if (!modOpens.moduleNames().isEmpty()) {
638       append(" to").append('\n');
639       indent += 2;
640       boolean first = true;
641       for (String moduleName : modOpens.moduleNames()) {
642         if (!first) {
643           append(',').append('\n');
644         }
645         append(moduleName);
646         first = false;
647       }
648       indent -= 2;
649     }
650     append(";");
651     append('\n');
652     return null;
653   }
654 
655   @Override
visitModUses(ModUses modUses, @Nullable Void input)656   public @Nullable Void visitModUses(ModUses modUses, @Nullable Void input) {
657     append("uses ");
658     append(Joiner.on('.').join(modUses.typeName()));
659     append(";");
660     append('\n');
661     return null;
662   }
663 
664   @Override
visitModProvides(ModProvides modProvides, @Nullable Void input)665   public @Nullable Void visitModProvides(ModProvides modProvides, @Nullable Void input) {
666     append("provides ");
667     append(Joiner.on('.').join(modProvides.typeName()));
668     if (!modProvides.implNames().isEmpty()) {
669       append(" with").append('\n');
670       indent += 2;
671       boolean first = true;
672       for (ImmutableList<Ident> implName : modProvides.implNames()) {
673         if (!first) {
674           append(',').append('\n');
675         }
676         append(Joiner.on('.').join(implName));
677         first = false;
678       }
679       indent -= 2;
680     }
681     append(";");
682     append('\n');
683     return null;
684   }
685 }
686