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