Skip to content

Commit

Permalink
Add a pretty printer for Skylark ASTs
Browse files Browse the repository at this point in the history
This can be used to canonically compare ASTs for equality, e.g. in tests.

RELNOTES: None
PiperOrigin-RevId: 160283160
  • Loading branch information
brandjon authored and hlopko committed Jun 28, 2017
1 parent 3bc1547 commit e2ffd5d
Show file tree
Hide file tree
Showing 38 changed files with 984 additions and 137 deletions.
80 changes: 76 additions & 4 deletions src/main/java/com/google/devtools/build/lib/syntax/ASTNode.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@

import com.google.common.annotations.VisibleForTesting;
import com.google.devtools.build.lib.events.Location;

import java.io.IOException;
import java.io.Serializable;
import java.util.List;

/**
* Root class for nodes in the Abstract Syntax Tree of the Build language.
Expand Down Expand Up @@ -73,12 +74,83 @@ public static <NODE extends ASTNode> NODE setLocation(Location location, NODE no
return node;
}

/** Number of spaces that each indentation level expands to when pretty-printing. */
public static final int INDENT_WIDTH = 2;

/** Writes out the indentation prefix for a line. */
protected void printIndent(Appendable buffer, int indentLevel) throws IOException {
for (int i = 0; i < indentLevel * INDENT_WIDTH; i++) {
buffer.append(' ');
}
}

/**
* Print the syntax node in a form useful for debugging. The output is not
* precisely specified, and should not be used by pretty-printing routines.
* Writes out a suite of statements. The statements are indented one more level than given, i.e.,
* the {@code indentLevel} parameter should be the same as the parent node's.
*
* <p>This also prints out a {@code pass} line if the suite is empty.
*/
protected void printSuite(Appendable buffer, List<Statement> statements, int parentIndentLevel)
throws IOException {
if (statements.isEmpty()) {
printIndent(buffer, parentIndentLevel + 1);
buffer.append("pass\n");
} else {
for (Statement stmt : statements) {
stmt.prettyPrint(buffer, parentIndentLevel + 1);
}
}
}

/**
* Writes a pretty-printed representation of this node to a buffer, assuming the given starting
* indentation level.
*
* <p>For expressions, the indentation level is ignored. For statements, the indentation is
* written, then the statement contents (which may include multiple lines with their own
* indentation), then a newline character.
*
* <p>Indentation expands to {@code INDENT_WIDTH} many spaces per indent.
*
* <p>Pretty printing returns the canonical source code corresponding to an AST. Generally, the
* output can be round-tripped: Pretty printing an AST and then parsing the result should give you
* back an equivalent AST.
*
* <p>Pretty printing can also be used as a proxy for comparing for equality between two ASTs.
* This can be very useful in tests. However, it is still possible for two different trees to have
* the same pretty printing. In particular, {@link BuildFileAST} includes import metadata and
* comment information that is not reflected in the string.
*/
public abstract void prettyPrint(Appendable buffer, int indentLevel) throws IOException;

/** Same as {@link #prettyPrint(Appendable, int)}, except with no indent. */
public void prettyPrint(Appendable buffer) throws IOException {
prettyPrint(buffer, 0);
}

/** Returns a pretty-printed representation of this node. */
public String prettyPrint() {
StringBuilder builder = new StringBuilder();
try {
prettyPrint(builder);
} catch (IOException e) {
// Not possible for StringBuilder.
throw new AssertionError(e);
}
return builder.toString();
}

/**
* Print the syntax node in a form useful for debugging.
*
* <p>The output is not precisely specified; use {@link #prettyPrint()} if you need more stable
* and complete information. For instance, this function may omit child statements of compound
* statements, or parentheses around some expressions. It may also abbreviate large list literals.
*/
@Override
public abstract String toString();
public String toString() {
return prettyPrint();
}

