• 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.annotations.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     ImmutableList.Builder<Tree.ArrTy> flat = ImmutableList.builder();
112     Tree next = arrTy;
113     do {
114       Tree.ArrTy curr = (Tree.ArrTy) next;
115       flat.add(curr);
116       next = curr.elem();
117     } while (next.kind().equals(Tree.Kind.ARR_TY));
118 
119     next.accept(this, null);
120     for (Tree.ArrTy dim : flat.build()) {
121       if (!dim.annos().isEmpty()) {
122         append(' ');
123         printAnnos(dim.annos());
124       }
125       append("[]");
126     }
127     return null;
128   }
129 
130   @Override
visitPrimTy(Tree.PrimTy primTy, @Nullable Void input)131   public @Nullable Void visitPrimTy(Tree.PrimTy primTy, @Nullable Void input) {
132     printAnnos(primTy.annos());
133     append(primTy.tykind().toString());
134     return null;
135   }
136 
137   @Override
visitVoidTy(Tree.VoidTy voidTy, @Nullable Void input)138   public @Nullable Void visitVoidTy(Tree.VoidTy voidTy, @Nullable Void input) {
139     append("void");
140     return null;
141   }
142 
143   @Override
visitClassTy(Tree.ClassTy classTy, @Nullable Void input)144   public @Nullable Void visitClassTy(Tree.ClassTy classTy, @Nullable Void input) {
145     if (classTy.base().isPresent()) {
146       classTy.base().get().accept(this, null);
147       append('.');
148     }
149     printAnnos(classTy.annos());
150     append(classTy.name().value());
151     if (!classTy.tyargs().isEmpty()) {
152       append('<');
153       boolean first = true;
154       for (Tree t : classTy.tyargs()) {
155         if (!first) {
156           append(", ");
157         }
158         t.accept(this, null);
159         first = false;
160       }
161       append('>');
162     }
163     return null;
164   }
165 
166   @Override
visitLiteral(Tree.Literal literal, @Nullable Void input)167   public @Nullable Void visitLiteral(Tree.Literal literal, @Nullable Void input) {
168     append(literal.value().toString());
169     return null;
170   }
171 
172   @Override
visitParen(Tree.Paren paren, @Nullable Void input)173   public @Nullable Void visitParen(Tree.Paren paren, @Nullable Void input) {
174     paren.expr().accept(this, null);
175     return null;
176   }
177 
178   @Override
visitTypeCast(Tree.TypeCast typeCast, @Nullable Void input)179   public @Nullable Void visitTypeCast(Tree.TypeCast typeCast, @Nullable Void input) {
180     append('(');
181     typeCast.ty().accept(this, null);
182     append(") ");
183     typeCast.expr().accept(this, null);
184     return null;
185   }
186 
187   @Override
visitUnary(Tree.Unary unary, @Nullable Void input)188   public @Nullable Void visitUnary(Tree.Unary unary, @Nullable Void input) {
189     switch (unary.op()) {
190       case POST_INCR:
191       case POST_DECR:
192         unary.expr().accept(this, null);
193         append(unary.op().toString());
194         break;
195       case PRE_INCR:
196       case PRE_DECR:
197       case UNARY_PLUS:
198       case NEG:
199       case NOT:
200       case BITWISE_COMP:
201         append(unary.op().toString());
202         unary.expr().accept(this, null);
203         break;
204       default:
205         throw new AssertionError(unary.op().name());
206     }
207     return null;
208   }
209 
210   @Override
visitBinary(Tree.Binary binary, @Nullable Void input)211   public @Nullable Void visitBinary(Tree.Binary binary, @Nullable Void input) {
212     append('(');
213     boolean first = true;
214     for (Tree child : binary.children()) {
215       if (!first) {
216         append(" ").append(binary.op().toString()).append(" ");
217       }
218       child.accept(this, null);
219       first = false;
220     }
221     append(')');
222     return null;
223   }
224 
225   @Override
visitConstVarName(Tree.ConstVarName constVarName, @Nullable Void input)226   public @Nullable Void visitConstVarName(Tree.ConstVarName constVarName, @Nullable Void input) {
227     append(Joiner.on('.').join(constVarName.name()));
228     return null;
229   }
230 
231   @Override
visitClassLiteral(ClassLiteral classLiteral, @Nullable Void input)232   public @Nullable Void visitClassLiteral(ClassLiteral classLiteral, @Nullable Void input) {
233     classLiteral.type().accept(this, input);
234     append(".class");
235     return null;
236   }
237 
238   @Override
visitAssign(Tree.Assign assign, @Nullable Void input)239   public @Nullable Void visitAssign(Tree.Assign assign, @Nullable Void input) {
240     append(assign.name().value()).append(" = ");
241     assign.expr().accept(this, null);
242     return null;
243   }
244 
245   @Override
visitConditional(Tree.Conditional conditional, @Nullable Void input)246   public @Nullable Void visitConditional(Tree.Conditional conditional, @Nullable Void input) {
247     append("(");
248     conditional.cond().accept(this, null);
249     append(" ? ");
250     conditional.iftrue().accept(this, null);
251     append(" : ");
252     conditional.iffalse().accept(this, null);
253     append(")");
254     return null;
255   }
256 
257   @Override
visitArrayInit(Tree.ArrayInit arrayInit, @Nullable Void input)258   public @Nullable Void visitArrayInit(Tree.ArrayInit arrayInit, @Nullable Void input) {
259     append('{');
260     boolean first = true;
261     for (Tree.Expression e : arrayInit.exprs()) {
262       if (!first) {
263         append(", ");
264       }
265       e.accept(this, null);
266       first = false;
267     }
268     append('}');
269     return null;
270   }
271 
272   @Override
visitCompUnit(Tree.CompUnit compUnit, @Nullable Void input)273   public @Nullable Void visitCompUnit(Tree.CompUnit compUnit, @Nullable Void input) {
274     if (compUnit.pkg().isPresent()) {
275       compUnit.pkg().get().accept(this, null);
276       printLine();
277     }
278     for (Tree.ImportDecl i : compUnit.imports()) {
279       i.accept(this, null);
280     }
281     if (compUnit.mod().isPresent()) {
282       printLine();
283       compUnit.mod().get().accept(this, null);
284     }
285     for (Tree.TyDecl decl : compUnit.decls()) {
286       printLine();
287       decl.accept(this, null);
288     }
289     return null;
290   }
291 
292   @Override
visitImportDecl(Tree.ImportDecl importDecl, @Nullable Void input)293   public @Nullable Void visitImportDecl(Tree.ImportDecl importDecl, @Nullable Void input) {
294     append("import ");
295     if (importDecl.stat()) {
296       append("static ");
297     }
298     append(Joiner.on('.').join(importDecl.type()));
299     if (importDecl.wild()) {
300       append(".*");
301     }
302     append(";").append('\n');
303     return null;
304   }
305 
306   @Override
visitVarDecl(Tree.VarDecl varDecl, @Nullable Void input)307   public @Nullable Void visitVarDecl(Tree.VarDecl varDecl, @Nullable Void input) {
308     printVarDecl(varDecl);
309     append(';');
310     return null;
311   }
312 
printVarDecl(Tree.VarDecl varDecl)313   private void printVarDecl(Tree.VarDecl varDecl) {
314     printAnnos(varDecl.annos());
315     printModifiers(varDecl.mods());
316     varDecl.ty().accept(this, null);
317     append(' ').append(varDecl.name().value());
318     if (varDecl.init().isPresent()) {
319       append(" = ");
320       varDecl.init().get().accept(this, null);
321     }
322   }
323 
printAnnos(ImmutableList<Anno> annos)324   private void printAnnos(ImmutableList<Anno> annos) {
325     for (Tree.Anno anno : annos) {
326       anno.accept(this, null);
327       append(' ');
328     }
329   }
330 
331   @Override
visitMethDecl(Tree.MethDecl methDecl, @Nullable Void input)332   public @Nullable Void visitMethDecl(Tree.MethDecl methDecl, @Nullable Void input) {
333     for (Tree.Anno anno : methDecl.annos()) {
334       anno.accept(this, null);
335       printLine();
336     }
337     printModifiers(methDecl.mods());
338     if (!methDecl.typarams().isEmpty()) {
339       append('<');
340       boolean first = true;
341       for (Tree.TyParam t : methDecl.typarams()) {
342         if (!first) {
343           append(", ");
344         }
345         t.accept(this, null);
346         first = false;
347       }
348       append('>');
349       append(' ');
350     }
351     if (methDecl.ret().isPresent()) {
352       methDecl.ret().get().accept(this, null);
353       append(' ');
354     }
355     append(methDecl.name().value());
356     append('(');
357     boolean first = true;
358     for (Tree.VarDecl param : methDecl.params()) {
359       if (!first) {
360         append(", ");
361       }
362       printVarDecl(param);
363       first = false;
364     }
365     append(')');
366     if (!methDecl.exntys().isEmpty()) {
367       append(" throws ");
368       first = true;
369       for (Tree.Type e : methDecl.exntys()) {
370         if (!first) {
371           append(", ");
372         }
373         e.accept(this, null);
374         first = false;
375       }
376     }
377     if (methDecl.defaultValue().isPresent()) {
378       append(" default ");
379       methDecl.defaultValue().get().accept(this, null);
380       append(";");
381     } else if (methDecl.mods().contains(TurbineModifier.ABSTRACT)
382         || methDecl.mods().contains(TurbineModifier.NATIVE)) {
383       append(";");
384     } else {
385       append(" {}");
386     }
387     return null;
388   }
389 
390   @Override
visitAnno(Tree.Anno anno, @Nullable Void input)391   public @Nullable Void visitAnno(Tree.Anno anno, @Nullable Void input) {
392     append('@');
393     append(Joiner.on('.').join(anno.name()));
394     if (!anno.args().isEmpty()) {
395       append('(');
396       boolean first = true;
397       for (Tree.Expression e : anno.args()) {
398         if (!first) {
399           append(", ");
400         }
401         e.accept(this, null);
402         first = false;
403       }
404       append(')');
405     }
406     return null;
407   }
408 
409   @Override
visitTyDecl(Tree.TyDecl tyDecl, @Nullable Void input)410   public @Nullable Void visitTyDecl(Tree.TyDecl tyDecl, @Nullable Void input) {
411     for (Tree.Anno anno : tyDecl.annos()) {
412       anno.accept(this, null);
413       printLine();
414     }
415     printModifiers(tyDecl.mods());
416     switch (tyDecl.tykind()) {
417       case CLASS:
418         append("class");
419         break;
420       case INTERFACE:
421         append("interface");
422         break;
423       case ENUM:
424         append("enum");
425         break;
426       case ANNOTATION:
427         append("@interface");
428         break;
429       case RECORD:
430         append("record");
431         break;
432     }
433     append(' ').append(tyDecl.name().value());
434     if (!tyDecl.typarams().isEmpty()) {
435       append('<');
436       boolean first = true;
437       for (Tree.TyParam t : tyDecl.typarams()) {
438         if (!first) {
439           append(", ");
440         }
441         t.accept(this, null);
442         first = false;
443       }
444       append('>');
445     }
446     if (tyDecl.tykind().equals(TurbineTyKind.RECORD)) {
447       append("(");
448       boolean first = true;
449       for (Tree.VarDecl c : tyDecl.components()) {
450         if (!first) {
451           append(", ");
452         }
453         printVarDecl(c);
454         first = false;
455       }
456       append(")");
457     }
458     if (tyDecl.xtnds().isPresent()) {
459       append(" extends ");
460       tyDecl.xtnds().get().accept(this, null);
461     }
462     if (!tyDecl.impls().isEmpty()) {
463       append(" implements ");
464       boolean first = true;
465       for (Tree.ClassTy t : tyDecl.impls()) {
466         if (!first) {
467           append(", ");
468         }
469         t.accept(this, null);
470         first = false;
471       }
472     }
473     if (!tyDecl.permits().isEmpty()) {
474       append(" permits ");
475       boolean first = true;
476       for (Tree.ClassTy t : tyDecl.permits()) {
477         if (!first) {
478           append(", ");
479         }
480         t.accept(this, null);
481         first = false;
482       }
483     }
484     append(" {").append('\n');
485     indent++;
486     switch (tyDecl.tykind()) {
487       case ENUM:
488         {
489           List<Tree> nonConsts = new ArrayList<>();
490           for (Tree t : tyDecl.members()) {
491             if (t instanceof Tree.VarDecl) {
492               Tree.VarDecl decl = (Tree.VarDecl) t;
493               if (decl.mods().contains(TurbineModifier.ACC_ENUM)) {
494                 append(decl.name().value()).append(',').append('\n');
495                 continue;
496               }
497             }
498             nonConsts.add(t);
499           }
500           printLine(";");
501           boolean first = true;
502           for (Tree t : nonConsts) {
503             if (!first) {
504               printLine();
505             }
506             t.accept(this, null);
507             first = false;
508           }
509           break;
510         }
511       default:
512         {
513           boolean first = true;
514           for (Tree t : tyDecl.members()) {
515             if (!first) {
516               printLine();
517             }
518             t.accept(this, null);
519             first = false;
520           }
521           break;
522         }
523     }
524     indent--;
525     printLine("}");
526     return null;
527   }
528 
printModifiers(ImmutableSet<TurbineModifier> mods)529   private void printModifiers(ImmutableSet<TurbineModifier> mods) {
530     List<TurbineModifier> modifiers = new ArrayList<>(mods);
531     Collections.sort(modifiers);
532     for (TurbineModifier mod : modifiers) {
533       switch (mod) {
534         case PRIVATE:
535         case PROTECTED:
536         case PUBLIC:
537         case ABSTRACT:
538         case FINAL:
539         case STATIC:
540         case VOLATILE:
541         case SYNCHRONIZED:
542         case STRICTFP:
543         case NATIVE:
544         case TRANSIENT:
545         case DEFAULT:
546         case TRANSITIVE:
547         case SEALED:
548         case NON_SEALED:
549           append(mod.toString()).append(' ');
550           break;
551         case ACC_SUPER:
552         case VARARGS:
553         case INTERFACE:
554         case ACC_ENUM:
555         case ACC_ANNOTATION:
556         case ACC_SYNTHETIC:
557         case ACC_BRIDGE:
558         case COMPACT_CTOR:
559         case ENUM_IMPL:
560           break;
561       }
562     }
563   }
564 
565   @Override
visitTyParam(Tree.TyParam tyParam, @Nullable Void input)566   public @Nullable Void visitTyParam(Tree.TyParam tyParam, @Nullable Void input) {
567     printAnnos(tyParam.annos());
568     append(tyParam.name().value());
569     if (!tyParam.bounds().isEmpty()) {
570       append(" extends ");
571       boolean first = true;
572       for (Tree bound : tyParam.bounds()) {
573         if (!first) {
574           append(" & ");
575         }
576         bound.accept(this, null);
577         first = false;
578       }
579     }
580     return null;
581   }
582 
583   @Override
visitPkgDecl(Tree.PkgDecl pkgDecl, @Nullable Void input)584   public @Nullable Void visitPkgDecl(Tree.PkgDecl pkgDecl, @Nullable Void input) {
585     for (Tree.Anno anno : pkgDecl.annos()) {
586       anno.accept(this, null);
587       printLine();
588     }
589     append("package ").append(Joiner.on('.').join(pkgDecl.name())).append(';');
590     return null;
591   }
592 
593   @Override
visitModDecl(ModDecl modDecl, @Nullable Void input)594   public @Nullable Void visitModDecl(ModDecl modDecl, @Nullable Void input) {
595     for (Tree.Anno anno : modDecl.annos()) {
596       anno.accept(this, null);
597       printLine();
598     }
599     if (modDecl.open()) {
600       append("open ");
601     }
602     append("module ").append(modDecl.moduleName()).append(" {");
603     indent++;
604     append('\n');
605     for (ModDirective directive : modDecl.directives()) {
606       directive.accept(this, null);
607     }
608     indent--;
609     append("}\n");
610     return null;
611   }
612 
613   @Override
visitModRequires(ModRequires modRequires, @Nullable Void input)614   public @Nullable Void visitModRequires(ModRequires modRequires, @Nullable Void input) {
615     append("requires ");
616     printModifiers(modRequires.mods());
617     append(modRequires.moduleName());
618     append(";");
619     append('\n');
620     return null;
621   }
622 
623   @Override
visitModExports(ModExports modExports, @Nullable Void input)624   public @Nullable Void visitModExports(ModExports modExports, @Nullable Void input) {
625     append("exports ");
626     append(modExports.packageName().replace('/', '.'));
627     if (!modExports.moduleNames().isEmpty()) {
628       append(" to").append('\n');
629       indent += 2;
630       boolean first = true;
631       for (String moduleName : modExports.moduleNames()) {
632         if (!first) {
633           append(',').append('\n');
634         }
635         append(moduleName);
636         first = false;
637       }
638       indent -= 2;
639     }
640     append(";");
641     append('\n');
642     return null;
643   }
644 
645   @Override
visitModOpens(ModOpens modOpens, @Nullable Void input)646   public @Nullable Void visitModOpens(ModOpens modOpens, @Nullable Void input) {
647     append("opens ");
648     append(modOpens.packageName().replace('/', '.'));
649     if (!modOpens.moduleNames().isEmpty()) {
650       append(" to").append('\n');
651       indent += 2;
652       boolean first = true;
653       for (String moduleName : modOpens.moduleNames()) {
654         if (!first) {
655           append(',').append('\n');
656         }
657         append(moduleName);
658         first = false;
659       }
660       indent -= 2;
661     }
662     append(";");
663     append('\n');
664     return null;
665   }
666 
667   @Override
visitModUses(ModUses modUses, @Nullable Void input)668   public @Nullable Void visitModUses(ModUses modUses, @Nullable Void input) {
669     append("uses ");
670     append(Joiner.on('.').join(modUses.typeName()));
671     append(";");
672     append('\n');
673     return null;
674   }
675 
676   @Override
visitModProvides(ModProvides modProvides, @Nullable Void input)677   public @Nullable Void visitModProvides(ModProvides modProvides, @Nullable Void input) {
678     append("provides ");
679     append(Joiner.on('.').join(modProvides.typeName()));
680     if (!modProvides.implNames().isEmpty()) {
681       append(" with").append('\n');
682       indent += 2;
683       boolean first = true;
684       for (ImmutableList<Ident> implName : modProvides.implNames()) {
685         if (!first) {
686           append(',').append('\n');
687         }
688         append(Joiner.on('.').join(implName));
689         first = false;
690       }
691       indent -= 2;
692     }
693     append(";");
694     append('\n');
695     return null;
696   }
697 }
698