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