@Override
public int hashCode() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import com.google.common.collect.ImmutableList;
import com.google.devtools.build.lib.events.Location;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
Expand Down Expand Up @@ -58,7 +59,7 @@ public enum Kind {
* <p>This avoids having to rely on reflection, or on checking whether {@link #getLValue} is
* null.
*/
public abstract Kind getKind();
public Kind getKind();

/**
* The evaluation of the comprehension is based on recursion. Each clause may
Expand All @@ -72,23 +73,26 @@ public enum Kind {
* @param collector the aggregated results of the comprehension.
* @param step the index of the next clause to evaluate.
*/
abstract void eval(Environment env, OutputCollector collector, int step)
void eval(Environment env, OutputCollector collector, int step)
throws EvalException, InterruptedException;

abstract void validate(ValidationEnvironment env, Location loc) throws EvalException;
void validate(ValidationEnvironment env, Location loc) throws EvalException;

/**
* The LValue defined in Clause, i.e. the loop variables for ForClause and null for
* IfClause. This is needed for SyntaxTreeVisitor.
*/
@Nullable // for the IfClause
public abstract LValue getLValue();
public LValue getLValue();

/**
* The Expression defined in Clause, i.e. the collection for ForClause and the
* condition for IfClause. This is needed for SyntaxTreeVisitor.
*/
public abstract Expression getExpression();
public Expression getExpression();

/** Pretty print to a buffer. */
public void prettyPrint(Appendable buffer) throws IOException;
}

/**
Expand Down Expand Up @@ -141,9 +145,24 @@ public Expression getExpression() {
return list;
}

@Override
public void prettyPrint(Appendable buffer) throws IOException {
buffer.append("for ");
variables.prettyPrint(buffer);
buffer.append(" in ");
list.prettyPrint(buffer);
}

@Override
public String toString() {
return Printer.format("for %s in %r", variables.toString(), list);
StringBuilder builder = new StringBuilder();
try {
prettyPrint(builder);
} catch (IOException e) {
// Not possible for StringBuilder.
throw new AssertionError(e);
}
return builder.toString();
}
}

Expand Down Expand Up @@ -185,9 +204,22 @@ public Expression getExpression() {
return condition;
}

@Override
public void prettyPrint(Appendable buffer) throws IOException {
buffer.append("if ");
condition.prettyPrint(buffer);
}

@Override
public String toString() {
return String.format("if %s", condition);
StringBuilder builder = new StringBuilder();
try {
prettyPrint(builder);
} catch (IOException e) {
// Not possible for StringBuilder.
throw new AssertionError(e);
}
return builder.toString();
}
}

Expand All @@ -213,14 +245,14 @@ public ImmutableList<Expression> getOutputExpressions() {
}

@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(openingBracket()).append(printExpressions());
public void prettyPrint(Appendable buffer) throws IOException {
buffer.append(openingBracket());
printExpressions(buffer);
for (Clause clause : clauses) {
sb.append(' ').append(clause);
buffer.append(' ');
clause.prettyPrint(buffer);
}
sb.append(closingBracket());
return sb.toString();
buffer.append(closingBracket());
}

/** Base class for comprehension builders. */
Expand Down Expand Up @@ -312,10 +344,8 @@ private static void evalStep(Environment env, OutputCollector collector, int ste
}
}

/**
* Returns a {@link String} representation of the output expression(s).
*/
abstract String printExpressions();
/** Pretty-prints the output expression(s). */
protected abstract void printExpressions(Appendable buffer) throws IOException;

abstract OutputCollector createCollector(Environment env);

Expand Down
58 changes: 42 additions & 16 deletions src/main/java/com/google/devtools/build/lib/syntax/Argument.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,8 @@
package com.google.devtools.build.lib.syntax;

import com.google.devtools.build.lib.util.Preconditions;

import java.io.IOException;
import java.util.List;

import javax.annotation.Nullable;

/**
Expand Down Expand Up @@ -54,15 +53,20 @@ private Passed(Expression value) {
public boolean isPositional() {
return false;
}

public boolean isKeyword() {
return false;
}
@Nullable public String getName() { // only for keyword arguments

@Nullable
public String getName() { // only for keyword arguments
return null;
}

public Expression getValue() {
return value;
}

@Override
public void accept(SyntaxTreeVisitor visitor) {
visitor.visit(this);
Expand All @@ -76,12 +80,14 @@ public Positional(Expression value) {
super(value);
}

@Override public boolean isPositional() {
@Override
public boolean isPositional() {
return true;
}

@Override
public String toString() {
return String.valueOf(value);
public void prettyPrint(Appendable buffer) throws IOException {
value.prettyPrint(buffer);
}
}

Expand All @@ -95,15 +101,21 @@ public Keyword(String name, Expression value) {
this.name = name;
}

@Override public String getName() {
@Override
public String getName() {
return name;
}
@Override public boolean isKeyword() {

@Override
public boolean isKeyword() {
return true;
}

@Override
public String toString() {
return name + " = " + value;
public void prettyPrint(Appendable buffer) throws IOException {
buffer.append(name);
buffer.append(" = ");
value.prettyPrint(buffer);
}
}

Expand All @@ -114,12 +126,15 @@ public Star(Expression value) {
super(value);
}

@Override public boolean isStar() {
@Override
public boolean isStar() {
return true;
}

@Override
public String toString() {
return "*" + value;
public void prettyPrint(Appendable buffer) throws IOException {
buffer.append('*');
value.prettyPrint(buffer);
}
}

Expand All @@ -130,12 +145,15 @@ public StarStar(Expression value) {
super(value);
}

@Override public boolean isStarStar() {
@Override
public boolean isStarStar() {
return true;
}

@Override
public String toString() {
return "**" + value;
public void prettyPrint(Appendable buffer) throws IOException {
buffer.append("**");
value.prettyPrint(buffer);
}
}

Expand Down Expand Up @@ -179,4 +197,12 @@ public static void validateFuncallArguments(List<Passed> arguments)
}
}
}

@Override
public final void prettyPrint(Appendable buffer, int indentLevel) throws IOException {
prettyPrint(buffer);
}

@Override
public abstract void prettyPrint(Appendable buffer) throws IOException;
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

package com.google.devtools.build.lib.syntax;

import java.io.IOException;

/**
* Syntax node for an assignment statement.
Expand Down Expand Up @@ -47,8 +48,12 @@ public Expression getExpression() {
}

@Override
public String toString() {
return lvalue + " = " + expression + '\n';
public void prettyPrint(Appendable buffer, int indentLevel) throws IOException {
printIndent(buffer, indentLevel);
lvalue.prettyPrint(buffer, indentLevel);
buffer.append(" = ");
expression.prettyPrint(buffer, indentLevel);
buffer.append('\n');
}

@Override
Expand Down
Loading

0 comments on commit e2ffd5d

Please sign in to comment.