123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218 |
- // 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
- string *StringExpr
- strings []*StringExpr
- ifstmt *IfStmt
- loadarg *struct{from Ident; to Ident}
- loadargs []*struct{from Ident; to Ident}
- // 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> _NUMBER // 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> _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 <expr> argument
- %type <exprs> arguments
- %type <exprs> arguments_opt
- %type <expr> parameter
- %type <exprs> parameters
- %type <exprs> parameters_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 <exprs> exprs
- %type <exprs> exprs_opt
- %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 <expr> keyvalue
- %type <exprs> keyvalues
- %type <exprs> keyvalues_no_comma
- %type <string> string
- %type <strings> strings
- %type <exprs> suite
- %type <exprs> comments
- %type <loadarg> load_argument
- %type <loadargs> load_arguments
- // 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 '.' '[' '('
- %right _UNARY
- %left _STRING
- %%
- // 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
- {
- $$ = $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
- }
- }
- }
- block_stmt:
- _DEF _IDENT '(' parameters_opt ')' ':' suite
- {
- $$ = &DefStmt{
- Function: Function{
- StartPos: $1,
- Params: $4,
- Body: $7,
- },
- Name: $<tok>2,
- ColonPos: $6,
- ForceCompact: forceCompact($3, $4, $5),
- ForceMultiLine: forceMultiLine($3, $4, $5),
- }
- $<lastStmt>$ = $<lastStmt>7
- }
- | _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
- | primary_expr '.' _IDENT
- {
- $$ = &DotExpr{
- X: $1,
- Dot: $2,
- NamePos: $3,
- Name: $<tok>3,
- }
- }
- | _LOAD '(' string ',' load_arguments comma_opt ')'
- {
- load := &LoadStmt{
- Load: $1,
- Module: $3,
- Rparen: End{Pos: $7},
- ForceCompact: $1.Line == $7.Line,
- }
- for _, arg := range $5 {
- 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,
- }
- }
- | strings %prec ShiftInstead
- {
- if len($1) == 1 {
- $$ = $1[0]
- break
- }
- $$ = $1[0]
- for _, x := range $1[1:] {
- _, end := $$.Span()
- $$ = binary($$, end, "+", x)
- }
- }
- | '[' 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 '}'
- {
- $$ = &DictExpr{
- Start: $1,
- List: $2,
- End: End{Pos: $3},
- ForceMultiLine: forceMultiLine($1, $2, $3),
- }
- }
- | '{' tests comma_opt '}' // TODO: remove, not supported
- {
- $$ = &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 comma_opt
- {
- $$ = $1
- }
- arguments:
- argument
- {
- $$ = []Expr{$1}
- }
- | arguments ',' 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:
- parameter
- {
- $$ = []Expr{$1}
- }
- | parameters ',' parameter
- {
- $$ = 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)
- }
- expr:
- test
- | 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
- exprs:
- expr
- {
- $$ = []Expr{$1}
- }
- | exprs ',' expr
- {
- $$ = append($1, $3)
- }
- exprs_opt:
- {
- $$ = nil
- }
- | exprs comma_opt
- {
- $$ = $1
- }
- test:
- primary_expr
- | _LAMBDA exprs_opt ':' expr // TODO: remove, not supported
- {
- $$ = &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 ',' test
- {
- $$ = append($1, $3)
- }
- test_opt:
- {
- $$ = nil
- }
- | test
- tests_opt:
- {
- $$, $<comma>$ = nil, Position{}
- }
- | tests comma_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{}
- }
- | ','
- keyvalue:
- test ':' test {
- $$ = &KeyValueExpr{
- Key: $1,
- Colon: $2,
- Value: $3,
- }
- }
- keyvalues_no_comma:
- keyvalue
- {
- $$ = []Expr{$1}
- }
- | keyvalues_no_comma ',' keyvalue
- {
- $$ = append($1, $3)
- }
- keyvalues:
- {
- $$ = nil
- }
- | keyvalues_no_comma
- {
- $$ = $1
- }
- | keyvalues_no_comma ','
- {
- $$ = $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,
- }
- }
- strings:
- string
- {
- $$ = []*StringExpr{$1}
- }
- | strings string
- {
- $$ = append($1, $2)
- }
- ident:
- _IDENT
- {
- $$ = &Ident{NamePos: $1, Name: $<tok>1}
- }
- number:
- _NUMBER
- {
- $$ = &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,
- }
- }
- // 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
- }
|