| /* |
| Copyright 2020 Google LLC |
| |
| 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 |
| |
| https://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. |
| */ |
| |
| // BUILD file parser. |
| |
| // This is a yacc grammar. Its lexer is in lex.go. |
| // |
| // For a good introduction to writing yacc grammars, see |
| // Kernighan and Pike's book The Unix Programming Environment. |
| // |
| // The definitive yacc manual is |
| // Stephen C. Johnson and Ravi Sethi, "Yacc: A Parser Generator", |
| // online at http://plan9.bell-labs.com/sys/doc/yacc.pdf. |
| |
| %{ |
| package build |
| %} |
| |
| // The generated parser puts these fields in a struct named yySymType. |
| // (The name %union is historical, but it is inaccurate for Go.) |
| %union { |
| // input tokens |
| tok string // raw input syntax |
| str string // decoding of quoted string |
| pos Position // position of token |
| triple bool // was string triple quoted? |
| |
| // partial syntax trees |
| expr Expr |
| exprs []Expr |
| kv *KeyValueExpr |
| kvs []*KeyValueExpr |
| string *StringExpr |
| ifstmt *IfStmt |
| loadarg *struct{from Ident; to Ident} |
| loadargs []*struct{from Ident; to Ident} |
| def_header *DefStmt // partially filled in def statement, without the body |
| |
| // supporting information |
| comma Position // position of trailing comma in list, if present |
| lastStmt Expr // most recent rule, to attach line comments to |
| } |
| |
| // These declarations set the type for a $ reference ($$, $1, $2, ...) |
| // based on the kind of symbol it refers to. Other fields can be referred |
| // to explicitly, as in $<tok>1. |
| // |
| // %token is for input tokens generated by the lexer. |
| // %type is for higher-level grammar rules defined here. |
| // |
| // It is possible to put multiple tokens per line, but it is easier to |
| // keep ordered using a sparser one-per-line list. |
| |
| %token <pos> '%' |
| %token <pos> '(' |
| %token <pos> ')' |
| %token <pos> '*' |
| %token <pos> '+' |
| %token <pos> ',' |
| %token <pos> '-' |
| %token <pos> '.' |
| %token <pos> '/' |
| %token <pos> ':' |
| %token <pos> '<' |
| %token <pos> '=' |
| %token <pos> '>' |
| %token <pos> '[' |
| %token <pos> ']' |
| %token <pos> '{' |
| %token <pos> '}' |
| %token <pos> '|' |
| %token <pos> '&' |
| %token <pos> '^' |
| %token <pos> '~' |
| |
| // By convention, yacc token names are all caps. |
| // However, we do not want to export them from the Go package |
| // we are creating, so prefix them all with underscores. |
| |
| %token <pos> _AUGM // augmented assignment |
| %token <pos> _AND // keyword and |
| %token <pos> _COMMENT // top-level # comment |
| %token <pos> _EOF // end of file |
| %token <pos> _EQ // operator == |
| %token <pos> _FOR // keyword for |
| %token <pos> _GE // operator >= |
| %token <pos> _IDENT // non-keyword identifier |
| %token <pos> _INT // integer number |
| %token <pos> _IF // keyword if |
| %token <pos> _ELSE // keyword else |
| %token <pos> _ELIF // keyword elif |
| %token <pos> _IN // keyword in |
| %token <pos> _IS // keyword is |
| %token <pos> _LAMBDA // keyword lambda |
| %token <pos> _LOAD // keyword load |
| %token <pos> _LE // operator <= |
| %token <pos> _NE // operator != |
| %token <pos> _STAR_STAR // operator ** |
| %token <pos> _INT_DIV // operator // |
| %token <pos> _BIT_LSH // bitwise operator << |
| %token <pos> _BIT_RSH // bitwise operator >> |
| %token <pos> _ARROW // functions type annotation -> |
| %token <pos> _NOT // keyword not |
| %token <pos> _OR // keyword or |
| %token <pos> _STRING // quoted string |
| %token <pos> _DEF // keyword def |
| %token <pos> _RETURN // keyword return |
| %token <pos> _PASS // keyword pass |
| %token <pos> _BREAK // keyword break |
| %token <pos> _CONTINUE // keyword continue |
| %token <pos> _INDENT // indentation |
| %token <pos> _UNINDENT // unindentation |
| |
| %type <pos> comma_opt |
| %type <pos> commas |
| %type <pos> commas_opt |
| %type <expr> argument |
| %type <exprs> arguments |
| %type <exprs> arguments_opt |
| %type <expr> parameter |
| %type <exprs> parameters |
| %type <exprs> parameters_opt |
| %type <expr> parameter_type |
| %type <exprs> parameters_type |
| %type <exprs> parameters_type_opt |
| %type <expr> test |
| %type <expr> test_opt |
| %type <exprs> tests_opt |
| %type <expr> primary_expr |
| %type <expr> expr |
| %type <expr> expr_opt |
| %type <exprs> tests |
| %type <expr> loop_vars |
| %type <expr> for_clause |
| %type <exprs> for_clause_with_if_clauses_opt |
| %type <exprs> for_clauses_with_if_clauses_opt |
| %type <expr> ident |
| %type <expr> number |
| %type <exprs> stmts |
| %type <exprs> stmt // a simple_stmt or a for/if/def block |
| %type <expr> block_stmt // a single for/if/def statement |
| %type <ifstmt> if_else_block // a complete if-elif-else block |
| %type <ifstmt> if_chain // an elif-elif-else chain |
| %type <pos> elif // `elif` or `else if` token(s) |
| %type <exprs> simple_stmt // One or many small_stmts on one line, e.g. 'a = f(x); return str(a)' |
| %type <expr> small_stmt // A single statement, e.g. 'a = f(x)' |
| %type <exprs> small_stmts_continuation // A sequence of `';' small_stmt` |
| %type <kv> keyvalue |
| %type <kvs> keyvalues |
| %type <kvs> keyvalues_no_comma |
| %type <string> string |
| %type <exprs> suite |
| %type <exprs> comments |
| %type <loadarg> load_argument |
| %type <loadargs> load_arguments |
| %type <def_header> def_header |
| %type <def_header> def_header_type_opt |
| |
| // Operator precedence. |
| // Operators listed lower in the table bind tighter. |
| |
| // We tag rules with this fake, low precedence to indicate |
| // that when the rule is involved in a shift/reduce |
| // conflict, we prefer that the parser shift (try for a longer parse). |
| // Shifting is the default resolution anyway, but stating it explicitly |
| // silences yacc's warning for that specific case. |
| %left ShiftInstead |
| |
| %left '\n' |
| %left _ASSERT |
| // '=' and augmented assignments have the lowest precedence |
| // e.g. "x = a if c > 0 else 'bar'" |
| // followed by |
| // 'if' and 'else' which have lower precedence than all other operators. |
| // e.g. "a, b if c > 0 else 'foo'" is either a tuple of (a,b) or 'foo' |
| // and not a tuple of "(a, (b if ... ))" |
| %left '=' _AUGM |
| %left _IF _ELSE _ELIF |
| %left ',' |
| %left ':' |
| %left _IS |
| %left _OR |
| %left _AND |
| %left '<' '>' _EQ _NE _LE _GE _NOT _IN |
| %left '|' |
| %left '^' |
| %left '&' |
| %left _BIT_LSH _BIT_RSH |
| %left '+' '-' |
| %left '*' '/' '%' _INT_DIV |
| %left '.' '[' '(' |
| %left _STRING |
| %right _UNARY |
| |
| %% |
| |
| // Grammar rules. |
| // |
| // A note on names: if foo is a rule, then foos is a sequence of foos |
| // (with interleaved commas or other syntax as appropriate) |
| // and foo_opt is an optional foo. |
| |
| file: |
| stmts _EOF |
| { |
| yylex.(*input).file = &File{Stmt: $1} |
| return 0 |
| } |
| |
| suite: |
| '\n' comments _INDENT stmts _UNINDENT |
| { |
| statements := $4 |
| if $2 != nil { |
| // $2 can only contain *CommentBlock objects, each of them contains a non-empty After slice |
| cb := $2[len($2)-1].(*CommentBlock) |
| // $4 can't be empty and can't start with a comment |
| stmt := $4[0] |
| start, _ := stmt.Span() |
| if start.Line - cb.After[len(cb.After)-1].Start.Line == 1 { |
| // The first statement of $4 starts on the next line after the last comment of $2. |
| // Attach the last comment to the first statement |
| stmt.Comment().Before = cb.After |
| $2 = $2[:len($2)-1] |
| } |
| statements = append($2, $4...) |
| } |
| $$ = statements |
| $<lastStmt>$ = $<lastStmt>4 |
| } |
| | simple_stmt linebreaks_opt %prec ShiftInstead |
| { |
| $$ = $1 |
| } |
| |
| linebreaks_opt: |
| | linebreaks_opt '\n' |
| |
| comments: |
| { |
| $$ = nil |
| $<lastStmt>$ = nil |
| } |
| | comments _COMMENT '\n' |
| { |
| $$ = $1 |
| $<lastStmt>$ = $<lastStmt>1 |
| if $<lastStmt>$ == nil { |
| cb := &CommentBlock{Start: $2} |
| $$ = append($$, cb) |
| $<lastStmt>$ = cb |
| } |
| com := $<lastStmt>$.Comment() |
| com.After = append(com.After, Comment{Start: $2, Token: $<tok>2}) |
| } |
| | comments '\n' |
| { |
| $$ = $1 |
| $<lastStmt>$ = nil |
| } |
| |
| stmts: |
| { |
| $$ = nil |
| $<lastStmt>$ = nil |
| } |
| | stmts stmt |
| { |
| // If this statement follows a comment block, |
| // attach the comments to the statement. |
| if cb, ok := $<lastStmt>1.(*CommentBlock); ok { |
| $$ = append($1[:len($1)-1], $2...) |
| $2[0].Comment().Before = cb.After |
| $<lastStmt>$ = $<lastStmt>2 |
| break |
| } |
| |
| // Otherwise add to list. |
| $$ = append($1, $2...) |
| $<lastStmt>$ = $<lastStmt>2 |
| |
| // Consider this input: |
| // |
| // foo() |
| // # bar |
| // baz() |
| // |
| // If we've just parsed baz(), the # bar is attached to |
| // foo() as an After comment. Make it a Before comment |
| // for baz() instead. |
| if x := $<lastStmt>1; x != nil { |
| com := x.Comment() |
| // stmt is never empty |
| $2[0].Comment().Before = com.After |
| com.After = nil |
| } |
| } |
| | stmts '\n' |
| { |
| // Blank line; sever last rule from future comments. |
| $$ = $1 |
| $<lastStmt>$ = nil |
| } |
| | stmts _COMMENT '\n' |
| { |
| $$ = $1 |
| $<lastStmt>$ = $<lastStmt>1 |
| if $<lastStmt>$ == nil { |
| cb := &CommentBlock{Start: $2} |
| $$ = append($$, cb) |
| $<lastStmt>$ = cb |
| } |
| com := $<lastStmt>$.Comment() |
| com.After = append(com.After, Comment{Start: $2, Token: $<tok>2}) |
| } |
| |
| stmt: |
| simple_stmt |
| { |
| $$ = $1 |
| $<lastStmt>$ = $1[len($1)-1] |
| } |
| | block_stmt |
| { |
| $$ = []Expr{$1} |
| $<lastStmt>$ = $1 |
| if cbs := extractTrailingComments($1); len(cbs) > 0 { |
| $$ = append($$, cbs...) |
| $<lastStmt>$ = cbs[len(cbs)-1] |
| if $<lastStmt>1 == nil { |
| $<lastStmt>$ = nil |
| } |
| } |
| } |
| |
| def_header: |
| _DEF _IDENT '(' parameters_type_opt ')' |
| { |
| $$ = &DefStmt{ |
| Function: Function{ |
| StartPos: $1, |
| Params: $4, |
| }, |
| Name: $<tok>2, |
| ForceCompact: forceCompact($3, $4, $5), |
| ForceMultiLine: forceMultiLine($3, $4, $5), |
| } |
| } |
| |
| def_header_type_opt: |
| def_header |
| | def_header _ARROW test |
| { |
| $1.Type = $3 |
| $$ = $1 |
| } |
| |
| block_stmt: |
| def_header_type_opt ':' suite |
| { |
| $1.Function.Body = $3 |
| $1.ColonPos = $2 |
| $$ = $1 |
| $<lastStmt>$ = $<lastStmt>3 |
| } |
| | _FOR loop_vars _IN expr ':' suite |
| { |
| $$ = &ForStmt{ |
| For: $1, |
| Vars: $2, |
| X: $4, |
| Body: $6, |
| } |
| $<lastStmt>$ = $<lastStmt>6 |
| } |
| | if_else_block |
| { |
| $$ = $1 |
| $<lastStmt>$ = $<lastStmt>1 |
| } |
| |
| // One or several if-elif-elif statements |
| if_chain: |
| _IF expr ':' suite |
| { |
| $$ = &IfStmt{ |
| If: $1, |
| Cond: $2, |
| True: $4, |
| } |
| $<lastStmt>$ = $<lastStmt>4 |
| } |
| | if_chain elif expr ':' suite |
| { |
| $$ = $1 |
| inner := $1 |
| for len(inner.False) == 1 { |
| inner = inner.False[0].(*IfStmt) |
| } |
| inner.ElsePos = End{Pos: $2} |
| inner.False = []Expr{ |
| &IfStmt{ |
| If: $2, |
| Cond: $3, |
| True: $5, |
| }, |
| } |
| $<lastStmt>$ = $<lastStmt>5 |
| } |
| |
| // A complete if-elif-elif-else chain |
| if_else_block: |
| if_chain |
| | if_chain _ELSE ':' suite |
| { |
| $$ = $1 |
| inner := $1 |
| for len(inner.False) == 1 { |
| inner = inner.False[0].(*IfStmt) |
| } |
| inner.ElsePos = End{Pos: $2} |
| inner.False = $4 |
| $<lastStmt>$ = $<lastStmt>4 |
| } |
| |
| elif: |
| _ELSE _IF |
| | _ELIF |
| |
| simple_stmt: |
| small_stmt small_stmts_continuation semi_opt '\n' |
| { |
| $$ = append([]Expr{$1}, $2...) |
| $<lastStmt>$ = $$[len($$)-1] |
| } |
| |
| small_stmts_continuation: |
| { |
| $$ = []Expr{} |
| } |
| | small_stmts_continuation ';' small_stmt |
| { |
| $$ = append($1, $3) |
| } |
| |
| small_stmt: |
| expr %prec ShiftInstead |
| | _RETURN expr |
| { |
| $$ = &ReturnStmt{ |
| Return: $1, |
| Result: $2, |
| } |
| } |
| | _RETURN |
| { |
| $$ = &ReturnStmt{ |
| Return: $1, |
| } |
| } |
| | expr '=' expr { $$ = binary($1, $2, $<tok>2, $3) } |
| | expr _AUGM expr { $$ = binary($1, $2, $<tok>2, $3) } |
| | _PASS |
| { |
| $$ = &BranchStmt{ |
| Token: $<tok>1, |
| TokenPos: $1, |
| } |
| } |
| | _BREAK |
| { |
| $$ = &BranchStmt{ |
| Token: $<tok>1, |
| TokenPos: $1, |
| } |
| } |
| | _CONTINUE |
| { |
| $$ = &BranchStmt{ |
| Token: $<tok>1, |
| TokenPos: $1, |
| } |
| } |
| |
| semi_opt: |
| | ';' |
| |
| primary_expr: |
| ident |
| | number |
| | string |
| { |
| $$ = $1 |
| } |
| | primary_expr '.' _IDENT |
| { |
| $$ = &DotExpr{ |
| X: $1, |
| Dot: $2, |
| NamePos: $3, |
| Name: $<tok>3, |
| } |
| } |
| | _LOAD '(' commas_opt string commas load_arguments commas_opt ')' |
| { |
| load := &LoadStmt{ |
| Load: $1, |
| Module: $4, |
| Rparen: End{Pos: $8}, |
| ForceCompact: $2.Line == $8.Line, |
| } |
| for _, arg := range $6 { |
| load.From = append(load.From, &arg.from) |
| load.To = append(load.To, &arg.to) |
| } |
| $$ = load |
| } |
| | primary_expr '(' arguments_opt ')' |
| { |
| $$ = &CallExpr{ |
| X: $1, |
| ListStart: $2, |
| List: $3, |
| End: End{Pos: $4}, |
| ForceCompact: forceCompact($2, $3, $4), |
| ForceMultiLine: forceMultiLine($2, $3, $4), |
| } |
| } |
| | primary_expr '[' expr ']' |
| { |
| $$ = &IndexExpr{ |
| X: $1, |
| IndexStart: $2, |
| Y: $3, |
| End: $4, |
| } |
| } |
| | primary_expr '[' expr_opt ':' test_opt ']' |
| { |
| $$ = &SliceExpr{ |
| X: $1, |
| SliceStart: $2, |
| From: $3, |
| FirstColon: $4, |
| To: $5, |
| End: $6, |
| } |
| } |
| | primary_expr '[' expr_opt ':' test_opt ':' test_opt ']' |
| { |
| $$ = &SliceExpr{ |
| X: $1, |
| SliceStart: $2, |
| From: $3, |
| FirstColon: $4, |
| To: $5, |
| SecondColon: $6, |
| Step: $7, |
| End: $8, |
| } |
| } |
| | '[' tests_opt ']' |
| { |
| $$ = &ListExpr{ |
| Start: $1, |
| List: $2, |
| End: End{Pos: $3}, |
| ForceMultiLine: forceMultiLine($1, $2, $3), |
| } |
| } |
| | '[' test for_clauses_with_if_clauses_opt ']' |
| { |
| $$ = &Comprehension{ |
| Curly: false, |
| Lbrack: $1, |
| Body: $2, |
| Clauses: $3, |
| End: End{Pos: $4}, |
| ForceMultiLine: forceMultiLineComprehension($1, $2, $3, $4), |
| } |
| } |
| | '{' keyvalue for_clauses_with_if_clauses_opt '}' |
| { |
| $$ = &Comprehension{ |
| Curly: true, |
| Lbrack: $1, |
| Body: $2, |
| Clauses: $3, |
| End: End{Pos: $4}, |
| ForceMultiLine: forceMultiLineComprehension($1, $2, $3, $4), |
| } |
| } |
| | '{' keyvalues '}' |
| { |
| exprValues := make([]Expr, 0, len($2)) |
| for _, kv := range $2 { |
| exprValues = append(exprValues, Expr(kv)) |
| } |
| $$ = &DictExpr{ |
| Start: $1, |
| List: $2, |
| End: End{Pos: $3}, |
| ForceMultiLine: forceMultiLine($1, exprValues, $3), |
| } |
| } |
| | '{' tests comma_opt '}' |
| { |
| $$ = &SetExpr{ |
| Start: $1, |
| List: $2, |
| End: End{Pos: $4}, |
| ForceMultiLine: forceMultiLine($1, $2, $4), |
| } |
| } |
| | '(' tests_opt ')' |
| { |
| if len($2) == 1 && $<comma>2.Line == 0 { |
| // Just a parenthesized expression, not a tuple. |
| $$ = &ParenExpr{ |
| Start: $1, |
| X: $2[0], |
| End: End{Pos: $3}, |
| ForceMultiLine: forceMultiLine($1, $2, $3), |
| } |
| } else { |
| $$ = &TupleExpr{ |
| Start: $1, |
| List: $2, |
| End: End{Pos: $3}, |
| ForceCompact: forceCompact($1, $2, $3), |
| ForceMultiLine: forceMultiLine($1, $2, $3), |
| } |
| } |
| } |
| |
| arguments_opt: |
| { |
| $$ = nil |
| } |
| | arguments commas_opt |
| { |
| $$ = $1 |
| } |
| |
| arguments: |
| commas_opt argument |
| { |
| $$ = []Expr{$2} |
| } |
| | arguments commas argument |
| { |
| $$ = append($1, $3) |
| } |
| |
| argument: |
| test |
| | ident '=' test |
| { |
| $$ = binary($1, $2, $<tok>2, $3) |
| } |
| | '*' test |
| { |
| $$ = unary($1, $<tok>1, $2) |
| } |
| | _STAR_STAR test |
| { |
| $$ = unary($1, $<tok>1, $2) |
| } |
| |
| load_arguments: |
| load_argument { |
| $$ = []*struct{from Ident; to Ident}{$1} |
| } |
| | load_arguments ',' load_argument |
| { |
| $1 = append($1, $3) |
| $$ = $1 |
| } |
| |
| load_argument: |
| string { |
| start := $1.Start.add("'") |
| if $1.TripleQuote { |
| start = start.add("''") |
| } |
| $$ = &struct{from Ident; to Ident}{ |
| from: Ident{ |
| Name: $1.Value, |
| NamePos: start, |
| }, |
| to: Ident{ |
| Name: $1.Value, |
| NamePos: start, |
| }, |
| } |
| } |
| | ident '=' string |
| { |
| start := $3.Start.add("'") |
| if $3.TripleQuote { |
| start = start.add("''") |
| } |
| $$ = &struct{from Ident; to Ident}{ |
| from: Ident{ |
| Name: $3.Value, |
| NamePos: start, |
| }, |
| to: *$1.(*Ident), |
| } |
| } |
| |
| parameters_opt: |
| { |
| $$ = nil |
| } |
| | parameters comma_opt |
| { |
| $$ = $1 |
| } |
| |
| parameters_type_opt: |
| { |
| $$ = nil |
| } |
| | parameters_type comma_opt |
| { |
| $$ = $1 |
| } |
| |
| parameters: |
| parameter |
| { |
| $$ = []Expr{$1} |
| } |
| | parameters ',' parameter |
| { |
| $$ = append($1, $3) |
| } |
| |
| // Parameters with optional type annotations |
| parameters_type: |
| parameter_type |
| { |
| $$ = []Expr{$1} |
| } |
| | parameters_type ',' parameter_type |
| { |
| $$ = append($1, $3) |
| } |
| |
| parameter: |
| ident |
| | ident '=' test |
| { |
| $$ = binary($1, $2, $<tok>2, $3) |
| } |
| | '*' ident |
| { |
| $$ = unary($1, $<tok>1, $2) |
| } |
| | '*' |
| { |
| $$ = unary($1, $<tok>1, nil) |
| } |
| | _STAR_STAR ident |
| { |
| $$ = unary($1, $<tok>1, $2) |
| } |
| |
| // Parameter with optional type annotation |
| parameter_type: |
| parameter |
| | |
| ident ':' test |
| { |
| $$ = typed($1, $3) |
| } |
| | ident ':' test '=' test |
| { |
| $$ = binary(typed($1, $3), $4, $<tok>4, $5) |
| } |
| | '*' ident ':' test |
| { |
| $$ = unary($1, $<tok>1, typed($2, $4)) |
| } |
| | _STAR_STAR ident ':' test |
| { |
| $$ = unary($1, $<tok>1, typed($2, $4)) |
| } |
| |
| expr: |
| test %prec ShiftInstead |
| | expr ',' test |
| { |
| tuple, ok := $1.(*TupleExpr) |
| if !ok || !tuple.NoBrackets { |
| tuple = &TupleExpr{ |
| List: []Expr{$1}, |
| NoBrackets: true, |
| ForceCompact: true, |
| ForceMultiLine: false, |
| } |
| } |
| tuple.List = append(tuple.List, $3) |
| $$ = tuple |
| } |
| |
| expr_opt: |
| { |
| $$ = nil |
| } |
| | expr |
| |
| test: |
| primary_expr |
| | _LAMBDA parameters_opt ':' expr |
| { |
| $$ = &LambdaExpr{ |
| Function: Function{ |
| StartPos: $1, |
| Params: $2, |
| Body: []Expr{$4}, |
| }, |
| } |
| } |
| | _NOT test %prec _UNARY { $$ = unary($1, $<tok>1, $2) } |
| | '-' test %prec _UNARY { $$ = unary($1, $<tok>1, $2) } |
| | '+' test %prec _UNARY { $$ = unary($1, $<tok>1, $2) } |
| | '~' test %prec _UNARY { $$ = unary($1, $<tok>1, $2) } |
| | test '*' test { $$ = binary($1, $2, $<tok>2, $3) } |
| | test '%' test { $$ = binary($1, $2, $<tok>2, $3) } |
| | test '/' test { $$ = binary($1, $2, $<tok>2, $3) } |
| | test _INT_DIV test { $$ = binary($1, $2, $<tok>2, $3) } |
| | test '+' test { $$ = binary($1, $2, $<tok>2, $3) } |
| | test '-' test { $$ = binary($1, $2, $<tok>2, $3) } |
| | test '<' test { $$ = binary($1, $2, $<tok>2, $3) } |
| | test '>' test { $$ = binary($1, $2, $<tok>2, $3) } |
| | test _EQ test { $$ = binary($1, $2, $<tok>2, $3) } |
| | test _LE test { $$ = binary($1, $2, $<tok>2, $3) } |
| | test _NE test { $$ = binary($1, $2, $<tok>2, $3) } |
| | test _GE test { $$ = binary($1, $2, $<tok>2, $3) } |
| | test _IN test { $$ = binary($1, $2, $<tok>2, $3) } |
| | test _NOT _IN test { $$ = binary($1, $2, "not in", $4) } |
| | test _OR test { $$ = binary($1, $2, $<tok>2, $3) } |
| | test _AND test { $$ = binary($1, $2, $<tok>2, $3) } |
| | test '|' test { $$ = binary($1, $2, $<tok>2, $3) } |
| | test '&' test { $$ = binary($1, $2, $<tok>2, $3) } |
| | test '^' test { $$ = binary($1, $2, $<tok>2, $3) } |
| | test _BIT_LSH test { $$ = binary($1, $2, $<tok>2, $3) } |
| | test _BIT_RSH test { $$ = binary($1, $2, $<tok>2, $3) } |
| | test _IS test |
| { |
| if b, ok := $3.(*UnaryExpr); ok && b.Op == "not" { |
| $$ = binary($1, $2, "is not", b.X) |
| } else { |
| $$ = binary($1, $2, $<tok>2, $3) |
| } |
| } |
| | test _IF test _ELSE test |
| { |
| $$ = &ConditionalExpr{ |
| Then: $1, |
| IfStart: $2, |
| Test: $3, |
| ElseStart: $4, |
| Else: $5, |
| } |
| } |
| |
| tests: |
| test |
| { |
| $$ = []Expr{$1} |
| } |
| | tests commas test |
| { |
| $$ = append($1, $3) |
| } |
| |
| test_opt: |
| { |
| $$ = nil |
| } |
| | test |
| |
| tests_opt: |
| { |
| $$, $<comma>$ = nil, Position{} |
| } |
| | tests commas_opt |
| { |
| $$, $<comma>$ = $1, $2 |
| } |
| |
| |
| // comma_opt is an optional comma. If the comma is present, |
| // the rule's value is the position of the comma. Otherwise |
| // the rule's value is the zero position. Tracking this |
| // lets us distinguish (x) and (x,). |
| comma_opt: |
| { |
| $$ = Position{} |
| } |
| | ',' |
| |
| // commas allows us to treat multiple consecutive commas as if they are a single |
| // comma token. This is a syntax error in bazel, but a common user error, so it |
| // is convenient to automatically fix it. |
| commas: |
| ',' |
| | commas ',' |
| { |
| $$ = $1 |
| } |
| |
| // commas_opt is the one-or-more comma equivalent of comma_opt, and is used |
| // where trailing commas have some significance. Like commas they squash down |
| // to a single comma if present to fix a common user error. |
| commas_opt: |
| { |
| $$ = Position{} |
| } |
| | commas |
| |
| |
| keyvalue: |
| test ':' test { |
| $$ = &KeyValueExpr{ |
| Key: $1, |
| Colon: $2, |
| Value: $3, |
| } |
| } |
| |
| keyvalues_no_comma: |
| keyvalue |
| { |
| $$ = []*KeyValueExpr{$1} |
| } |
| | keyvalues_no_comma commas keyvalue |
| { |
| $$ = append($1, $3) |
| } |
| |
| keyvalues: |
| { |
| $$ = nil |
| } |
| | keyvalues_no_comma |
| { |
| $$ = $1 |
| } |
| | keyvalues_no_comma commas |
| { |
| $$ = $1 |
| } |
| |
| loop_vars: |
| primary_expr |
| | loop_vars ',' primary_expr |
| { |
| tuple, ok := $1.(*TupleExpr) |
| if !ok || !tuple.NoBrackets { |
| tuple = &TupleExpr{ |
| List: []Expr{$1}, |
| NoBrackets: true, |
| ForceCompact: true, |
| ForceMultiLine: false, |
| } |
| } |
| tuple.List = append(tuple.List, $3) |
| $$ = tuple |
| } |
| |
| string: |
| _STRING |
| { |
| $$ = &StringExpr{ |
| Start: $1, |
| Value: $<str>1, |
| TripleQuote: $<triple>1, |
| End: $1.add($<tok>1), |
| Token: $<tok>1, |
| } |
| } |
| |
| ident: |
| _IDENT |
| { |
| $$ = &Ident{NamePos: $1, Name: $<tok>1} |
| } |
| |
| number: |
| _INT '.' _INT |
| { |
| $$ = &LiteralExpr{Start: $1, Token: $<tok>1 + "." + $<tok>3} |
| } |
| | _INT '.' |
| { |
| $$ = &LiteralExpr{Start: $1, Token: $<tok>1 + "."} |
| } |
| | '.' _INT |
| { |
| $$ = &LiteralExpr{Start: $1, Token: "." + $<tok>2} |
| } |
| | _INT %prec ShiftInstead |
| { |
| $$ = &LiteralExpr{Start: $1, Token: $<tok>1} |
| } |
| |
| for_clause: |
| _FOR loop_vars _IN test |
| { |
| $$ = &ForClause{ |
| For: $1, |
| Vars: $2, |
| In: $3, |
| X: $4, |
| } |
| } |
| |
| for_clause_with_if_clauses_opt: |
| for_clause |
| { |
| $$ = []Expr{$1} |
| } |
| | for_clause_with_if_clauses_opt _IF test |
| { |
| $$ = append($1, &IfClause{ |
| If: $2, |
| Cond: $3, |
| }) |
| } |
| |
| for_clauses_with_if_clauses_opt: |
| for_clause_with_if_clauses_opt |
| { |
| $$ = $1 |
| } |
| | for_clauses_with_if_clauses_opt for_clause_with_if_clauses_opt |
| { |
| $$ = append($1, $2...) |
| } |
| |
| %% |
| |
| // Go helper code. |
| |
| // unary returns a unary expression with the given |
| // position, operator, and subexpression. |
| func unary(pos Position, op string, x Expr) Expr { |
| return &UnaryExpr{ |
| OpStart: pos, |
| Op: op, |
| X: x, |
| } |
| } |
| |
| // binary returns a binary expression with the given |
| // operands, position, and operator. |
| func binary(x Expr, pos Position, op string, y Expr) Expr { |
| _, xend := x.Span() |
| ystart, _ := y.Span() |
| |
| switch op { |
| case "=", "+=", "-=", "*=", "/=", "//=", "%=", "&=", "|=", "^=", "<<=", ">>=": |
| return &AssignExpr{ |
| LHS: x, |
| OpPos: pos, |
| Op: op, |
| LineBreak: xend.Line < ystart.Line, |
| RHS: y, |
| } |
| } |
| |
| return &BinaryExpr{ |
| X: x, |
| OpStart: pos, |
| Op: op, |
| LineBreak: xend.Line < ystart.Line, |
| Y: y, |
| } |
| } |
| |
| // typed returns a TypedIdent expression |
| func typed(x, y Expr) *TypedIdent { |
| return &TypedIdent{ |
| Ident: x.(*Ident), |
| Type: y, |
| } |
| } |
| |
| // isSimpleExpression returns whether an expression is simple and allowed to exist in |
| // compact forms of sequences. |
| // The formal criteria are the following: an expression is considered simple if it's |
| // a literal (variable, string or a number), a literal with a unary operator or an empty sequence. |
| func isSimpleExpression(expr *Expr) bool { |
| switch x := (*expr).(type) { |
| case *LiteralExpr, *StringExpr, *Ident: |
| return true |
| case *UnaryExpr: |
| _, literal := x.X.(*LiteralExpr) |
| _, ident := x.X.(*Ident) |
| return literal || ident |
| case *ListExpr: |
| return len(x.List) == 0 |
| case *TupleExpr: |
| return len(x.List) == 0 |
| case *DictExpr: |
| return len(x.List) == 0 |
| case *SetExpr: |
| return len(x.List) == 0 |
| default: |
| return false |
| } |
| } |
| |
| // forceCompact returns the setting for the ForceCompact field for a call or tuple. |
| // |
| // NOTE 1: The field is called ForceCompact, not ForceSingleLine, |
| // because it only affects the formatting associated with the call or tuple syntax, |
| // not the formatting of the arguments. For example: |
| // |
| // call([ |
| // 1, |
| // 2, |
| // 3, |
| // ]) |
| // |
| // is still a compact call even though it runs on multiple lines. |
| // |
| // In contrast the multiline form puts a linebreak after the (. |
| // |
| // call( |
| // [ |
| // 1, |
| // 2, |
| // 3, |
| // ], |
| // ) |
| // |
| // NOTE 2: Because of NOTE 1, we cannot use start and end on the |
| // same line as a signal for compact mode: the formatting of an |
| // embedded list might move the end to a different line, which would |
| // then look different on rereading and cause buildifier not to be |
| // idempotent. Instead, we have to look at properties guaranteed |
| // to be preserved by the reformatting, namely that the opening |
| // paren and the first expression are on the same line and that |
| // each subsequent expression begins on the same line as the last |
| // one ended (no line breaks after comma). |
| func forceCompact(start Position, list []Expr, end Position) bool { |
| if len(list) <= 1 { |
| // The call or tuple will probably be compact anyway; don't force it. |
| return false |
| } |
| |
| // If there are any named arguments or non-string, non-literal |
| // arguments, cannot force compact mode. |
| line := start.Line |
| for _, x := range list { |
| start, end := x.Span() |
| if start.Line != line { |
| return false |
| } |
| line = end.Line |
| if !isSimpleExpression(&x) { |
| return false |
| } |
| } |
| return end.Line == line |
| } |
| |
| // forceMultiLine returns the setting for the ForceMultiLine field. |
| func forceMultiLine(start Position, list []Expr, end Position) bool { |
| if len(list) > 1 { |
| // The call will be multiline anyway, because it has multiple elements. Don't force it. |
| return false |
| } |
| |
| if len(list) == 0 { |
| // Empty list: use position of brackets. |
| return start.Line != end.Line |
| } |
| |
| // Single-element list. |
| // Check whether opening bracket is on different line than beginning of |
| // element, or closing bracket is on different line than end of element. |
| elemStart, elemEnd := list[0].Span() |
| return start.Line != elemStart.Line || end.Line != elemEnd.Line |
| } |
| |
| // forceMultiLineComprehension returns the setting for the ForceMultiLine field for a comprehension. |
| func forceMultiLineComprehension(start Position, expr Expr, clauses []Expr, end Position) bool { |
| // Return true if there's at least one line break between start, expr, each clause, and end |
| exprStart, exprEnd := expr.Span() |
| if start.Line != exprStart.Line { |
| return true |
| } |
| previousEnd := exprEnd |
| for _, clause := range clauses { |
| clauseStart, clauseEnd := clause.Span() |
| if previousEnd.Line != clauseStart.Line { |
| return true |
| } |
| previousEnd = clauseEnd |
| } |
| return previousEnd.Line != end.Line |
| } |
| |
| // extractTrailingComments extracts trailing comments of an indented block starting with the first |
| // comment line with indentation less than the block indentation. |
| // The comments can either belong to CommentBlock statements or to the last non-comment statement |
| // as After-comments. |
| func extractTrailingComments(stmt Expr) []Expr { |
| body := getLastBody(stmt) |
| var comments []Expr |
| if body != nil && len(*body) > 0 { |
| // Get the current indentation level |
| start, _ := (*body)[0].Span() |
| indentation := start.LineRune |
| |
| // Find the last non-comment statement |
| lastNonCommentIndex := -1 |
| for i, stmt := range *body { |
| if _, ok := stmt.(*CommentBlock); !ok { |
| lastNonCommentIndex = i |
| } |
| } |
| if lastNonCommentIndex == -1 { |
| return comments |
| } |
| |
| // Iterate over the trailing comments, find the first comment line that's not indented enough, |
| // dedent it and all the following comments. |
| for i := lastNonCommentIndex; i < len(*body); i++ { |
| stmt := (*body)[i] |
| if comment := extractDedentedComment(stmt, indentation); comment != nil { |
| // This comment and all the following CommentBlock statements are to be extracted. |
| comments = append(comments, comment) |
| comments = append(comments, (*body)[i+1:]...) |
| *body = (*body)[:i+1] |
| // If the current statement is a CommentBlock statement without any comment lines |
| // it should be removed too. |
| if i > lastNonCommentIndex && len(stmt.Comment().After) == 0 { |
| *body = (*body)[:i] |
| } |
| } |
| } |
| } |
| return comments |
| } |
| |
| // extractDedentedComment extract the first comment line from `stmt` which indentation is smaller |
| // than `indentation`, and all following comment lines, and returns them in a newly created |
| // CommentBlock statement. |
| func extractDedentedComment(stmt Expr, indentation int) Expr { |
| for i, line := range stmt.Comment().After { |
| // line.Start.LineRune == 0 can't exist in parsed files, it indicates that the comment line |
| // has been added by an AST modification. Don't take such lines into account. |
| if line.Start.LineRune > 0 && line.Start.LineRune < indentation { |
| // This and all the following lines should be dedented |
| cb := &CommentBlock{ |
| Start: line.Start, |
| Comments: Comments{After: stmt.Comment().After[i:]}, |
| } |
| stmt.Comment().After = stmt.Comment().After[:i] |
| return cb |
| } |
| } |
| return nil |
| } |
| |
| // getLastBody returns the last body of a block statement (the only body for For- and DefStmt |
| // objects, the last in a if-elif-else chain |
| func getLastBody(stmt Expr) *[]Expr { |
| switch block := stmt.(type) { |
| case *DefStmt: |
| return &block.Body |
| case *ForStmt: |
| return &block.Body |
| case *IfStmt: |
| if len(block.False) == 0 { |
| return &block.True |
| } else if len(block.False) == 1 { |
| if next, ok := block.False[0].(*IfStmt); ok { |
| // Recursively find the last block of the chain |
| return getLastBody(next) |
| } |
| } |
| return &block.False |
| } |
| return nil |
| } |