blob: 4ebc04f6964a87b42c41a3e64e7afe98adad57ef [file] [log] [blame]
/*
* Copyright 2016 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.turbine.tree;
import com.google.common.base.Joiner;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.turbine.model.TurbineTyKind;
import com.google.turbine.tree.Tree.Anno;
import com.google.turbine.tree.Tree.ClassLiteral;
import com.google.turbine.tree.Tree.Ident;
import com.google.turbine.tree.Tree.ModDecl;
import com.google.turbine.tree.Tree.ModDirective;
import com.google.turbine.tree.Tree.ModExports;
import com.google.turbine.tree.Tree.ModOpens;
import com.google.turbine.tree.Tree.ModProvides;
import com.google.turbine.tree.Tree.ModRequires;
import com.google.turbine.tree.Tree.ModUses;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.jspecify.nullness.Nullable;
/** A pretty-printer for {@link Tree}s. */
public class Pretty implements Tree.Visitor<@Nullable Void, @Nullable Void> {
static String pretty(Tree tree) {
Pretty pretty = new Pretty();
tree.accept(pretty, null);
return pretty.sb.toString();
}
private final StringBuilder sb = new StringBuilder();
int indent = 0;
boolean newLine = false;
void printLine() {
append('\n');
newLine = true;
}
void printLine(String line) {
if (!newLine) {
append('\n');
}
append(line).append('\n');
newLine = true;
}
@CanIgnoreReturnValue
Pretty append(char c) {
if (c == '\n') {
newLine = true;
} else if (newLine) {
sb.append(Strings.repeat(" ", indent * 2));
newLine = false;
}
sb.append(c);
return this;
}
@CanIgnoreReturnValue
Pretty append(String s) {
if (newLine) {
sb.append(Strings.repeat(" ", indent * 2));
newLine = false;
}
sb.append(s);
return this;
}
@Override
public @Nullable Void visitIdent(Ident ident, @Nullable Void input) {
sb.append(ident.value());
return null;
}
@Override
public @Nullable Void visitWildTy(Tree.WildTy wildTy, @Nullable Void input) {
printAnnos(wildTy.annos());
append('?');
if (wildTy.lower().isPresent()) {
append(" super ");
wildTy.lower().get().accept(this, null);
}
if (wildTy.upper().isPresent()) {
append(" extends ");
wildTy.upper().get().accept(this, null);
}
return null;
}
@Override
public @Nullable Void visitArrTy(Tree.ArrTy arrTy, @Nullable Void input) {
arrTy.elem().accept(this, null);
if (!arrTy.annos().isEmpty()) {
append(' ');
printAnnos(arrTy.annos());
}
append("[]");
return null;
}
@Override
public @Nullable Void visitPrimTy(Tree.PrimTy primTy, @Nullable Void input) {
append(primTy.tykind().toString());
return null;
}
@Override
public @Nullable Void visitVoidTy(Tree.VoidTy voidTy, @Nullable Void input) {
append("void");
return null;
}
@Override
public @Nullable Void visitClassTy(Tree.ClassTy classTy, @Nullable Void input) {
if (classTy.base().isPresent()) {
classTy.base().get().accept(this, null);
append('.');
}
printAnnos(classTy.annos());
append(classTy.name().value());
if (!classTy.tyargs().isEmpty()) {
append('<');
boolean first = true;
for (Tree t : classTy.tyargs()) {
if (!first) {
append(", ");
}
t.accept(this, null);
first = false;
}
append('>');
}
return null;
}
@Override
public @Nullable Void visitLiteral(Tree.Literal literal, @Nullable Void input) {
append(literal.value().toString());
return null;
}
@Override
public @Nullable Void visitParen(Tree.Paren paren, @Nullable Void input) {
paren.expr().accept(this, null);
return null;
}
@Override
public @Nullable Void visitTypeCast(Tree.TypeCast typeCast, @Nullable Void input) {
append('(');
typeCast.ty().accept(this, null);
append(") ");
typeCast.expr().accept(this, null);
return null;
}
@Override
public @Nullable Void visitUnary(Tree.Unary unary, @Nullable Void input) {
switch (unary.op()) {
case POST_INCR:
case POST_DECR:
unary.expr().accept(this, null);
append(unary.op().toString());
break;
case PRE_INCR:
case PRE_DECR:
case UNARY_PLUS:
case NEG:
case NOT:
case BITWISE_COMP:
append(unary.op().toString());
unary.expr().accept(this, null);
break;
default:
throw new AssertionError(unary.op().name());
}
return null;
}
@Override
public @Nullable Void visitBinary(Tree.Binary binary, @Nullable Void input) {
append('(');
boolean first = true;
for (Tree child : binary.children()) {
if (!first) {
append(" ").append(binary.op().toString()).append(" ");
}
child.accept(this, null);
first = false;
}
append(')');
return null;
}
@Override
public @Nullable Void visitConstVarName(Tree.ConstVarName constVarName, @Nullable Void input) {
append(Joiner.on('.').join(constVarName.name()));
return null;
}
@Override
public @Nullable Void visitClassLiteral(ClassLiteral classLiteral, @Nullable Void input) {
classLiteral.type().accept(this, input);
append(".class");
return null;
}
@Override
public @Nullable Void visitAssign(Tree.Assign assign, @Nullable Void input) {
append(assign.name().value()).append(" = ");
assign.expr().accept(this, null);
return null;
}
@Override
public @Nullable Void visitConditional(Tree.Conditional conditional, @Nullable Void input) {
append("(");
conditional.cond().accept(this, null);
append(" ? ");
conditional.iftrue().accept(this, null);
append(" : ");
conditional.iffalse().accept(this, null);
append(")");
return null;
}
@Override
public @Nullable Void visitArrayInit(Tree.ArrayInit arrayInit, @Nullable Void input) {
append('{');
boolean first = true;
for (Tree.Expression e : arrayInit.exprs()) {
if (!first) {
append(", ");
}
e.accept(this, null);
first = false;
}
append('}');
return null;
}
@Override
public @Nullable Void visitCompUnit(Tree.CompUnit compUnit, @Nullable Void input) {
if (compUnit.pkg().isPresent()) {
compUnit.pkg().get().accept(this, null);
printLine();
}
for (Tree.ImportDecl i : compUnit.imports()) {
i.accept(this, null);
}
if (compUnit.mod().isPresent()) {
printLine();
compUnit.mod().get().accept(this, null);
}
for (Tree.TyDecl decl : compUnit.decls()) {
printLine();
decl.accept(this, null);
}
return null;
}
@Override
public @Nullable Void visitImportDecl(Tree.ImportDecl importDecl, @Nullable Void input) {
append("import ");
if (importDecl.stat()) {
append("static ");
}
append(Joiner.on('.').join(importDecl.type()));
if (importDecl.wild()) {
append(".*");
}
append(";").append('\n');
return null;
}
@Override
public @Nullable Void visitVarDecl(Tree.VarDecl varDecl, @Nullable Void input) {
printVarDecl(varDecl);
append(';');
return null;
}
private void printVarDecl(Tree.VarDecl varDecl) {
printAnnos(varDecl.annos());
printModifiers(varDecl.mods());
varDecl.ty().accept(this, null);
append(' ').append(varDecl.name().value());
if (varDecl.init().isPresent()) {
append(" = ");
varDecl.init().get().accept(this, null);
}
}
private void printAnnos(ImmutableList<Anno> annos) {
for (Tree.Anno anno : annos) {
anno.accept(this, null);
append(' ');
}
}
@Override
public @Nullable Void visitMethDecl(Tree.MethDecl methDecl, @Nullable Void input) {
for (Tree.Anno anno : methDecl.annos()) {
anno.accept(this, null);
printLine();
}
printModifiers(methDecl.mods());
if (!methDecl.typarams().isEmpty()) {
append('<');
boolean first = true;
for (Tree.TyParam t : methDecl.typarams()) {
if (!first) {
append(", ");
}
t.accept(this, null);
first = false;
}
append('>');
append(' ');
}
if (methDecl.ret().isPresent()) {
methDecl.ret().get().accept(this, null);
append(' ');
}
append(methDecl.name().value());
append('(');
boolean first = true;
for (Tree.VarDecl param : methDecl.params()) {
if (!first) {
append(", ");
}
printVarDecl(param);
first = false;
}
append(')');
if (!methDecl.exntys().isEmpty()) {
append(" throws ");
first = true;
for (Tree.Type e : methDecl.exntys()) {
if (!first) {
append(", ");
}
e.accept(this, null);
first = false;
}
}
if (methDecl.defaultValue().isPresent()) {
append(" default ");
methDecl.defaultValue().get().accept(this, null);
append(";");
} else if (methDecl.mods().contains(TurbineModifier.ABSTRACT)
|| methDecl.mods().contains(TurbineModifier.NATIVE)) {
append(";");
} else {
append(" {}");
}
return null;
}
@Override
public @Nullable Void visitAnno(Tree.Anno anno, @Nullable Void input) {
append('@');
append(Joiner.on('.').join(anno.name()));
if (!anno.args().isEmpty()) {
append('(');
boolean first = true;
for (Tree.Expression e : anno.args()) {
if (!first) {
append(", ");
}
e.accept(this, null);
first = false;
}
append(')');
}
return null;
}
@Override
public @Nullable Void visitTyDecl(Tree.TyDecl tyDecl, @Nullable Void input) {
for (Tree.Anno anno : tyDecl.annos()) {
anno.accept(this, null);
printLine();
}
printModifiers(tyDecl.mods());
switch (tyDecl.tykind()) {
case CLASS:
append("class");
break;
case INTERFACE:
append("interface");
break;
case ENUM:
append("enum");
break;
case ANNOTATION:
append("@interface");
break;
case RECORD:
append("record");
break;
}
append(' ').append(tyDecl.name().value());
if (!tyDecl.typarams().isEmpty()) {
append('<');
boolean first = true;
for (Tree.TyParam t : tyDecl.typarams()) {
if (!first) {
append(", ");
}
t.accept(this, null);
first = false;
}
append('>');
}
if (tyDecl.tykind().equals(TurbineTyKind.RECORD)) {
append("(");
boolean first = true;
for (Tree.VarDecl c : tyDecl.components()) {
if (!first) {
append(", ");
}
printVarDecl(c);
first = false;
}
append(")");
}
if (tyDecl.xtnds().isPresent()) {
append(" extends ");
tyDecl.xtnds().get().accept(this, null);
}
if (!tyDecl.impls().isEmpty()) {
append(" implements ");
boolean first = true;
for (Tree.ClassTy t : tyDecl.impls()) {
if (!first) {
append(", ");
}
t.accept(this, null);
first = false;
}
}
if (!tyDecl.permits().isEmpty()) {
append(" permits ");
boolean first = true;
for (Tree.ClassTy t : tyDecl.permits()) {
if (!first) {
append(", ");
}
t.accept(this, null);
first = false;
}
}
append(" {").append('\n');
indent++;
switch (tyDecl.tykind()) {
case ENUM:
{
List<Tree> nonConsts = new ArrayList<>();
for (Tree t : tyDecl.members()) {
if (t instanceof Tree.VarDecl) {
Tree.VarDecl decl = (Tree.VarDecl) t;
if (decl.mods().contains(TurbineModifier.ACC_ENUM)) {
append(decl.name().value()).append(',').append('\n');
continue;
}
}
nonConsts.add(t);
}
printLine(";");
boolean first = true;
for (Tree t : nonConsts) {
if (!first) {
printLine();
}
t.accept(this, null);
first = false;
}
break;
}
default:
{
boolean first = true;
for (Tree t : tyDecl.members()) {
if (!first) {
printLine();
}
t.accept(this, null);
first = false;
}
break;
}
}
indent--;
printLine("}");
return null;
}
private void printModifiers(ImmutableSet<TurbineModifier> mods) {
List<TurbineModifier> modifiers = new ArrayList<>(mods);
Collections.sort(modifiers);
for (TurbineModifier mod : modifiers) {
switch (mod) {
case PRIVATE:
case PROTECTED:
case PUBLIC:
case ABSTRACT:
case FINAL:
case STATIC:
case VOLATILE:
case SYNCHRONIZED:
case STRICTFP:
case NATIVE:
case TRANSIENT:
case DEFAULT:
case TRANSITIVE:
case SEALED:
case NON_SEALED:
append(mod.toString()).append(' ');
break;
case ACC_SUPER:
case VARARGS:
case INTERFACE:
case ACC_ENUM:
case ACC_ANNOTATION:
case ACC_SYNTHETIC:
case ACC_BRIDGE:
case COMPACT_CTOR:
break;
}
}
}
@Override
public @Nullable Void visitTyParam(Tree.TyParam tyParam, @Nullable Void input) {
printAnnos(tyParam.annos());
append(tyParam.name().value());
if (!tyParam.bounds().isEmpty()) {
append(" extends ");
boolean first = true;
for (Tree bound : tyParam.bounds()) {
if (!first) {
append(" & ");
}
bound.accept(this, null);
first = false;
}
}
return null;
}
@Override
public @Nullable Void visitPkgDecl(Tree.PkgDecl pkgDecl, @Nullable Void input) {
for (Tree.Anno anno : pkgDecl.annos()) {
anno.accept(this, null);
printLine();
}
append("package ").append(Joiner.on('.').join(pkgDecl.name())).append(';');
return null;
}
@Override
public @Nullable Void visitModDecl(ModDecl modDecl, @Nullable Void input) {
for (Tree.Anno anno : modDecl.annos()) {
anno.accept(this, null);
printLine();
}
if (modDecl.open()) {
append("open ");
}
append("module ").append(modDecl.moduleName()).append(" {");
indent++;
append('\n');
for (ModDirective directive : modDecl.directives()) {
directive.accept(this, null);
}
indent--;
append("}\n");
return null;
}
@Override
public @Nullable Void visitModRequires(ModRequires modRequires, @Nullable Void input) {
append("requires ");
printModifiers(modRequires.mods());
append(modRequires.moduleName());
append(";");
append('\n');
return null;
}
@Override
public @Nullable Void visitModExports(ModExports modExports, @Nullable Void input) {
append("exports ");
append(modExports.packageName().replace('/', '.'));
if (!modExports.moduleNames().isEmpty()) {
append(" to").append('\n');
indent += 2;
boolean first = true;
for (String moduleName : modExports.moduleNames()) {
if (!first) {
append(',').append('\n');
}
append(moduleName);
first = false;
}
indent -= 2;
}
append(";");
append('\n');
return null;
}
@Override
public @Nullable Void visitModOpens(ModOpens modOpens, @Nullable Void input) {
append("opens ");
append(modOpens.packageName().replace('/', '.'));
if (!modOpens.moduleNames().isEmpty()) {
append(" to").append('\n');
indent += 2;
boolean first = true;
for (String moduleName : modOpens.moduleNames()) {
if (!first) {
append(',').append('\n');
}
append(moduleName);
first = false;
}
indent -= 2;
}
append(";");
append('\n');
return null;
}
@Override
public @Nullable Void visitModUses(ModUses modUses, @Nullable Void input) {
append("uses ");
append(Joiner.on('.').join(modUses.typeName()));
append(";");
append('\n');
return null;
}
@Override
public @Nullable Void visitModProvides(ModProvides modProvides, @Nullable Void input) {
append("provides ");
append(Joiner.on('.').join(modProvides.typeName()));
if (!modProvides.implNames().isEmpty()) {
append(" with").append('\n');
indent += 2;
boolean first = true;
for (ImmutableList<Ident> implName : modProvides.implNames()) {
if (!first) {
append(',').append('\n');
}
append(Joiner.on('.').join(implName));
first = false;
}
indent -= 2;
}
append(";");
append('\n');
return null;
}
}