You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
719 lines
17 KiB
719 lines
17 KiB
/* |
|
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. |
|
*/ |
|
// Printing of syntax trees. |
|
|
|
package build |
|
|
|
import ( |
|
"bytes" |
|
"fmt" |
|
"strings" |
|
) |
|
|
|
const nestedIndentation = 2 // Indentation of nested blocks |
|
const listIndentation = 4 // Indentation of multiline expressions |
|
|
|
// Format returns the formatted form of the given BUILD file. |
|
func Format(f *File) []byte { |
|
pr := &printer{} |
|
pr.file(f) |
|
return pr.Bytes() |
|
} |
|
|
|
// FormatString returns the string form of the given expression. |
|
func FormatString(x Expr) string { |
|
pr := &printer{} |
|
switch x := x.(type) { |
|
case *File: |
|
pr.file(x) |
|
default: |
|
pr.expr(x, precLow) |
|
} |
|
return pr.String() |
|
} |
|
|
|
// A printer collects the state during printing of a file or expression. |
|
type printer struct { |
|
bytes.Buffer // output buffer |
|
comment []Comment // pending end-of-line comments |
|
margin int // left margin (indent), a number of spaces |
|
depth int // nesting depth inside ( ) [ ] { } |
|
} |
|
|
|
// printf prints to the buffer. |
|
func (p *printer) printf(format string, args ...interface{}) { |
|
fmt.Fprintf(p, format, args...) |
|
} |
|
|
|
// indent returns the position on the current line, in bytes, 0-indexed. |
|
func (p *printer) indent() int { |
|
b := p.Bytes() |
|
n := 0 |
|
for n < len(b) && b[len(b)-1-n] != '\n' { |
|
n++ |
|
} |
|
return n |
|
} |
|
|
|
// newline ends the current line, flushing end-of-line comments. |
|
// It must only be called when printing a newline is known to be safe: |
|
// when not inside an expression or when p.depth > 0. |
|
// To break a line inside an expression that might not be enclosed |
|
// in brackets of some kind, use breakline instead. |
|
func (p *printer) newline() { |
|
if len(p.comment) > 0 { |
|
p.printf(" ") |
|
for i, com := range p.comment { |
|
if i > 0 { |
|
p.trim() |
|
p.printf("\n%*s", p.margin, "") |
|
} |
|
p.printf("%s", strings.TrimSpace(com.Token)) |
|
} |
|
p.comment = p.comment[:0] |
|
} |
|
|
|
p.trim() |
|
p.printf("\n%*s", p.margin, "") |
|
} |
|
|
|
// breakline breaks the current line, inserting a continuation \ if needed. |
|
// If no continuation \ is needed, breakline flushes end-of-line comments. |
|
func (p *printer) breakline() { |
|
if p.depth == 0 { |
|
// Cannot have both final \ and comments. |
|
p.printf(" \\\n%*s", p.margin, "") |
|
return |
|
} |
|
|
|
// Safe to use newline. |
|
p.newline() |
|
} |
|
|
|
// trim removes trailing spaces from the current line. |
|
func (p *printer) trim() { |
|
// Remove trailing space from line we're about to end. |
|
b := p.Bytes() |
|
n := len(b) |
|
for n > 0 && b[n-1] == ' ' { |
|
n-- |
|
} |
|
p.Truncate(n) |
|
} |
|
|
|
// file formats the given file into the print buffer. |
|
func (p *printer) file(f *File) { |
|
for _, com := range f.Before { |
|
p.printf("%s", strings.TrimSpace(com.Token)) |
|
p.newline() |
|
} |
|
|
|
p.statements(f.Stmt) |
|
|
|
for _, com := range f.After { |
|
p.printf("%s", strings.TrimSpace(com.Token)) |
|
p.newline() |
|
} |
|
|
|
// If the last expression is in an indented code block there can be spaces in the last line. |
|
p.trim() |
|
} |
|
|
|
func (p *printer) statements(stmts []Expr) { |
|
for i, stmt := range stmts { |
|
switch stmt := stmt.(type) { |
|
case *CommentBlock: |
|
// comments already handled |
|
|
|
case *PythonBlock: |
|
for _, com := range stmt.Before { |
|
p.printf("%s", strings.TrimSpace(com.Token)) |
|
p.newline() |
|
} |
|
p.printf("%s", stmt.Token) |
|
p.newline() |
|
|
|
default: |
|
p.expr(stmt, precLow) |
|
|
|
// Print an empty line break after the expression unless it's a code block. |
|
// For a code block, the line break is generated by its last statement. |
|
if !isCodeBlock(stmt) { |
|
p.newline() |
|
} |
|
} |
|
|
|
for _, com := range stmt.Comment().After { |
|
p.printf("%s", strings.TrimSpace(com.Token)) |
|
p.newline() |
|
} |
|
|
|
if i+1 < len(stmts) && !compactStmt(stmt, stmts[i+1], p.margin == 0) { |
|
p.newline() |
|
} |
|
} |
|
} |
|
|
|
// compactStmt reports whether the pair of statements s1, s2 |
|
// should be printed without an intervening blank line. |
|
// We omit the blank line when both are subinclude statements |
|
// and the second one has no leading comments. |
|
func compactStmt(s1, s2 Expr, isTopLevel bool) bool { |
|
if len(s2.Comment().Before) > 0 { |
|
return false |
|
} |
|
|
|
if isTopLevel { |
|
return isCall(s1, "load") && isCall(s2, "load") |
|
} else { |
|
return !(isCodeBlock(s1) || isCodeBlock(s2)) |
|
} |
|
} |
|
|
|
// isCall reports whether x is a call to a function with the given name. |
|
func isCall(x Expr, name string) bool { |
|
c, ok := x.(*CallExpr) |
|
if !ok { |
|
return false |
|
} |
|
nam, ok := c.X.(*LiteralExpr) |
|
if !ok { |
|
return false |
|
} |
|
return nam.Token == name |
|
} |
|
|
|
// isCodeBlock checks if the statement is a code block (def, if, for, etc.) |
|
func isCodeBlock(x Expr) bool { |
|
switch x.(type) { |
|
case *FuncDef: |
|
return true |
|
case *ForLoop: |
|
return true |
|
case *IfElse: |
|
return true |
|
default: |
|
return false |
|
} |
|
} |
|
|
|
// Expression formatting. |
|
|
|
// The expression formatter must introduce parentheses to force the |
|
// meaning described by the parse tree. We preserve parentheses in the |
|
// input, so extra parentheses are only needed if we have edited the tree. |
|
// |
|
// For example consider these expressions: |
|
// (1) "x" "y" % foo |
|
// (2) "x" + "y" % foo |
|
// (3) "x" + ("y" % foo) |
|
// (4) ("x" + "y") % foo |
|
// When we parse (1), we represent the concatenation as an addition. |
|
// However, if we print the addition back out without additional parens, |
|
// as in (2), it has the same meaning as (3), which is not the original |
|
// meaning. To preserve the original meaning we must add parens as in (4). |
|
// |
|
// To allow arbitrary rewrites to be formatted properly, we track full |
|
// operator precedence while printing instead of just handling this one |
|
// case of string concatenation. |
|
// |
|
// The precedences are assigned values low to high. A larger number |
|
// binds tighter than a smaller number. All binary operators bind |
|
// left-to-right. |
|
const ( |
|
precLow = iota |
|
precAssign |
|
precComma |
|
precColon |
|
precIn |
|
precOr |
|
precAnd |
|
precCmp |
|
precAdd |
|
precMultiply |
|
precSuffix |
|
precUnary |
|
precConcat |
|
) |
|
|
|
// opPrec gives the precedence for operators found in a BinaryExpr. |
|
var opPrec = map[string]int{ |
|
"=": precAssign, |
|
"+=": precAssign, |
|
"-=": precAssign, |
|
"*=": precAssign, |
|
"/=": precAssign, |
|
"//=": precAssign, |
|
"%=": precAssign, |
|
"or": precOr, |
|
"and": precAnd, |
|
"<": precCmp, |
|
">": precCmp, |
|
"==": precCmp, |
|
"!=": precCmp, |
|
"<=": precCmp, |
|
">=": precCmp, |
|
"+": precAdd, |
|
"-": precAdd, |
|
"*": precMultiply, |
|
"/": precMultiply, |
|
"//": precMultiply, |
|
"%": precMultiply, |
|
} |
|
|
|
// expr prints the expression v to the print buffer. |
|
// The value outerPrec gives the precedence of the operator |
|
// outside expr. If that operator binds tighter than v's operator, |
|
// expr must introduce parentheses to preserve the meaning |
|
// of the parse tree (see above). |
|
func (p *printer) expr(v Expr, outerPrec int) { |
|
// Emit line-comments preceding this expression. |
|
// If we are in the middle of an expression but not inside ( ) [ ] { } |
|
// then we cannot just break the line: we'd have to end it with a \. |
|
// However, even then we can't emit line comments since that would |
|
// end the expression. This is only a concern if we have rewritten |
|
// the parse tree. If comments were okay before this expression in |
|
// the original input they're still okay now, in the absense of rewrites. |
|
// |
|
// TODO(bazel-team): Check whether it is valid to emit comments right now, |
|
// and if not, insert them earlier in the output instead, at the most |
|
// recent \n not following a \ line. |
|
if before := v.Comment().Before; len(before) > 0 { |
|
// Want to print a line comment. |
|
// Line comments must be at the current margin. |
|
p.trim() |
|
if p.indent() > 0 { |
|
// There's other text on the line. Start a new line. |
|
p.printf("\n") |
|
} |
|
// Re-indent to margin. |
|
p.printf("%*s", p.margin, "") |
|
for _, com := range before { |
|
p.printf("%s", strings.TrimSpace(com.Token)) |
|
p.newline() |
|
} |
|
} |
|
|
|
// Do we introduce parentheses? |
|
// The result depends on the kind of expression. |
|
// Each expression type that might need parentheses |
|
// calls addParen with its own precedence. |
|
// If parentheses are necessary, addParen prints the |
|
// opening parenthesis and sets parenthesized so that |
|
// the code after the switch can print the closing one. |
|
parenthesized := false |
|
addParen := func(prec int) { |
|
if prec < outerPrec { |
|
p.printf("(") |
|
p.depth++ |
|
parenthesized = true |
|
} |
|
} |
|
|
|
switch v := v.(type) { |
|
default: |
|
panic(fmt.Errorf("printer: unexpected type %T", v)) |
|
|
|
case *LiteralExpr: |
|
p.printf("%s", v.Token) |
|
|
|
case *StringExpr: |
|
// If the Token is a correct quoting of Value, use it. |
|
// This preserves the specific escaping choices that |
|
// BUILD authors have made, and it also works around |
|
// b/7272572. |
|
if strings.HasPrefix(v.Token, `"`) { |
|
s, triple, err := unquote(v.Token) |
|
if s == v.Value && triple == v.TripleQuote && err == nil { |
|
p.printf("%s", v.Token) |
|
break |
|
} |
|
} |
|
|
|
p.printf("%s", quote(v.Value, v.TripleQuote)) |
|
|
|
case *DotExpr: |
|
addParen(precSuffix) |
|
p.expr(v.X, precSuffix) |
|
p.printf(".%s", v.Name) |
|
|
|
case *IndexExpr: |
|
addParen(precSuffix) |
|
p.expr(v.X, precSuffix) |
|
p.printf("[") |
|
p.expr(v.Y, precLow) |
|
p.printf("]") |
|
|
|
case *KeyValueExpr: |
|
p.expr(v.Key, precLow) |
|
p.printf(": ") |
|
p.expr(v.Value, precLow) |
|
|
|
case *SliceExpr: |
|
addParen(precSuffix) |
|
p.expr(v.X, precSuffix) |
|
p.printf("[") |
|
if v.From != nil { |
|
p.expr(v.From, precLow) |
|
} |
|
p.printf(":") |
|
if v.To != nil { |
|
p.expr(v.To, precLow) |
|
} |
|
if v.SecondColon.Byte != 0 { |
|
p.printf(":") |
|
if v.Step != nil { |
|
p.expr(v.Step, precLow) |
|
} |
|
} |
|
p.printf("]") |
|
|
|
case *UnaryExpr: |
|
addParen(precUnary) |
|
if v.Op == "not" { |
|
p.printf("not ") // Requires a space after it. |
|
} else { |
|
p.printf("%s", v.Op) |
|
} |
|
p.expr(v.X, precUnary) |
|
|
|
case *LambdaExpr: |
|
addParen(precColon) |
|
p.printf("lambda ") |
|
for i, name := range v.Var { |
|
if i > 0 { |
|
p.printf(", ") |
|
} |
|
p.expr(name, precLow) |
|
} |
|
p.printf(": ") |
|
p.expr(v.Expr, precColon) |
|
|
|
case *BinaryExpr: |
|
// Precedence: use the precedence of the operator. |
|
// Since all binary expressions format left-to-right, |
|
// it is okay for the left side to reuse the same operator |
|
// without parentheses, so we use prec for v.X. |
|
// For the same reason, the right side cannot reuse the same |
|
// operator, or else a parse tree for a + (b + c), where the ( ) are |
|
// not present in the source, will format as a + b + c, which |
|
// means (a + b) + c. Treat the right expression as appearing |
|
// in a context one precedence level higher: use prec+1 for v.Y. |
|
// |
|
// Line breaks: if we are to break the line immediately after |
|
// the operator, introduce a margin at the current column, |
|
// so that the second operand lines up with the first one and |
|
// also so that neither operand can use space to the left. |
|
// If the operator is an =, indent the right side another 4 spaces. |
|
prec := opPrec[v.Op] |
|
addParen(prec) |
|
m := p.margin |
|
if v.LineBreak { |
|
p.margin = p.indent() |
|
if v.Op == "=" { |
|
p.margin += listIndentation |
|
} |
|
} |
|
|
|
p.expr(v.X, prec) |
|
p.printf(" %s", v.Op) |
|
if v.LineBreak { |
|
p.breakline() |
|
} else { |
|
p.printf(" ") |
|
} |
|
p.expr(v.Y, prec+1) |
|
p.margin = m |
|
|
|
case *ParenExpr: |
|
p.seq("()", []Expr{v.X}, &v.End, modeParen, false, v.ForceMultiLine) |
|
|
|
case *CallExpr: |
|
addParen(precSuffix) |
|
p.expr(v.X, precSuffix) |
|
p.seq("()", v.List, &v.End, modeCall, v.ForceCompact, v.ForceMultiLine) |
|
|
|
case *ListExpr: |
|
p.seq("[]", v.List, &v.End, modeList, false, v.ForceMultiLine) |
|
|
|
case *SetExpr: |
|
p.seq("{}", v.List, &v.End, modeList, false, v.ForceMultiLine) |
|
|
|
case *TupleExpr: |
|
p.seq("()", v.List, &v.End, modeTuple, v.ForceCompact, v.ForceMultiLine) |
|
|
|
case *DictExpr: |
|
var list []Expr |
|
for _, x := range v.List { |
|
list = append(list, x) |
|
} |
|
p.seq("{}", list, &v.End, modeDict, false, v.ForceMultiLine) |
|
|
|
case *ListForExpr: |
|
p.listFor(v) |
|
|
|
case *ConditionalExpr: |
|
addParen(precSuffix) |
|
p.expr(v.Then, precSuffix) |
|
p.printf(" if ") |
|
p.expr(v.Test, precSuffix) |
|
p.printf(" else ") |
|
p.expr(v.Else, precSuffix) |
|
|
|
case *ReturnExpr: |
|
p.printf("return") |
|
if v.X != nil { |
|
p.printf(" ") |
|
p.expr(v.X, precSuffix) |
|
} |
|
|
|
case *FuncDef: |
|
p.printf("def ") |
|
p.printf(v.Name) |
|
p.seq("()", v.Args, &v.End, modeCall, v.ForceCompact, v.ForceMultiLine) |
|
p.printf(":") |
|
p.margin += nestedIndentation |
|
p.newline() |
|
p.statements(v.Body.Statements) |
|
p.margin -= nestedIndentation |
|
|
|
case *ForLoop: |
|
p.printf("for ") |
|
for i, loopVar := range v.LoopVars { |
|
if i > 0 { |
|
p.printf(", ") |
|
} |
|
p.expr(loopVar, precLow) |
|
} |
|
p.printf(" in ") |
|
p.expr(v.Iterable, precLow) |
|
p.printf(":") |
|
p.margin += nestedIndentation |
|
p.newline() |
|
p.statements(v.Body.Statements) |
|
p.margin -= nestedIndentation |
|
|
|
case *IfElse: |
|
for i, block := range v.Conditions { |
|
if i == 0 { |
|
p.printf("if ") |
|
} else if block.If == nil { |
|
p.newline() |
|
p.printf("else") |
|
} else { |
|
p.newline() |
|
p.printf("elif ") |
|
} |
|
|
|
if block.If != nil { |
|
p.expr(block.If, precLow) |
|
} |
|
p.printf(":") |
|
p.margin += nestedIndentation |
|
p.newline() |
|
p.statements(block.Then.Statements) |
|
p.margin -= nestedIndentation |
|
} |
|
} |
|
|
|
// Add closing parenthesis if needed. |
|
if parenthesized { |
|
p.depth-- |
|
p.printf(")") |
|
} |
|
|
|
// Queue end-of-line comments for printing when we |
|
// reach the end of the line. |
|
p.comment = append(p.comment, v.Comment().Suffix...) |
|
} |
|
|
|
// A seqMode describes a formatting mode for a sequence of values, |
|
// like a list or call arguments. |
|
type seqMode int |
|
|
|
const ( |
|
_ seqMode = iota |
|
|
|
modeCall // f(x) |
|
modeList // [x] |
|
modeTuple // (x,) |
|
modeParen // (x) |
|
modeDict // {x:y} |
|
modeSeq // x, y |
|
) |
|
|
|
// seq formats a list of values inside a given bracket pair (brack = "()", "[]", "{}"). |
|
// The end node holds any trailing comments to be printed just before the |
|
// closing bracket. |
|
// The mode parameter specifies the sequence mode (see above). |
|
// If multiLine is true, seq avoids the compact form even |
|
// for 0- and 1-element sequences. |
|
func (p *printer) seq(brack string, list []Expr, end *End, mode seqMode, forceCompact, forceMultiLine bool) { |
|
p.printf("%s", brack[:1]) |
|
p.depth++ |
|
|
|
// If there are line comments, force multiline |
|
// so we can print the comments before the closing bracket. |
|
for _, x := range list { |
|
if len(x.Comment().Before) > 0 { |
|
forceMultiLine = true |
|
} |
|
} |
|
if len(end.Before) > 0 { |
|
forceMultiLine = true |
|
} |
|
|
|
// Resolve possibly ambiguous call arguments explicitly |
|
// instead of depending on implicit resolution in logic below. |
|
if forceMultiLine { |
|
forceCompact = false |
|
} |
|
|
|
switch { |
|
case len(list) == 0 && !forceMultiLine: |
|
// Compact form: print nothing. |
|
|
|
case len(list) == 1 && !forceMultiLine: |
|
// Compact form. |
|
p.expr(list[0], precLow) |
|
// Tuple must end with comma, to mark it as a tuple. |
|
if mode == modeTuple { |
|
p.printf(",") |
|
} |
|
|
|
case forceCompact: |
|
// Compact form but multiple elements. |
|
for i, x := range list { |
|
if i > 0 { |
|
p.printf(", ") |
|
} |
|
p.expr(x, precLow) |
|
} |
|
|
|
default: |
|
// Multi-line form. |
|
p.margin += listIndentation |
|
for i, x := range list { |
|
// If we are about to break the line before the first |
|
// element and there are trailing end-of-line comments |
|
// waiting to be printed, delay them and print them as |
|
// whole-line comments preceding that element. |
|
// Do this by printing a newline ourselves and positioning |
|
// so that the end-of-line comment, with the two spaces added, |
|
// will line up with the current margin. |
|
if i == 0 && len(p.comment) > 0 { |
|
p.printf("\n%*s", p.margin-2, "") |
|
} |
|
|
|
p.newline() |
|
p.expr(x, precLow) |
|
if mode != modeParen || i+1 < len(list) { |
|
p.printf(",") |
|
} |
|
} |
|
// Final comments. |
|
for _, com := range end.Before { |
|
p.newline() |
|
p.printf("%s", strings.TrimSpace(com.Token)) |
|
} |
|
p.margin -= listIndentation |
|
p.newline() |
|
} |
|
p.depth-- |
|
p.printf("%s", brack[1:]) |
|
} |
|
|
|
// listFor formats a ListForExpr (list comprehension). |
|
// The single-line form is: |
|
// [x for y in z if c] |
|
// |
|
// and the multi-line form is: |
|
// [ |
|
// x |
|
// for y in z |
|
// if c |
|
// ] |
|
// |
|
func (p *printer) listFor(v *ListForExpr) { |
|
multiLine := v.ForceMultiLine || len(v.End.Before) > 0 |
|
|
|
// space breaks the line in multiline mode |
|
// or else prints a space. |
|
space := func() { |
|
if multiLine { |
|
p.breakline() |
|
} else { |
|
p.printf(" ") |
|
} |
|
} |
|
|
|
if v.Brack != "" { |
|
p.depth++ |
|
p.printf("%s", v.Brack[:1]) |
|
} |
|
|
|
if multiLine { |
|
if v.Brack != "" { |
|
p.margin += listIndentation |
|
} |
|
p.newline() |
|
} |
|
|
|
p.expr(v.X, precLow) |
|
|
|
for _, c := range v.For { |
|
space() |
|
p.printf("for ") |
|
for i, name := range c.For.Var { |
|
if i > 0 { |
|
p.printf(", ") |
|
} |
|
p.expr(name, precLow) |
|
} |
|
p.printf(" in ") |
|
p.expr(c.For.Expr, precLow) |
|
p.comment = append(p.comment, c.For.Comment().Suffix...) |
|
|
|
for _, i := range c.Ifs { |
|
space() |
|
p.printf("if ") |
|
p.expr(i.Cond, precLow) |
|
p.comment = append(p.comment, i.Comment().Suffix...) |
|
} |
|
p.comment = append(p.comment, c.Comment().Suffix...) |
|
|
|
} |
|
|
|
if multiLine { |
|
for _, com := range v.End.Before { |
|
p.newline() |
|
p.printf("%s", strings.TrimSpace(com.Token)) |
|
} |
|
if v.Brack != "" { |
|
p.margin -= listIndentation |
|
} |
|
p.newline() |
|
} |
|
|
|
if v.Brack != "" { |
|
p.printf("%s", v.Brack[1:]) |
|
p.depth-- |
|
} |
|
} |
|
|
|
func (p *printer) isTopLevel() bool { |
|
return p.margin == 0 |
|
}
|
|
|