blob: cf0b068839905cf3c09d871293ae2225db9ac5a2 [file] [log] [blame]
// Copyright 2019 The Bazel Authors. 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.devtools.build.lib.syntax;
import java.util.List;
/** A pretty-printer for Starlark syntax trees. */
final class NodePrinter {
private final StringBuilder buf;
private int indent;
NodePrinter(StringBuilder buf) {
this.buf = buf;
}
// Constructor exposed to legacy tests.
// TODO(adonovan): rewrite the tests not to care about the indent parameter.
NodePrinter(StringBuilder buf, int indent) {
this.buf = buf;
this.indent = indent;
}
// Main entry point for an arbitrary node.
// Called by Node.prettyPrint.
void printNode(Node n) {
if (n instanceof Expression) {
printExpr((Expression) n);
} else if (n instanceof Statement) {
printStmt((Statement) n);
} else if (n instanceof StarlarkFile) {
StarlarkFile file = (StarlarkFile) n;
// Only statements are printed, not comments.
for (Statement stmt : file.getStatements()) {
printStmt(stmt);
}
} else if (n instanceof Comment) {
Comment comment = (Comment) n;
// We can't really print comments in the right place anyway,
// due to how their relative order is lost in the representation
// of StarlarkFile. So don't bother word-wrapping and just print
// it on a single line.
printIndent();
buf.append("# ");
buf.append(comment.getValue());
} else if (n instanceof Argument) {
printArgument((Argument) n);
} else if (n instanceof Parameter) {
printParameter((Parameter) n);
} else if (n instanceof DictExpression.Entry) {
printDictEntry((DictExpression.Entry) n);
} else {
throw new IllegalArgumentException("unexpected: " + n.getClass());
}
}
private void printSuite(List<Statement> statements) {
// A suite is non-empty; pass statements are explicit.
indent++;
for (Statement stmt : statements) {
printStmt(stmt);
}
indent--;
}
private void printIndent() {
for (int i = 0; i < indent; i++) {
buf.append(" ");
}
}
private void printArgument(Argument arg) {
if (arg instanceof Argument.Positional) {
// nop
} else if (arg instanceof Argument.Keyword) {
buf.append(((Argument.Keyword) arg).getIdentifier().getName());
buf.append(" = ");
} else if (arg instanceof Argument.Star) {
buf.append('*');
} else if (arg instanceof Argument.StarStar) {
buf.append("**");
}
printExpr(arg.getValue());
}
private void printParameter(Parameter param) {
if (param instanceof Parameter.Mandatory) {
buf.append(param.getName());
} else if (param instanceof Parameter.Optional) {
buf.append(param.getName());
buf.append('=');
printExpr(param.getDefaultValue());
} else if (param instanceof Parameter.Star) {
buf.append('*');
if (param.getName() != null) {
buf.append(param.getName());
}
} else if (param instanceof Parameter.StarStar) {
buf.append("**");
buf.append(param.getName());
}
}
private void printDictEntry(DictExpression.Entry e) {
printExpr(e.getKey());
buf.append(": ");
printExpr(e.getValue());
}
// Appends "def f(a, ..., z):" to the buf.
// Also used by DefStatement.toString.
void printDefSignature(DefStatement def) {
buf.append("def ");
printExpr(def.getIdentifier());
buf.append('(');
String sep = "";
for (Parameter param : def.getParameters()) {
buf.append(sep);
printParameter(param);
sep = ", ";
}
buf.append("):");
}
private void printStmt(Statement s) {
printIndent();
switch (s.kind()) {
case ASSIGNMENT:
{
AssignmentStatement stmt = (AssignmentStatement) s;
printExpr(stmt.getLHS());
buf.append(' ');
if (stmt.isAugmented()) {
buf.append(stmt.getOperator());
}
buf.append("= ");
printExpr(stmt.getRHS());
buf.append('\n');
break;
}
case EXPRESSION:
{
ExpressionStatement stmt = (ExpressionStatement) s;
printExpr(stmt.getExpression());
buf.append('\n');
break;
}
case FLOW:
{
FlowStatement stmt = (FlowStatement) s;
buf.append(stmt.getKind()).append('\n');
break;
}
case FOR:
{
ForStatement stmt = (ForStatement) s;
buf.append("for ");
printExpr(stmt.getLHS());
buf.append(" in ");
printExpr(stmt.getCollection());
buf.append(":\n");
printSuite(stmt.getBlock());
break;
}
case DEF:
{
DefStatement stmt = (DefStatement) s;
printDefSignature(stmt);
buf.append('\n');
printSuite(stmt.getStatements());
break;
}
case IF:
{
IfStatement stmt = (IfStatement) s;
buf.append(stmt.isElif() ? "elif " : "if ");
printExpr(stmt.getCondition());
buf.append(":\n");
printSuite(stmt.getThenBlock());
List<Statement> elseBlock = stmt.getElseBlock();
if (elseBlock != null) {
if (elseBlock.size() == 1
&& elseBlock.get(0) instanceof IfStatement
&& ((IfStatement) elseBlock.get(0)).isElif()) {
printStmt(elseBlock.get(0));
} else {
printIndent();
buf.append("else:\n");
printSuite(elseBlock);
}
}
break;
}
case LOAD:
{
LoadStatement stmt = (LoadStatement) s;
buf.append("load(");
printExpr(stmt.getImport());
for (LoadStatement.Binding binding : stmt.getBindings()) {
buf.append(", ");
Identifier local = binding.getLocalName();
String origName = binding.getOriginalName().getName();
if (origName.equals(local.getName())) {
buf.append('"');
printExpr(local);
buf.append('"');
} else {
printExpr(local);
buf.append("=\"");
buf.append(origName);
buf.append('"');
}
}
buf.append(")\n");
break;
}
case RETURN:
{
ReturnStatement stmt = (ReturnStatement) s;
buf.append("return");
if (stmt.getReturnExpression() != null) {
buf.append(' ');
printExpr(stmt.getReturnExpression());
}
buf.append('\n');
break;
}
}
}
private void printExpr(Expression expr) {
switch (expr.kind()) {
case BINARY_OPERATOR:
{
BinaryOperatorExpression binop = (BinaryOperatorExpression) expr;
// TODO(bazel-team): retain parentheses in the syntax tree so we needn't
// conservatively emit them here.
buf.append('(');
printExpr(binop.getX());
buf.append(' ');
buf.append(binop.getOperator());
buf.append(' ');
printExpr(binop.getY());
buf.append(')');
break;
}
case COMPREHENSION:
{
Comprehension comp = (Comprehension) expr;
buf.append(comp.isDict() ? '{' : '[');
printNode(comp.getBody()); // Expression or DictExpression.Entry
for (Comprehension.Clause clause : comp.getClauses()) {
buf.append(' ');
if (clause instanceof Comprehension.For) {
Comprehension.For forClause = (Comprehension.For) clause;
buf.append("for ");
printExpr(forClause.getVars());
buf.append(" in ");
printExpr(forClause.getIterable());
} else {
Comprehension.If ifClause = (Comprehension.If) clause;
buf.append("if ");
printExpr(ifClause.getCondition());
}
}
buf.append(comp.isDict() ? '}' : ']');
break;
}
case CONDITIONAL:
{
ConditionalExpression cond = (ConditionalExpression) expr;
printExpr(cond.getThenCase());
buf.append(" if ");
printExpr(cond.getCondition());
buf.append(" else ");
printExpr(cond.getElseCase());
break;
}
case DICT_EXPR:
{
DictExpression dictexpr = (DictExpression) expr;
buf.append("{");
String sep = "";
for (DictExpression.Entry entry : dictexpr.getEntries()) {
buf.append(sep);
printDictEntry(entry);
sep = ", ";
}
buf.append("}");
break;
}
case DOT:
{
DotExpression dot = (DotExpression) expr;
printExpr(dot.getObject());
buf.append('.');
printExpr(dot.getField());
break;
}
case CALL:
{
CallExpression call = (CallExpression) expr;
printExpr(call.getFunction());
buf.append('(');
String sep = "";
for (Argument arg : call.getArguments()) {
buf.append(sep);
printArgument(arg);
sep = ", ";
}
buf.append(')');
break;
}
case IDENTIFIER:
buf.append(((Identifier) expr).getName());
break;
case INDEX:
{
IndexExpression index = (IndexExpression) expr;
printExpr(index.getObject());
buf.append('[');
printExpr(index.getKey());
buf.append(']');
break;
}
case INTEGER_LITERAL:
{
buf.append(((IntegerLiteral) expr).getValue());
break;
}
case LIST_EXPR:
{
ListExpression list = (ListExpression) expr;
buf.append(list.isTuple() ? '(' : '[');
String sep = "";
for (Expression e : list.getElements()) {
buf.append(sep);
printExpr(e);
sep = ", ";
}
if (list.isTuple() && list.getElements().size() == 1) {
buf.append(',');
}
buf.append(list.isTuple() ? ')' : ']');
break;
}
case SLICE:
{
SliceExpression slice = (SliceExpression) expr;
printExpr(slice.getObject());
buf.append('[');
// The first separator colon is unconditional.
// The second separator appears only if step is printed.
if (slice.getStart() != null) {
printExpr(slice.getStart());
}
buf.append(':');
if (slice.getStop() != null) {
printExpr(slice.getStop());
}
if (slice.getStep() != null) {
buf.append(':');
printExpr(slice.getStep());
}
buf.append(']');
break;
}
case STRING_LITERAL:
{
StringLiteral literal = (StringLiteral) expr;
String value = literal.getValue();
// TODO(adonovan): record the raw text of string (and integer) literals
// so that we can use the syntax tree for source modification tools.
// However, that may come with a memory cost until we start compiling
// (at which point the cost is only transient).
// For now, just simulate the behavior of repr(str).
buf.append('"');
for (int i = 0; i < value.length(); i++) {
char c = value.charAt(i);
switch (c) {
case '"':
buf.append("\\\"");
break;
case '\\':
buf.append("\\\\");
break;
case '\r':
buf.append("\\r");
break;
case '\n':
buf.append("\\n");
break;
case '\t':
buf.append("\\t");
break;
default:
// The Starlark spec (and lexer) are far from complete here,
// and it's hard to come up with a clean semantics for
// string escapes that serves Java (UTF-16) and Go (UTF-8).
// Clearly string literals should not contain non-printable
// characters. For now we'll continue to pretend that all
// non-printables are < 32, but this obviously false.
if (c < 32) {
buf.append(String.format("\\x%02x", (int) c));
} else {
buf.append(c);
}
}
}
buf.append('"');
break;
}
case UNARY_OPERATOR:
{
UnaryOperatorExpression unop = (UnaryOperatorExpression) expr;
// TODO(bazel-team): retain parentheses in the syntax tree so we needn't
// conservatively emit them here.
buf.append(unop.getOperator() == TokenKind.NOT ? "not " : unop.getOperator().toString());
buf.append('(');
printExpr(unop.getX());
buf.append(')');
break;
}
}
}
}