720 lines
17 KiB
Go
720 lines
17 KiB
Go
/*
|
|
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
|
|
}
|