1/* 2 * Copyright (C) 2010 Google Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 /* This is the JSilver grammar fed into SableCC to generate the parser. */ 18 19// Java package of generated code. 20Package 21 com.google.clearsilver.jsilver.syntax; 22 23/***** Stage 1: The Lexer 24 * 25 * The lexer breaks the inbound text stream in to a sequence of tokens. 26 * 27 * SableCC will generate a wrapper class for each type of token. 28 * 29 * Anything outside of <?cs and ?> will be returned as a TData token. 30 * Anything in between (including) <?cs and ?> will be broken down 31 * into a finer grained set of tokens. 32 * 33 * For example: "Hello <?cs var:person.name ?>!" 34 * Results in: 35 * TData "Hello " 36 * TCsOpen "<?cs" 37 * TWhiteSpace " " 38 * TVar "var" 39 * TColon ":" 40 * TWord "person.name" 41 * TWhitspace " " 42 * TCsClose "?>" 43 * TData "!" 44 */ 45 46/* Constants to be used elsewhere. */ 47Helpers 48 49 // These aren't actually tokens, but can be used by tokens. Avoids the 50 // need for magic numbers in the token definitions. 51 alphanumeric = (['a' .. 'z'] | ['A' .. 'Z'] | ['0' .. '9']); 52 alpha = (['a' .. 'z'] | ['A' .. 'Z']); 53 all = [0 .. 0xFFFF]; 54 tab = 9; 55 cr = 13; 56 lf = 10; 57 whitespace = (tab | cr | lf | ' '); 58 59/* States of the lexer. */ 60States 61 62 content, // Anything outside of <?cs and ?>. 63 command, // ClearSilver command: "<?cs var:". 64 args, // Args to command: "some.variable=3 ?>" 65 comment; // Inside a <?cs # comment ?>. 66 67/* Tokens and state transitions. */ 68Tokens 69 70 // In the 'content' state, treat everything as a chunk of data, 71 // up to the sequence '<?cs '. 72 // Note, because the lexer has to read 5 characters '<?cs ' before 73 // knowing the definite state, it needs to be supplied a 74 // PushbackReader() with a buffer of at least 5. 75 {content} data = ( [all - '<'] 76 | '<' [all - '?'] 77 | '<?' [all - 'c'] 78 | '<?c' [all - 's'] 79 | '<?cs' [[[[all - ' '] - cr] - lf] - tab])+; 80 81 {comment} comment = ( [all - '?'] 82 | '?' [all - '>'] )+; // All up to ?> 83 84 // In the 'clearsilver' state, break each keyword, operator 85 // and symbol into it's own token. 86 {command} var = 'var'; 87 {command} lvar = 'lvar'; 88 {command} evar = 'evar'; 89 {command} uvar = 'uvar'; 90 {command} set = 'set'; 91 {command} if = 'if'; 92 {command} else_if = ('elif' | 'elseif'); 93 {command} else = 'else'; 94 {command} with = 'with'; 95 {command} escape = 'escape'; 96 {command} autoescape = 'autoescape'; 97 {command} loop = 'loop'; 98 {command} each = 'each'; 99 {command} alt = 'alt'; 100 {command} name = 'name'; 101 {command} def = 'def'; 102 {command} call = 'call'; 103 {command} include = 'include'; 104 {command} linclude = 'linclude'; 105 {command} content_type = 'content-type'; 106 {command} inline = 'inline'; 107 108 {args} comma = ','; 109 {args} bang = '!'; 110 {args} assignment = '='; 111 {args} eq = '=='; 112 {args} ne = '!='; 113 {args} lt = '<'; 114 {args} gt = '>'; 115 {args} lte = '<='; 116 {args} gte = '>='; 117 {args} and = '&&'; 118 {args} or = '||'; 119 {args} string = ('"' [all - '"']* '"' | ''' [all - ''']* '''); 120 {args} hash = '#'; 121 {args} plus = '+'; 122 {args} minus = '-'; 123 {args} star = '*'; 124 {args} percent = '%'; 125 {args} bracket_open = '['; 126 {args} bracket_close = ']'; 127 {args} paren_open = '('; 128 {args} paren_close = ')'; 129 {args} dot = '.'; 130 {args} dollar = '$'; 131 {args} question = '?'; 132 {args} dec_number = (['0' .. '9'])+; 133 {args} hex_number = ('0x' | '0X') (['0' .. '9'] | ['a' .. 'f'] | ['A' .. 'F'])+; 134 {args} word = (alphanumeric | '_')+; 135 {args} arg_whitespace = whitespace+; 136 137 // Tokens that are valid in multiple states 138 {command, args} slash = '/'; // means divide or end command. 139 140 // State transitions. 141 {content->command} cs_open = '<?cs' whitespace+; 142 {command->comment} comment_start = '#'; 143 {command->args} command_delimiter = ':' | whitespace; 144 {command->args} hard_delimiter = '!' | whitespace; 145 {command->content, args->content, comment->content} cs_close = whitespace* '?>'; 146 147/***** Stage 2: The Parser 148 * 149 * Below is a BNF-like grammar of how the tokens are assembled. 150 * The generate parser will read the token stream and build a 151 * tree of nodes representing the structure. 152 * 153 * This is the Concrete Syntax Tree (CST). 154 * 155 * Though this provides access to the underlying syntax tree, the 156 * resulting tree would be quite tricky to work with from code as 157 * it's heavily loaded with syntax specifics and parser tricks... 158 * 159 * So, the CST also contains transformation rules ({->x}) to 160 * convert it into a much simpler Abstract Syntax Tree (AST), 161 * which is defined in stage 3. 162 */ 163 164/* Tokens from the lexer that the parser doesn't care about. */ 165Ignored Tokens 166 167 arg_whitespace; 168 169/* Concrete syntax tree. */ 170Productions 171 172 // Overall template structure... 173 174 grammar {->command} 175 = commands 176 {->commands.command} 177 ; 178 179 commands {->command} 180 = {none} 181 {->New command.noop()} 182 | {one} command 183 {->command.command} 184 | {many} command [more]:command+ 185 {->New command.multiple([command.command, more.command])} 186 ; 187 188 command {->command} 189 190 = {data} data 191 // Anything outside of <?cs ?> tag 192 {->New command.data(data)} 193 194 | {comment} cs_open comment_start comment? cs_close 195 // <?cs # comment ?> 196 {->New command.comment(New position.cs_open(cs_open),comment)} 197 198 | {var} cs_open var command_delimiter expression_list cs_close 199 // <?cs var:x ?> 200 {->New command.var( 201 New position.cs_open(cs_open), 202 New expression.sequence([expression_list.expression]))} 203 204 | {lvar} cs_open lvar command_delimiter expression_list cs_close 205 // <?cs lvar:x ?> 206 {->New command.lvar( 207 New position.cs_open(cs_open), 208 New expression.sequence([expression_list.expression]))} 209 210 | {evar} cs_open evar command_delimiter expression_list cs_close 211 // <?cs evar:x ?> 212 {->New command.evar( 213 New position.cs_open(cs_open), 214 New expression.sequence([expression_list.expression]))} 215 216 | {uvar} cs_open uvar command_delimiter expression_list cs_close 217 // <?cs uvar:x ?> 218 {->New command.uvar( 219 New position.cs_open(cs_open), 220 New expression.sequence([expression_list.expression]))} 221 222 | {set} cs_open set command_delimiter variable assignment expression cs_close 223 // <?cs set:x = y ?> 224 {->New command.set( 225 New position.cs_open(cs_open), 226 variable.variable, 227 expression.expression)} 228 229 | {name} cs_open name command_delimiter variable cs_close 230 // <?cs name:x ?> 231 {->New command.name( 232 New position.cs_open(cs_open), 233 variable.variable)} 234 235 | {escape} cs_open escape command_delimiter expression cs_close 236 commands 237 [i1]:cs_open slash [i3]:escape [i2]:cs_close 238 // <?cs escape:"html" ?>...<?cs /escape?> 239 {->New command.escape( 240 New position.cs_open(cs_open), 241 expression.expression, 242 commands.command)} 243 244 | {autoescape} cs_open autoescape command_delimiter expression cs_close 245 commands 246 [i1]:cs_open slash [i3]:autoescape [i2]:cs_close 247 // <?cs autoescape:"html" ?>...<?cs /autoescape?> 248 {->New command.autoescape( 249 New position.cs_open(cs_open), 250 expression.expression, 251 commands.command)} 252 253 | {with} cs_open with command_delimiter variable assignment expression cs_close 254 commands 255 [i1]:cs_open slash [i3]:with [i2]:cs_close 256 // <?cs with:x=y ?>...<?cs /with?> 257 {->New command.with( 258 New position.cs_open(cs_open), 259 variable.variable, 260 expression.expression, 261 commands.command)} 262 263 | {loop_to} cs_open loop command_delimiter variable assignment expression cs_close 264 commands 265 [i1]:cs_open slash [i3]:loop [i2]:cs_close 266 // <?cs loop:x=20 ?>...<?cs /loop ?> 267 {->New command.loop_to( 268 New position.cs_open(cs_open), 269 variable.variable, 270 expression.expression, 271 commands.command)} 272 273 | {loop} cs_open loop command_delimiter variable assignment 274 [start]:expression comma [end]:expression cs_close 275 commands 276 [i1]:cs_open slash [i3]:loop [i2]:cs_close 277 // <?cs loop:x=1,20 ?>...<?cs /loop ?> 278 {->New command.loop( 279 New position.cs_open(cs_open), 280 variable.variable, 281 start.expression, 282 end.expression, 283 commands.command)} 284 285 | {loop_inc} cs_open loop command_delimiter variable assignment 286 [start]:expression comma 287 [end]:expression [i3]:comma [increment]:expression cs_close 288 commands [i1]:cs_open slash [i4]:loop [i2]:cs_close 289 // <?cs loop:x=1,20,5 ?>...<?cs /loop ?> 290 {->New command.loop_inc( 291 New position.cs_open(cs_open), 292 variable.variable, 293 start.expression, 294 end.expression, 295 increment.expression, 296 commands.command)} 297 298 | {each} cs_open each command_delimiter variable assignment expression cs_close 299 commands 300 [i1]:cs_open slash [i3]:each [i2]:cs_close 301 // <?cs each:x=some.thing ?>...<?cs /each ?> 302 {->New command.each( 303 New position.cs_open(cs_open), 304 variable.variable, 305 expression.expression, 306 commands.command)} 307 308 | {alt} cs_open alt command_delimiter expression cs_close 309 commands 310 [i1]:cs_open slash [i3]:alt [i2]:cs_close 311 // <?cs alt:some.thing ?>...<?cs /alt ?> 312 {->New command.alt( 313 New position.cs_open(cs_open), 314 expression.expression, 315 commands.command)} 316 317 | {def} cs_open def command_delimiter multipart_word paren_open variable_list? 318 paren_close cs_close commands 319 [i1]:cs_open slash [i3]:def [i2]:cs_close 320 // <?cs def:some.macro(arg,arg) ?>...<?cs /def ?> 321 {->New command.def( 322 New position.cs_open(cs_open), 323 [multipart_word.word], 324 [variable_list.variable], 325 commands.command)} 326 327 | {call} cs_open call command_delimiter multipart_word paren_open expression_list? 328 paren_close cs_close 329 // <?cs call:some.macro(arg,arg) ?> 330 {->New command.call( 331 New position.cs_open(cs_open), 332 [multipart_word.word], 333 [expression_list.expression])} 334 335 | {if} if_block 336 {->if_block.command} 337 338 | {include} cs_open include command_delimiter expression cs_close 339 // <?cs include:x ?> 340 {->New command.include( 341 New position.cs_open(cs_open), 342 expression.expression)} 343 344 | {hard_include} cs_open include hard_delimiter expression cs_close 345 // <?cs include!x ?> 346 {->New command.hard_include( 347 New position.cs_open(cs_open), 348 expression.expression)} 349 350 | {linclude} cs_open linclude command_delimiter expression cs_close 351 // <?cs linclude:x ?> 352 {->New command.linclude( 353 New position.cs_open(cs_open), 354 expression.expression)} 355 356 | {hard_linclude} cs_open linclude hard_delimiter expression cs_close 357 // <?cs linclude!x ?> 358 {->New command.hard_linclude( 359 New position.cs_open(cs_open), 360 expression.expression)} 361 362 | {content_type} cs_open content_type command_delimiter string cs_close 363 // <?cs content-type:"html" ?> 364 {->New command.content_type( 365 New position.cs_open(cs_open), 366 string)} 367 368 | {inline} cs_open inline cs_close 369 commands 370 [i1]:cs_open slash [i3]:inline [i2]:cs_close 371 // <?cs inline ?>...<?cs /inline?> 372 {->New command.inline( 373 New position.cs_open(cs_open), 374 commands.command)} 375 376 ; 377 378 multipart_word {->word*} 379 = {bit} word 380 {->[word]} 381 | {m} multipart_word dot word 382 {->[multipart_word.word, word]} 383 ; 384 385 variable_list {->variable*} 386 = {single} variable 387 {->[variable.variable]} 388 | {multiple} variable_list comma variable 389 {->[variable_list.variable, variable.variable]} 390 ; 391 392 expression_list {->expression*} 393 = {single} expression 394 {->[expression.expression]} 395 | {multiple} expression_list comma expression 396 {->[expression_list.expression, expression.expression]} 397 ; 398 399 // If/ElseIf/Else block... 400 401 if_block {->command} 402 = cs_open if command_delimiter expression cs_close 403 commands 404 else_if_block 405 // <?cs if:x.y ?> (commands, then optional else_if_block) 406 {->New command.if( 407 New position.cs_open(cs_open), 408 expression.expression, 409 commands.command, 410 else_if_block.command)} 411 ; 412 413 // ElseIf statements get transformed into nested if/else blocks to simplify 414 // final AST. 415 else_if_block {->command} 416 = {present} cs_open else_if command_delimiter expression cs_close 417 commands 418 else_if_block 419 // <?cs elif:x.y ?> (recurses) 420 {->New command.if( 421 New position.cs_open(cs_open), 422 expression.expression, 423 commands.command, 424 else_if_block.command)} 425 | {missing} else_block 426 {->else_block.command} 427 ; 428 429 else_block {->command} 430 = {present} cs_open else cs_close 431 commands 432 end_if_block 433 // <?cs else ?> (followed by end_if_block) 434 {->commands.command} 435 | {skip} end_if_block 436 {->New command.noop()} 437 ; 438 439 end_if_block 440 = cs_open slash if cs_close 441 // <?cs /if ?> 442 ; 443 444 // Expression language... 445 446 // The multiple levels allow the parser to build a tree based on operator 447 // precedence. The higher the level in the tree, the lower the precedence. 448 449 expression {->expression} 450 = {or} [left]:expression or [right]:and_expression // x.y || a.b 451 {->New expression.or(left.expression, right.expression)} 452 | {and_expression} [value]:and_expression // x.y 453 {->value.expression} 454 ; 455 456 and_expression {->expression} 457 = {and} [left]:and_expression and [right]:equality // x.y && a.b 458 {->New expression.and(left.expression, right.expression)} 459 | {equality} [value]:equality // x.y 460 {->value.expression} 461 ; 462 463 equality {->expression} 464 = {eq} [left]:equality eq [right]:comparison // x.y == a.b 465 {->New expression.eq(left.expression, right.expression)} 466 | {ne} [left]:equality ne [right]:comparison // x.y != a.b 467 {->New expression.ne(left.expression, right.expression)} 468 | {comparison} [value]:comparison // x.y 469 {->value.expression} 470 ; 471 472 comparison {->expression} 473 = {lt} [left]:comparison lt [right]:add_subtract // x.y < a.b 474 {->New expression.lt(left.expression, right.expression)} 475 | {gt} [left]:comparison gt [right]:add_subtract // x.y > a.b 476 {->New expression.gt(left.expression, right.expression)} 477 | {lte} [left]:comparison lte [right]:add_subtract // x.y <= a.b 478 {->New expression.lte(left.expression, right.expression)} 479 | {gte} [left]:comparison gte [right]:add_subtract // x.y >= a.b 480 {->New expression.gte(left.expression, right.expression)} 481 | {add_subtract} [value]:add_subtract // x.y 482 {->value.expression} 483 ; 484 485 add_subtract {->expression} 486 = {add} [left]:add_subtract plus [right]:factor // x.y + a.b 487 {->New expression.add(left.expression, right.expression)} 488 | {subtract} [left]:add_subtract minus [right]:factor // x.y - a.b 489 {->New expression.subtract(left.expression, right.expression)} 490 | {factor} [value]:factor // x.y 491 {->value.expression} 492 ; 493 494 factor {->expression} 495 = {multiply} [left]:factor star [right]:value // x.y * a.b 496 {->New expression.multiply(left.expression, right.expression)} 497 | {divide} [left]:factor slash [right]:value // x.y / a.b 498 {->New expression.divide(left.expression, right.expression)} 499 | {modulo} [left]:factor percent [right]:value // x.y % a.b 500 {->New expression.modulo(left.expression, right.expression)} 501 | {value} value // x.y 502 {->value.expression} 503 ; 504 505 value {->expression} 506 = {variable} variable // x.y 507 {->New expression.variable(variable.variable)} 508 | {string} string // "hello" 509 {->New expression.string(string)} 510 | {number} number // 123 511 {->number.expression} 512 | {forced_number} hash value // #123 or #some.var 513 {->New expression.numeric(value.expression)} 514 | {not} bang value // !x.y 515 {->New expression.not(value.expression)} 516 | {exists} question value // ?x.y 517 {->New expression.exists(value.expression)} 518 | {parens} paren_open expression_list paren_close // (x.y, a.b, d.e) 519 {->New expression.sequence([expression_list.expression])} 520 | {function} [name]:variable paren_open 521 expression_list? paren_close // a.b(x, y) 522 {->New expression.function( 523 name.variable,[expression_list.expression])} 524 ; 525 526 variable {->variable} 527 = {name} dollar? word 528 {->New variable.name(word)} 529 | {dec_number} dollar dec_number 530 {->New variable.dec_number(dec_number)} 531 | {hex_number} dollar hex_number 532 {->New variable.hex_number(hex_number)} 533 | {descend_name} variable dot word 534 {->New variable.descend( 535 variable.variable, New variable.name(word))} 536 | {descend_dec_number} variable dot dec_number 537 {->New variable.descend( 538 variable.variable, New variable.dec_number(dec_number))} 539 | {descend_hex_number} variable dot hex_number 540 {->New variable.descend( 541 variable.variable, New variable.hex_number(hex_number))} 542 | {expand} variable bracket_open expression bracket_close 543 {->New variable.expand( 544 variable.variable, expression.expression)} 545 ; 546 547 number {->expression} 548 = {unsigned} digits 549 {->digits.expression} 550 | {positive} plus digits 551 {->digits.expression} 552 | {negative} minus digits 553 {->New expression.negative(digits.expression)} 554 ; 555 556 digits {->expression} 557 = {decimal} dec_number 558 {->New expression.decimal(dec_number)} 559 | {hex} hex_number 560 {->New expression.hex(hex_number)} 561 ; 562 563 564/***** Stage 3: The Abstract Syntax Tree 565 * 566 * This is the resulting model that will be generated by the parser. 567 * 568 * It is represented in Java by a strongly typed node tree (the 569 * classes are code generated by SableCC). These can be interrogated 570 * using getter methods, or processed by passing a visitor to the 571 * tree. 572 * 573 * The Abstract Syntax Tree definition below is the only thing 574 * the Java application need to worry about. All previous definitions 575 * in this file are taken care of by the generated parser. 576 * 577 * Example input: 578 * Hello <?cs var:user.name ?>! 579 * <?cs if:user.age >= 90 ?> 580 * You're way old. 581 * <?cs elif:user.age >= 21 ?> 582 * You're about the right age. 583 * <?cs else ?> 584 * You're too young. 585 * <?cs /if ?> 586 * Seeya! 587 * 588 * Results in the tree: 589 * AMultipleCommand (start) 590 * ADataCommand (command) 591 * TData (data) "Hello " 592 * AVarCommand (command) 593 * AVariableExpression (expression) 594 * TWord "user.name" (name) 595 * ADataCommand (command) 596 * TData "!\n" (data) 597 * AIfCommand (command) 598 * AGteExpression (expression) 599 * AVariableExpression (left) 600 * TWord "user.age" (name) 601 * ADecimalExpresion (right) 602 * TDecNumber "90" (value) 603 * ADataCommand (block) 604 * TData (data) "\nYou're way old.\n" 605 * AGteCommand (otherwise) 606 * AEqExpression (expression) 607 * AVariableExpression (left) 608 * TWord "user.age" (name) 609 * ADecimalExpresion (right) 610 * TDecNumber "21" (value) 611 * ADataCommand (block) 612 * TData (data) "\nYou're about the right age.\n" 613 * ADataCommand (otherwise) 614 * TData (data) "\nYou're too young.\n" 615 * ADataCommand (command) 616 * TData (data) "\nSeeya!\n" 617 * 618 * Although not strictly necessary, tokens are prefixed with 'T.' in 619 * the grammar so they stand out from the rest of the other rules. 620 */ 621Abstract Syntax Tree 622 623 command = {multiple} command* // Sequence of commands 624 | {comment} position T.comment? // Contents of <?cs # comment ?> 625 | {data} T.data // Any data outside of <?cs ?> 626 | {var} position expression // var:x statement 627 | {lvar} position expression // lvar:x statement 628 | {evar} position expression // evar:x statement 629 | {uvar} position expression // uvar:x statement 630 | {set} position variable expression // set:x=y statement 631 | {name} position variable // name:x statement 632 | {escape} position expression // escape:x statement 633 command // ... commands in context 634 | {autoescape} position expression // autoescape:x statement 635 command // ... commands in context 636 | {with} position variable expression // with:x=y statement 637 command // ... commands in context 638 | {loop_to} position variable // loop:x=10 statement 639 expression // ... value to end at 640 command // ... commands in loop 641 | {loop} position variable // loop:x=1,10 statement 642 [start]:expression // ... value to start at 643 [end]:expression // ... value to end at 644 command // ... commands in loop 645 | {loop_inc} position variable // loop:x=1,10,2 statement 646 [start]:expression // ... value to start at 647 [end]:expression // ... value to end at 648 [increment]:expression // . value to increment by 649 command // ... commands in loop 650 | {each} position variable expression // each:x=y statement 651 command // ... commands in loop 652 | {def} position [macro]:T.word* // def:some_macro statement 653 [arguments]:variable* // ... arguments 654 command // ... commands to execute 655 | {call} position [macro]:T.word* // call:some_macro statement 656 [arguments]:expression* // ... arguments 657 | {if} position expression // if:x statement 658 [block]:command // ... commands if true 659 [otherwise]:command // ... commands if false 660 | {alt} position expression command // alt:x statement 661 | {include} position expression // include:x statement 662 | {hard_include} position expression // include!x statement 663 | {linclude} position expression // linclude:x statement 664 | {hard_linclude} position expression // linclude!x statement 665 | {content_type} position string // content-type:x statement 666 | {inline} position command // inline commands 667 | {noop} // No operation 668 ; 669 670 position = {cs_open} T.cs_open // We retain the <?cs token in the AST 671 ; // as a convenient way to get the position 672 // of the start of the command. 673 674 expression = {string} [value]:T.string // "hello" 675 | {numeric} expression // #something 676 | {decimal} [value]:T.dec_number // 123 677 | {hex} [value]:T.hex_number // 0x1BF 678 | {variable} variable // some.thing[1] 679 | {function} [name]:variable [args]:expression* // a.b(x, y) 680 | {sequence} [args]:expression* // x, y, z 681 | {negative} expression // -x 682 | {not} expression // !x 683 | {exists} expression // ?x 684 | {comma} [left]:expression [right]:expression // x, y 685 | {eq} [left]:expression [right]:expression // x == y 686 | {numeric_eq} [left]:expression [right]:expression // x == y (numeric) 687 | {ne} [left]:expression [right]:expression // x != y 688 | {numeric_ne} [left]:expression [right]:expression // x != y (numeric) 689 | {lt} [left]:expression [right]:expression // x < y 690 | {gt} [left]:expression [right]:expression // x > y 691 | {lte} [left]:expression [right]:expression // x <= y 692 | {gte} [left]:expression [right]:expression // x >= y 693 | {and} [left]:expression [right]:expression // x && y 694 | {or} [left]:expression [right]:expression // x || y 695 | {add} [left]:expression [right]:expression // x + y 696 | {numeric_add} [left]:expression [right]:expression // x + y (numeric) 697 | {subtract} [left]:expression [right]:expression // x - y 698 | {multiply} [left]:expression [right]:expression // x * y 699 | {divide} [left]:expression [right]:expression // x / y 700 | {modulo} [left]:expression [right]:expression // x % y 701 | {noop} // No operation 702 ; 703 704 variable = {name} T.word // something 705 | {dec_number} T.dec_number // 2 706 | {hex_number} T.hex_number // 0xA1 707 | {descend} [parent]:variable [child]:variable // foo.bar 708 | {expand} [parent]:variable [child]:expression // foo["bar"] 709 ; 710