github.com/cheshirekow/buildtools@v0.0.0-20200224190056-5d637702fe81/build/parse.y (about) 1 // BUILD file parser. 2 3 // This is a yacc grammar. Its lexer is in lex.go. 4 // 5 // For a good introduction to writing yacc grammars, see 6 // Kernighan and Pike's book The Unix Programming Environment. 7 // 8 // The definitive yacc manual is 9 // Stephen C. Johnson and Ravi Sethi, "Yacc: A Parser Generator", 10 // online at http://plan9.bell-labs.com/sys/doc/yacc.pdf. 11 12 %{ 13 package build 14 %} 15 16 // The generated parser puts these fields in a struct named yySymType. 17 // (The name %union is historical, but it is inaccurate for Go.) 18 %union { 19 // input tokens 20 tok string // raw input syntax 21 str string // decoding of quoted string 22 pos Position // position of token 23 triple bool // was string triple quoted? 24 25 // partial syntax trees 26 expr Expr 27 exprs []Expr 28 string *StringExpr 29 ifstmt *IfStmt 30 loadarg *struct{from Ident; to Ident} 31 loadargs []*struct{from Ident; to Ident} 32 33 // supporting information 34 comma Position // position of trailing comma in list, if present 35 lastStmt Expr // most recent rule, to attach line comments to 36 } 37 38 // These declarations set the type for a $ reference ($$, $1, $2, ...) 39 // based on the kind of symbol it refers to. Other fields can be referred 40 // to explicitly, as in $<tok>1. 41 // 42 // %token is for input tokens generated by the lexer. 43 // %type is for higher-level grammar rules defined here. 44 // 45 // It is possible to put multiple tokens per line, but it is easier to 46 // keep ordered using a sparser one-per-line list. 47 48 %token <pos> '%' 49 %token <pos> '(' 50 %token <pos> ')' 51 %token <pos> '*' 52 %token <pos> '+' 53 %token <pos> ',' 54 %token <pos> '-' 55 %token <pos> '.' 56 %token <pos> '/' 57 %token <pos> ':' 58 %token <pos> '<' 59 %token <pos> '=' 60 %token <pos> '>' 61 %token <pos> '[' 62 %token <pos> ']' 63 %token <pos> '{' 64 %token <pos> '}' 65 %token <pos> '|' 66 %token <pos> '&' 67 %token <pos> '^' 68 %token <pos> '~' 69 70 // By convention, yacc token names are all caps. 71 // However, we do not want to export them from the Go package 72 // we are creating, so prefix them all with underscores. 73 74 %token <pos> _AUGM // augmented assignment 75 %token <pos> _AND // keyword and 76 %token <pos> _COMMENT // top-level # comment 77 %token <pos> _EOF // end of file 78 %token <pos> _EQ // operator == 79 %token <pos> _FOR // keyword for 80 %token <pos> _GE // operator >= 81 %token <pos> _IDENT // non-keyword identifier 82 %token <pos> _NUMBER // number 83 %token <pos> _IF // keyword if 84 %token <pos> _ELSE // keyword else 85 %token <pos> _ELIF // keyword elif 86 %token <pos> _IN // keyword in 87 %token <pos> _IS // keyword is 88 %token <pos> _LAMBDA // keyword lambda 89 %token <pos> _LOAD // keyword load 90 %token <pos> _LE // operator <= 91 %token <pos> _NE // operator != 92 %token <pos> _STAR_STAR // operator ** 93 %token <pos> _INT_DIV // operator // 94 %token <pos> _BIT_LSH // bitwise operator << 95 %token <pos> _BIT_RSH // bitwise operator >> 96 %token <pos> _NOT // keyword not 97 %token <pos> _OR // keyword or 98 %token <pos> _STRING // quoted string 99 %token <pos> _DEF // keyword def 100 %token <pos> _RETURN // keyword return 101 %token <pos> _PASS // keyword pass 102 %token <pos> _BREAK // keyword break 103 %token <pos> _CONTINUE // keyword continue 104 %token <pos> _INDENT // indentation 105 %token <pos> _UNINDENT // unindentation 106 107 %type <pos> comma_opt 108 %type <expr> argument 109 %type <exprs> arguments 110 %type <exprs> arguments_opt 111 %type <expr> parameter 112 %type <exprs> parameters 113 %type <exprs> parameters_opt 114 %type <expr> test 115 %type <expr> test_opt 116 %type <exprs> tests_opt 117 %type <expr> primary_expr 118 %type <expr> expr 119 %type <expr> expr_opt 120 %type <exprs> tests 121 %type <exprs> exprs 122 %type <exprs> exprs_opt 123 %type <expr> loop_vars 124 %type <expr> for_clause 125 %type <exprs> for_clause_with_if_clauses_opt 126 %type <exprs> for_clauses_with_if_clauses_opt 127 %type <expr> ident 128 %type <expr> number 129 %type <exprs> stmts 130 %type <exprs> stmt // a simple_stmt or a for/if/def block 131 %type <expr> block_stmt // a single for/if/def statement 132 %type <ifstmt> if_else_block // a complete if-elif-else block 133 %type <ifstmt> if_chain // an elif-elif-else chain 134 %type <pos> elif // `elif` or `else if` token(s) 135 %type <exprs> simple_stmt // One or many small_stmts on one line, e.g. 'a = f(x); return str(a)' 136 %type <expr> small_stmt // A single statement, e.g. 'a = f(x)' 137 %type <exprs> small_stmts_continuation // A sequence of `';' small_stmt` 138 %type <expr> keyvalue 139 %type <exprs> keyvalues 140 %type <exprs> keyvalues_no_comma 141 %type <string> string 142 %type <exprs> suite 143 %type <exprs> comments 144 %type <loadarg> load_argument 145 %type <loadargs> load_arguments 146 147 // Operator precedence. 148 // Operators listed lower in the table bind tighter. 149 150 // We tag rules with this fake, low precedence to indicate 151 // that when the rule is involved in a shift/reduce 152 // conflict, we prefer that the parser shift (try for a longer parse). 153 // Shifting is the default resolution anyway, but stating it explicitly 154 // silences yacc's warning for that specific case. 155 %left ShiftInstead 156 157 %left '\n' 158 %left _ASSERT 159 // '=' and augmented assignments have the lowest precedence 160 // e.g. "x = a if c > 0 else 'bar'" 161 // followed by 162 // 'if' and 'else' which have lower precedence than all other operators. 163 // e.g. "a, b if c > 0 else 'foo'" is either a tuple of (a,b) or 'foo' 164 // and not a tuple of "(a, (b if ... ))" 165 %left '=' _AUGM 166 %left _IF _ELSE _ELIF 167 %left ',' 168 %left ':' 169 %left _IS 170 %left _OR 171 %left _AND 172 %left '<' '>' _EQ _NE _LE _GE _NOT _IN 173 %left '|' 174 %left '^' 175 %left '&' 176 %left _BIT_LSH _BIT_RSH 177 %left '+' '-' 178 %left '*' '/' '%' _INT_DIV 179 %left '.' '[' '(' 180 %right _UNARY 181 %left _STRING 182 183 %% 184 185 // Grammar rules. 186 // 187 // A note on names: if foo is a rule, then foos is a sequence of foos 188 // (with interleaved commas or other syntax as appropriate) 189 // and foo_opt is an optional foo. 190 191 file: 192 stmts _EOF 193 { 194 yylex.(*input).file = &File{Stmt: $1} 195 return 0 196 } 197 198 suite: 199 '\n' comments _INDENT stmts _UNINDENT 200 { 201 statements := $4 202 if $2 != nil { 203 // $2 can only contain *CommentBlock objects, each of them contains a non-empty After slice 204 cb := $2[len($2)-1].(*CommentBlock) 205 // $4 can't be empty and can't start with a comment 206 stmt := $4[0] 207 start, _ := stmt.Span() 208 if start.Line - cb.After[len(cb.After)-1].Start.Line == 1 { 209 // The first statement of $4 starts on the next line after the last comment of $2. 210 // Attach the last comment to the first statement 211 stmt.Comment().Before = cb.After 212 $2 = $2[:len($2)-1] 213 } 214 statements = append($2, $4...) 215 } 216 $$ = statements 217 $<lastStmt>$ = $<lastStmt>4 218 } 219 | simple_stmt linebreaks_opt 220 { 221 $$ = $1 222 } 223 224 linebreaks_opt: 225 | linebreaks_opt '\n' 226 227 comments: 228 { 229 $$ = nil 230 $<lastStmt>$ = nil 231 } 232 | comments _COMMENT '\n' 233 { 234 $$ = $1 235 $<lastStmt>$ = $<lastStmt>1 236 if $<lastStmt>$ == nil { 237 cb := &CommentBlock{Start: $2} 238 $$ = append($$, cb) 239 $<lastStmt>$ = cb 240 } 241 com := $<lastStmt>$.Comment() 242 com.After = append(com.After, Comment{Start: $2, Token: $<tok>2}) 243 } 244 | comments '\n' 245 { 246 $$ = $1 247 $<lastStmt>$ = nil 248 } 249 250 stmts: 251 { 252 $$ = nil 253 $<lastStmt>$ = nil 254 } 255 | stmts stmt 256 { 257 // If this statement follows a comment block, 258 // attach the comments to the statement. 259 if cb, ok := $<lastStmt>1.(*CommentBlock); ok { 260 $$ = append($1[:len($1)-1], $2...) 261 $2[0].Comment().Before = cb.After 262 $<lastStmt>$ = $<lastStmt>2 263 break 264 } 265 266 // Otherwise add to list. 267 $$ = append($1, $2...) 268 $<lastStmt>$ = $<lastStmt>2 269 270 // Consider this input: 271 // 272 // foo() 273 // # bar 274 // baz() 275 // 276 // If we've just parsed baz(), the # bar is attached to 277 // foo() as an After comment. Make it a Before comment 278 // for baz() instead. 279 if x := $<lastStmt>1; x != nil { 280 com := x.Comment() 281 // stmt is never empty 282 $2[0].Comment().Before = com.After 283 com.After = nil 284 } 285 } 286 | stmts '\n' 287 { 288 // Blank line; sever last rule from future comments. 289 $$ = $1 290 $<lastStmt>$ = nil 291 } 292 | stmts _COMMENT '\n' 293 { 294 $$ = $1 295 $<lastStmt>$ = $<lastStmt>1 296 if $<lastStmt>$ == nil { 297 cb := &CommentBlock{Start: $2} 298 $$ = append($$, cb) 299 $<lastStmt>$ = cb 300 } 301 com := $<lastStmt>$.Comment() 302 com.After = append(com.After, Comment{Start: $2, Token: $<tok>2}) 303 } 304 305 stmt: 306 simple_stmt 307 { 308 $$ = $1 309 $<lastStmt>$ = $1[len($1)-1] 310 } 311 | block_stmt 312 { 313 $$ = []Expr{$1} 314 $<lastStmt>$ = $1 315 if cbs := extractTrailingComments($1); len(cbs) > 0 { 316 $$ = append($$, cbs...) 317 $<lastStmt>$ = cbs[len(cbs)-1] 318 if $<lastStmt>1 == nil { 319 $<lastStmt>$ = nil 320 } 321 } 322 } 323 324 block_stmt: 325 _DEF _IDENT '(' parameters_opt ')' ':' suite 326 { 327 $$ = &DefStmt{ 328 Function: Function{ 329 StartPos: $1, 330 Params: $4, 331 Body: $7, 332 }, 333 Name: $<tok>2, 334 ColonPos: $6, 335 ForceCompact: forceCompact($3, $4, $5), 336 ForceMultiLine: forceMultiLine($3, $4, $5), 337 } 338 $<lastStmt>$ = $<lastStmt>7 339 } 340 | _FOR loop_vars _IN expr ':' suite 341 { 342 $$ = &ForStmt{ 343 For: $1, 344 Vars: $2, 345 X: $4, 346 Body: $6, 347 } 348 $<lastStmt>$ = $<lastStmt>6 349 } 350 | if_else_block 351 { 352 $$ = $1 353 $<lastStmt>$ = $<lastStmt>1 354 } 355 356 // One or several if-elif-elif statements 357 if_chain: 358 _IF expr ':' suite 359 { 360 $$ = &IfStmt{ 361 If: $1, 362 Cond: $2, 363 True: $4, 364 } 365 $<lastStmt>$ = $<lastStmt>4 366 } 367 | if_chain elif expr ':' suite 368 { 369 $$ = $1 370 inner := $1 371 for len(inner.False) == 1 { 372 inner = inner.False[0].(*IfStmt) 373 } 374 inner.ElsePos = End{Pos: $2} 375 inner.False = []Expr{ 376 &IfStmt{ 377 If: $2, 378 Cond: $3, 379 True: $5, 380 }, 381 } 382 $<lastStmt>$ = $<lastStmt>5 383 } 384 385 // A complete if-elif-elif-else chain 386 if_else_block: 387 if_chain 388 | if_chain _ELSE ':' suite 389 { 390 $$ = $1 391 inner := $1 392 for len(inner.False) == 1 { 393 inner = inner.False[0].(*IfStmt) 394 } 395 inner.ElsePos = End{Pos: $2} 396 inner.False = $4 397 $<lastStmt>$ = $<lastStmt>4 398 } 399 400 elif: 401 _ELSE _IF 402 | _ELIF 403 404 simple_stmt: 405 small_stmt small_stmts_continuation semi_opt '\n' 406 { 407 $$ = append([]Expr{$1}, $2...) 408 $<lastStmt>$ = $$[len($$)-1] 409 } 410 411 small_stmts_continuation: 412 { 413 $$ = []Expr{} 414 } 415 | small_stmts_continuation ';' small_stmt 416 { 417 $$ = append($1, $3) 418 } 419 420 small_stmt: 421 expr %prec ShiftInstead 422 | _RETURN expr 423 { 424 $$ = &ReturnStmt{ 425 Return: $1, 426 Result: $2, 427 } 428 } 429 | _RETURN 430 { 431 $$ = &ReturnStmt{ 432 Return: $1, 433 } 434 } 435 | expr '=' expr { $$ = binary($1, $2, $<tok>2, $3) } 436 | expr _AUGM expr { $$ = binary($1, $2, $<tok>2, $3) } 437 | _PASS 438 { 439 $$ = &BranchStmt{ 440 Token: $<tok>1, 441 TokenPos: $1, 442 } 443 } 444 | _BREAK 445 { 446 $$ = &BranchStmt{ 447 Token: $<tok>1, 448 TokenPos: $1, 449 } 450 } 451 | _CONTINUE 452 { 453 $$ = &BranchStmt{ 454 Token: $<tok>1, 455 TokenPos: $1, 456 } 457 } 458 459 semi_opt: 460 | ';' 461 462 primary_expr: 463 ident 464 | number 465 | string 466 { 467 $$ = $1 468 } 469 | primary_expr '.' _IDENT 470 { 471 $$ = &DotExpr{ 472 X: $1, 473 Dot: $2, 474 NamePos: $3, 475 Name: $<tok>3, 476 } 477 } 478 | _LOAD '(' string ',' load_arguments comma_opt ')' 479 { 480 load := &LoadStmt{ 481 Load: $1, 482 Module: $3, 483 Rparen: End{Pos: $7}, 484 ForceCompact: $1.Line == $7.Line, 485 } 486 for _, arg := range $5 { 487 load.From = append(load.From, &arg.from) 488 load.To = append(load.To, &arg.to) 489 } 490 $$ = load 491 } 492 | primary_expr '(' arguments_opt ')' 493 { 494 $$ = &CallExpr{ 495 X: $1, 496 ListStart: $2, 497 List: $3, 498 End: End{Pos: $4}, 499 ForceCompact: forceCompact($2, $3, $4), 500 ForceMultiLine: forceMultiLine($2, $3, $4), 501 } 502 } 503 | primary_expr '[' expr ']' 504 { 505 $$ = &IndexExpr{ 506 X: $1, 507 IndexStart: $2, 508 Y: $3, 509 End: $4, 510 } 511 } 512 | primary_expr '[' expr_opt ':' test_opt ']' 513 { 514 $$ = &SliceExpr{ 515 X: $1, 516 SliceStart: $2, 517 From: $3, 518 FirstColon: $4, 519 To: $5, 520 End: $6, 521 } 522 } 523 | primary_expr '[' expr_opt ':' test_opt ':' test_opt ']' 524 { 525 $$ = &SliceExpr{ 526 X: $1, 527 SliceStart: $2, 528 From: $3, 529 FirstColon: $4, 530 To: $5, 531 SecondColon: $6, 532 Step: $7, 533 End: $8, 534 } 535 } 536 | '[' tests_opt ']' 537 { 538 $$ = &ListExpr{ 539 Start: $1, 540 List: $2, 541 End: End{Pos: $3}, 542 ForceMultiLine: forceMultiLine($1, $2, $3), 543 } 544 } 545 | '[' test for_clauses_with_if_clauses_opt ']' 546 { 547 $$ = &Comprehension{ 548 Curly: false, 549 Lbrack: $1, 550 Body: $2, 551 Clauses: $3, 552 End: End{Pos: $4}, 553 ForceMultiLine: forceMultiLineComprehension($1, $2, $3, $4), 554 } 555 } 556 | '{' keyvalue for_clauses_with_if_clauses_opt '}' 557 { 558 $$ = &Comprehension{ 559 Curly: true, 560 Lbrack: $1, 561 Body: $2, 562 Clauses: $3, 563 End: End{Pos: $4}, 564 ForceMultiLine: forceMultiLineComprehension($1, $2, $3, $4), 565 } 566 } 567 | '{' keyvalues '}' 568 { 569 $$ = &DictExpr{ 570 Start: $1, 571 List: $2, 572 End: End{Pos: $3}, 573 ForceMultiLine: forceMultiLine($1, $2, $3), 574 } 575 } 576 | '{' tests comma_opt '}' // TODO: remove, not supported 577 { 578 $$ = &SetExpr{ 579 Start: $1, 580 List: $2, 581 End: End{Pos: $4}, 582 ForceMultiLine: forceMultiLine($1, $2, $4), 583 } 584 } 585 | '(' tests_opt ')' 586 { 587 if len($2) == 1 && $<comma>2.Line == 0 { 588 // Just a parenthesized expression, not a tuple. 589 $$ = &ParenExpr{ 590 Start: $1, 591 X: $2[0], 592 End: End{Pos: $3}, 593 ForceMultiLine: forceMultiLine($1, $2, $3), 594 } 595 } else { 596 $$ = &TupleExpr{ 597 Start: $1, 598 List: $2, 599 End: End{Pos: $3}, 600 ForceCompact: forceCompact($1, $2, $3), 601 ForceMultiLine: forceMultiLine($1, $2, $3), 602 } 603 } 604 } 605 606 arguments_opt: 607 { 608 $$ = nil 609 } 610 | arguments comma_opt 611 { 612 $$ = $1 613 } 614 615 arguments: 616 argument 617 { 618 $$ = []Expr{$1} 619 } 620 | arguments ',' argument 621 { 622 $$ = append($1, $3) 623 } 624 625 argument: 626 test 627 | ident '=' test 628 { 629 $$ = binary($1, $2, $<tok>2, $3) 630 } 631 | '*' test 632 { 633 $$ = unary($1, $<tok>1, $2) 634 } 635 | _STAR_STAR test 636 { 637 $$ = unary($1, $<tok>1, $2) 638 } 639 640 load_arguments: 641 load_argument { 642 $$ = []*struct{from Ident; to Ident}{$1} 643 } 644 | load_arguments ',' load_argument 645 { 646 $1 = append($1, $3) 647 $$ = $1 648 } 649 650 load_argument: 651 string { 652 start := $1.Start.add("'") 653 if $1.TripleQuote { 654 start = start.add("''") 655 } 656 $$ = &struct{from Ident; to Ident}{ 657 from: Ident{ 658 Name: $1.Value, 659 NamePos: start, 660 }, 661 to: Ident{ 662 Name: $1.Value, 663 NamePos: start, 664 }, 665 } 666 } 667 | ident '=' string 668 { 669 start := $3.Start.add("'") 670 if $3.TripleQuote { 671 start = start.add("''") 672 } 673 $$ = &struct{from Ident; to Ident}{ 674 from: Ident{ 675 Name: $3.Value, 676 NamePos: start, 677 }, 678 to: *$1.(*Ident), 679 } 680 } 681 682 parameters_opt: 683 { 684 $$ = nil 685 } 686 | parameters comma_opt 687 { 688 $$ = $1 689 } 690 691 parameters: 692 parameter 693 { 694 $$ = []Expr{$1} 695 } 696 | parameters ',' parameter 697 { 698 $$ = append($1, $3) 699 } 700 701 parameter: 702 ident 703 | ident '=' test 704 { 705 $$ = binary($1, $2, $<tok>2, $3) 706 } 707 | '*' ident 708 { 709 $$ = unary($1, $<tok>1, $2) 710 } 711 | '*' 712 { 713 $$ = unary($1, $<tok>1, nil) 714 } 715 | _STAR_STAR ident 716 { 717 $$ = unary($1, $<tok>1, $2) 718 } 719 720 expr: 721 test 722 | expr ',' test 723 { 724 tuple, ok := $1.(*TupleExpr) 725 if !ok || !tuple.NoBrackets { 726 tuple = &TupleExpr{ 727 List: []Expr{$1}, 728 NoBrackets: true, 729 ForceCompact: true, 730 ForceMultiLine: false, 731 } 732 } 733 tuple.List = append(tuple.List, $3) 734 $$ = tuple 735 } 736 737 expr_opt: 738 { 739 $$ = nil 740 } 741 | expr 742 743 exprs: 744 expr 745 { 746 $$ = []Expr{$1} 747 } 748 | exprs ',' expr 749 { 750 $$ = append($1, $3) 751 } 752 753 exprs_opt: 754 { 755 $$ = nil 756 } 757 | exprs comma_opt 758 { 759 $$ = $1 760 } 761 762 test: 763 primary_expr 764 | _LAMBDA exprs_opt ':' expr // TODO: remove, not supported 765 { 766 $$ = &LambdaExpr{ 767 Function: Function{ 768 StartPos: $1, 769 Params: $2, 770 Body: []Expr{$4}, 771 }, 772 } 773 } 774 | _NOT test %prec _UNARY { $$ = unary($1, $<tok>1, $2) } 775 | '-' test %prec _UNARY { $$ = unary($1, $<tok>1, $2) } 776 | '+' test %prec _UNARY { $$ = unary($1, $<tok>1, $2) } 777 | '~' test %prec _UNARY { $$ = unary($1, $<tok>1, $2) } 778 | test '*' test { $$ = binary($1, $2, $<tok>2, $3) } 779 | test '%' test { $$ = binary($1, $2, $<tok>2, $3) } 780 | test '/' test { $$ = binary($1, $2, $<tok>2, $3) } 781 | test _INT_DIV test { $$ = binary($1, $2, $<tok>2, $3) } 782 | test '+' test { $$ = binary($1, $2, $<tok>2, $3) } 783 | test '-' test { $$ = binary($1, $2, $<tok>2, $3) } 784 | test '<' test { $$ = binary($1, $2, $<tok>2, $3) } 785 | test '>' test { $$ = binary($1, $2, $<tok>2, $3) } 786 | test _EQ test { $$ = binary($1, $2, $<tok>2, $3) } 787 | test _LE test { $$ = binary($1, $2, $<tok>2, $3) } 788 | test _NE test { $$ = binary($1, $2, $<tok>2, $3) } 789 | test _GE test { $$ = binary($1, $2, $<tok>2, $3) } 790 | test _IN test { $$ = binary($1, $2, $<tok>2, $3) } 791 | test _NOT _IN test { $$ = binary($1, $2, "not in", $4) } 792 | test _OR test { $$ = binary($1, $2, $<tok>2, $3) } 793 | test _AND test { $$ = binary($1, $2, $<tok>2, $3) } 794 | test '|' test { $$ = binary($1, $2, $<tok>2, $3) } 795 | test '&' test { $$ = binary($1, $2, $<tok>2, $3) } 796 | test '^' test { $$ = binary($1, $2, $<tok>2, $3) } 797 | test _BIT_LSH test { $$ = binary($1, $2, $<tok>2, $3) } 798 | test _BIT_RSH test { $$ = binary($1, $2, $<tok>2, $3) } 799 | test _IS test 800 { 801 if b, ok := $3.(*UnaryExpr); ok && b.Op == "not" { 802 $$ = binary($1, $2, "is not", b.X) 803 } else { 804 $$ = binary($1, $2, $<tok>2, $3) 805 } 806 } 807 | test _IF test _ELSE test 808 { 809 $$ = &ConditionalExpr{ 810 Then: $1, 811 IfStart: $2, 812 Test: $3, 813 ElseStart: $4, 814 Else: $5, 815 } 816 } 817 818 tests: 819 test 820 { 821 $$ = []Expr{$1} 822 } 823 | tests ',' test 824 { 825 $$ = append($1, $3) 826 } 827 828 test_opt: 829 { 830 $$ = nil 831 } 832 | test 833 834 tests_opt: 835 { 836 $$, $<comma>$ = nil, Position{} 837 } 838 | tests comma_opt 839 { 840 $$, $<comma>$ = $1, $2 841 } 842 843 // comma_opt is an optional comma. If the comma is present, 844 // the rule's value is the position of the comma. Otherwise 845 // the rule's value is the zero position. Tracking this 846 // lets us distinguish (x) and (x,). 847 comma_opt: 848 { 849 $$ = Position{} 850 } 851 | ',' 852 853 keyvalue: 854 test ':' test { 855 $$ = &KeyValueExpr{ 856 Key: $1, 857 Colon: $2, 858 Value: $3, 859 } 860 } 861 862 keyvalues_no_comma: 863 keyvalue 864 { 865 $$ = []Expr{$1} 866 } 867 | keyvalues_no_comma ',' keyvalue 868 { 869 $$ = append($1, $3) 870 } 871 872 keyvalues: 873 { 874 $$ = nil 875 } 876 | keyvalues_no_comma 877 { 878 $$ = $1 879 } 880 | keyvalues_no_comma ',' 881 { 882 $$ = $1 883 } 884 885 loop_vars: 886 primary_expr 887 | loop_vars ',' primary_expr 888 { 889 tuple, ok := $1.(*TupleExpr) 890 if !ok || !tuple.NoBrackets { 891 tuple = &TupleExpr{ 892 List: []Expr{$1}, 893 NoBrackets: true, 894 ForceCompact: true, 895 ForceMultiLine: false, 896 } 897 } 898 tuple.List = append(tuple.List, $3) 899 $$ = tuple 900 } 901 902 string: 903 _STRING 904 { 905 $$ = &StringExpr{ 906 Start: $1, 907 Value: $<str>1, 908 TripleQuote: $<triple>1, 909 End: $1.add($<tok>1), 910 Token: $<tok>1, 911 } 912 } 913 914 ident: 915 _IDENT 916 { 917 $$ = &Ident{NamePos: $1, Name: $<tok>1} 918 } 919 920 number: 921 _NUMBER 922 { 923 $$ = &LiteralExpr{Start: $1, Token: $<tok>1} 924 } 925 926 for_clause: 927 _FOR loop_vars _IN test 928 { 929 $$ = &ForClause{ 930 For: $1, 931 Vars: $2, 932 In: $3, 933 X: $4, 934 } 935 } 936 937 for_clause_with_if_clauses_opt: 938 for_clause { 939 $$ = []Expr{$1} 940 } 941 | for_clause_with_if_clauses_opt _IF test { 942 $$ = append($1, &IfClause{ 943 If: $2, 944 Cond: $3, 945 }) 946 } 947 948 for_clauses_with_if_clauses_opt: 949 for_clause_with_if_clauses_opt 950 { 951 $$ = $1 952 } 953 | for_clauses_with_if_clauses_opt for_clause_with_if_clauses_opt { 954 $$ = append($1, $2...) 955 } 956 957 %% 958 959 // Go helper code. 960 961 // unary returns a unary expression with the given 962 // position, operator, and subexpression. 963 func unary(pos Position, op string, x Expr) Expr { 964 return &UnaryExpr{ 965 OpStart: pos, 966 Op: op, 967 X: x, 968 } 969 } 970 971 // binary returns a binary expression with the given 972 // operands, position, and operator. 973 func binary(x Expr, pos Position, op string, y Expr) Expr { 974 _, xend := x.Span() 975 ystart, _ := y.Span() 976 977 switch op { 978 case "=", "+=", "-=", "*=", "/=", "//=", "%=", "|=": 979 return &AssignExpr{ 980 LHS: x, 981 OpPos: pos, 982 Op: op, 983 LineBreak: xend.Line < ystart.Line, 984 RHS: y, 985 } 986 } 987 988 return &BinaryExpr{ 989 X: x, 990 OpStart: pos, 991 Op: op, 992 LineBreak: xend.Line < ystart.Line, 993 Y: y, 994 } 995 } 996 997 // isSimpleExpression returns whether an expression is simple and allowed to exist in 998 // compact forms of sequences. 999 // The formal criteria are the following: an expression is considered simple if it's 1000 // a literal (variable, string or a number), a literal with a unary operator or an empty sequence. 1001 func isSimpleExpression(expr *Expr) bool { 1002 switch x := (*expr).(type) { 1003 case *LiteralExpr, *StringExpr, *Ident: 1004 return true 1005 case *UnaryExpr: 1006 _, literal := x.X.(*LiteralExpr) 1007 _, ident := x.X.(*Ident) 1008 return literal || ident 1009 case *ListExpr: 1010 return len(x.List) == 0 1011 case *TupleExpr: 1012 return len(x.List) == 0 1013 case *DictExpr: 1014 return len(x.List) == 0 1015 case *SetExpr: 1016 return len(x.List) == 0 1017 default: 1018 return false 1019 } 1020 } 1021 1022 // forceCompact returns the setting for the ForceCompact field for a call or tuple. 1023 // 1024 // NOTE 1: The field is called ForceCompact, not ForceSingleLine, 1025 // because it only affects the formatting associated with the call or tuple syntax, 1026 // not the formatting of the arguments. For example: 1027 // 1028 // call([ 1029 // 1, 1030 // 2, 1031 // 3, 1032 // ]) 1033 // 1034 // is still a compact call even though it runs on multiple lines. 1035 // 1036 // In contrast the multiline form puts a linebreak after the (. 1037 // 1038 // call( 1039 // [ 1040 // 1, 1041 // 2, 1042 // 3, 1043 // ], 1044 // ) 1045 // 1046 // NOTE 2: Because of NOTE 1, we cannot use start and end on the 1047 // same line as a signal for compact mode: the formatting of an 1048 // embedded list might move the end to a different line, which would 1049 // then look different on rereading and cause buildifier not to be 1050 // idempotent. Instead, we have to look at properties guaranteed 1051 // to be preserved by the reformatting, namely that the opening 1052 // paren and the first expression are on the same line and that 1053 // each subsequent expression begins on the same line as the last 1054 // one ended (no line breaks after comma). 1055 func forceCompact(start Position, list []Expr, end Position) bool { 1056 if len(list) <= 1 { 1057 // The call or tuple will probably be compact anyway; don't force it. 1058 return false 1059 } 1060 1061 // If there are any named arguments or non-string, non-literal 1062 // arguments, cannot force compact mode. 1063 line := start.Line 1064 for _, x := range list { 1065 start, end := x.Span() 1066 if start.Line != line { 1067 return false 1068 } 1069 line = end.Line 1070 if !isSimpleExpression(&x) { 1071 return false 1072 } 1073 } 1074 return end.Line == line 1075 } 1076 1077 // forceMultiLine returns the setting for the ForceMultiLine field. 1078 func forceMultiLine(start Position, list []Expr, end Position) bool { 1079 if len(list) > 1 { 1080 // The call will be multiline anyway, because it has multiple elements. Don't force it. 1081 return false 1082 } 1083 1084 if len(list) == 0 { 1085 // Empty list: use position of brackets. 1086 return start.Line != end.Line 1087 } 1088 1089 // Single-element list. 1090 // Check whether opening bracket is on different line than beginning of 1091 // element, or closing bracket is on different line than end of element. 1092 elemStart, elemEnd := list[0].Span() 1093 return start.Line != elemStart.Line || end.Line != elemEnd.Line 1094 } 1095 1096 // forceMultiLineComprehension returns the setting for the ForceMultiLine field for a comprehension. 1097 func forceMultiLineComprehension(start Position, expr Expr, clauses []Expr, end Position) bool { 1098 // Return true if there's at least one line break between start, expr, each clause, and end 1099 exprStart, exprEnd := expr.Span() 1100 if start.Line != exprStart.Line { 1101 return true 1102 } 1103 previousEnd := exprEnd 1104 for _, clause := range clauses { 1105 clauseStart, clauseEnd := clause.Span() 1106 if previousEnd.Line != clauseStart.Line { 1107 return true 1108 } 1109 previousEnd = clauseEnd 1110 } 1111 return previousEnd.Line != end.Line 1112 } 1113 1114 // extractTrailingComments extracts trailing comments of an indented block starting with the first 1115 // comment line with indentation less than the block indentation. 1116 // The comments can either belong to CommentBlock statements or to the last non-comment statement 1117 // as After-comments. 1118 func extractTrailingComments(stmt Expr) []Expr { 1119 body := getLastBody(stmt) 1120 var comments []Expr 1121 if body != nil && len(*body) > 0 { 1122 // Get the current indentation level 1123 start, _ := (*body)[0].Span() 1124 indentation := start.LineRune 1125 1126 // Find the last non-comment statement 1127 lastNonCommentIndex := -1 1128 for i, stmt := range *body { 1129 if _, ok := stmt.(*CommentBlock); !ok { 1130 lastNonCommentIndex = i 1131 } 1132 } 1133 if lastNonCommentIndex == -1 { 1134 return comments 1135 } 1136 1137 // Iterate over the trailing comments, find the first comment line that's not indented enough, 1138 // dedent it and all the following comments. 1139 for i := lastNonCommentIndex; i < len(*body); i++ { 1140 stmt := (*body)[i] 1141 if comment := extractDedentedComment(stmt, indentation); comment != nil { 1142 // This comment and all the following CommentBlock statements are to be extracted. 1143 comments = append(comments, comment) 1144 comments = append(comments, (*body)[i+1:]...) 1145 *body = (*body)[:i+1] 1146 // If the current statement is a CommentBlock statement without any comment lines 1147 // it should be removed too. 1148 if i > lastNonCommentIndex && len(stmt.Comment().After) == 0 { 1149 *body = (*body)[:i] 1150 } 1151 } 1152 } 1153 } 1154 return comments 1155 } 1156 1157 // extractDedentedComment extract the first comment line from `stmt` which indentation is smaller 1158 // than `indentation`, and all following comment lines, and returns them in a newly created 1159 // CommentBlock statement. 1160 func extractDedentedComment(stmt Expr, indentation int) Expr { 1161 for i, line := range stmt.Comment().After { 1162 // line.Start.LineRune == 0 can't exist in parsed files, it indicates that the comment line 1163 // has been added by an AST modification. Don't take such lines into account. 1164 if line.Start.LineRune > 0 && line.Start.LineRune < indentation { 1165 // This and all the following lines should be dedented 1166 cb := &CommentBlock{ 1167 Start: line.Start, 1168 Comments: Comments{After: stmt.Comment().After[i:]}, 1169 } 1170 stmt.Comment().After = stmt.Comment().After[:i] 1171 return cb 1172 } 1173 } 1174 return nil 1175 } 1176 1177 // getLastBody returns the last body of a block statement (the only body for For- and DefStmt 1178 // objects, the last in a if-elif-else chain 1179 func getLastBody(stmt Expr) *[]Expr { 1180 switch block := stmt.(type) { 1181 case *DefStmt: 1182 return &block.Body 1183 case *ForStmt: 1184 return &block.Body 1185 case *IfStmt: 1186 if len(block.False) == 0 { 1187 return &block.True 1188 } else if len(block.False) == 1 { 1189 if next, ok := block.False[0].(*IfStmt); ok { 1190 // Recursively find the last block of the chain 1191 return getLastBody(next) 1192 } 1193 } 1194 return &block.False 1195 } 1196 return nil 1197 }