1'use strict'; 2 3function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; _setPrototypeOf(subClass, superClass); } 4function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } 5var lexer = require('./lexer'); 6var nodes = require('./nodes'); 7var Obj = require('./object').Obj; 8var lib = require('./lib'); 9var Parser = /*#__PURE__*/function (_Obj) { 10 _inheritsLoose(Parser, _Obj); 11 function Parser() { 12 return _Obj.apply(this, arguments) || this; 13 } 14 var _proto = Parser.prototype; 15 _proto.init = function init(tokens) { 16 this.tokens = tokens; 17 this.peeked = null; 18 this.breakOnBlocks = null; 19 this.dropLeadingWhitespace = false; 20 this.extensions = []; 21 }; 22 _proto.nextToken = function nextToken(withWhitespace) { 23 var tok; 24 if (this.peeked) { 25 if (!withWhitespace && this.peeked.type === lexer.TOKEN_WHITESPACE) { 26 this.peeked = null; 27 } else { 28 tok = this.peeked; 29 this.peeked = null; 30 return tok; 31 } 32 } 33 tok = this.tokens.nextToken(); 34 if (!withWhitespace) { 35 while (tok && tok.type === lexer.TOKEN_WHITESPACE) { 36 tok = this.tokens.nextToken(); 37 } 38 } 39 return tok; 40 }; 41 _proto.peekToken = function peekToken() { 42 this.peeked = this.peeked || this.nextToken(); 43 return this.peeked; 44 }; 45 _proto.pushToken = function pushToken(tok) { 46 if (this.peeked) { 47 throw new Error('pushToken: can only push one token on between reads'); 48 } 49 this.peeked = tok; 50 }; 51 _proto.error = function error(msg, lineno, colno) { 52 if (lineno === undefined || colno === undefined) { 53 var tok = this.peekToken() || {}; 54 lineno = tok.lineno; 55 colno = tok.colno; 56 } 57 if (lineno !== undefined) { 58 lineno += 1; 59 } 60 if (colno !== undefined) { 61 colno += 1; 62 } 63 return new lib.TemplateError(msg, lineno, colno); 64 }; 65 _proto.fail = function fail(msg, lineno, colno) { 66 throw this.error(msg, lineno, colno); 67 }; 68 _proto.skip = function skip(type) { 69 var tok = this.nextToken(); 70 if (!tok || tok.type !== type) { 71 this.pushToken(tok); 72 return false; 73 } 74 return true; 75 }; 76 _proto.expect = function expect(type) { 77 var tok = this.nextToken(); 78 if (tok.type !== type) { 79 this.fail('expected ' + type + ', got ' + tok.type, tok.lineno, tok.colno); 80 } 81 return tok; 82 }; 83 _proto.skipValue = function skipValue(type, val) { 84 var tok = this.nextToken(); 85 if (!tok || tok.type !== type || tok.value !== val) { 86 this.pushToken(tok); 87 return false; 88 } 89 return true; 90 }; 91 _proto.skipSymbol = function skipSymbol(val) { 92 return this.skipValue(lexer.TOKEN_SYMBOL, val); 93 }; 94 _proto.advanceAfterBlockEnd = function advanceAfterBlockEnd(name) { 95 var tok; 96 if (!name) { 97 tok = this.peekToken(); 98 if (!tok) { 99 this.fail('unexpected end of file'); 100 } 101 if (tok.type !== lexer.TOKEN_SYMBOL) { 102 this.fail('advanceAfterBlockEnd: expected symbol token or ' + 'explicit name to be passed'); 103 } 104 name = this.nextToken().value; 105 } 106 tok = this.nextToken(); 107 if (tok && tok.type === lexer.TOKEN_BLOCK_END) { 108 if (tok.value.charAt(0) === '-') { 109 this.dropLeadingWhitespace = true; 110 } 111 } else { 112 this.fail('expected block end in ' + name + ' statement'); 113 } 114 return tok; 115 }; 116 _proto.advanceAfterVariableEnd = function advanceAfterVariableEnd() { 117 var tok = this.nextToken(); 118 if (tok && tok.type === lexer.TOKEN_VARIABLE_END) { 119 this.dropLeadingWhitespace = tok.value.charAt(tok.value.length - this.tokens.tags.VARIABLE_END.length - 1) === '-'; 120 } else { 121 this.pushToken(tok); 122 this.fail('expected variable end'); 123 } 124 }; 125 _proto.parseFor = function parseFor() { 126 var forTok = this.peekToken(); 127 var node; 128 var endBlock; 129 if (this.skipSymbol('for')) { 130 node = new nodes.For(forTok.lineno, forTok.colno); 131 endBlock = 'endfor'; 132 } else if (this.skipSymbol('asyncEach')) { 133 node = new nodes.AsyncEach(forTok.lineno, forTok.colno); 134 endBlock = 'endeach'; 135 } else if (this.skipSymbol('asyncAll')) { 136 node = new nodes.AsyncAll(forTok.lineno, forTok.colno); 137 endBlock = 'endall'; 138 } else { 139 this.fail('parseFor: expected for{Async}', forTok.lineno, forTok.colno); 140 } 141 node.name = this.parsePrimary(); 142 if (!(node.name instanceof nodes.Symbol)) { 143 this.fail('parseFor: variable name expected for loop'); 144 } 145 var type = this.peekToken().type; 146 if (type === lexer.TOKEN_COMMA) { 147 // key/value iteration 148 var key = node.name; 149 node.name = new nodes.Array(key.lineno, key.colno); 150 node.name.addChild(key); 151 while (this.skip(lexer.TOKEN_COMMA)) { 152 var prim = this.parsePrimary(); 153 node.name.addChild(prim); 154 } 155 } 156 if (!this.skipSymbol('in')) { 157 this.fail('parseFor: expected "in" keyword for loop', forTok.lineno, forTok.colno); 158 } 159 node.arr = this.parseExpression(); 160 this.advanceAfterBlockEnd(forTok.value); 161 node.body = this.parseUntilBlocks(endBlock, 'else'); 162 if (this.skipSymbol('else')) { 163 this.advanceAfterBlockEnd('else'); 164 node.else_ = this.parseUntilBlocks(endBlock); 165 } 166 this.advanceAfterBlockEnd(); 167 return node; 168 }; 169 _proto.parseMacro = function parseMacro() { 170 var macroTok = this.peekToken(); 171 if (!this.skipSymbol('macro')) { 172 this.fail('expected macro'); 173 } 174 var name = this.parsePrimary(true); 175 var args = this.parseSignature(); 176 var node = new nodes.Macro(macroTok.lineno, macroTok.colno, name, args); 177 this.advanceAfterBlockEnd(macroTok.value); 178 node.body = this.parseUntilBlocks('endmacro'); 179 this.advanceAfterBlockEnd(); 180 return node; 181 }; 182 _proto.parseCall = function parseCall() { 183 // a call block is parsed as a normal FunCall, but with an added 184 // 'caller' kwarg which is a Caller node. 185 var callTok = this.peekToken(); 186 if (!this.skipSymbol('call')) { 187 this.fail('expected call'); 188 } 189 var callerArgs = this.parseSignature(true) || new nodes.NodeList(); 190 var macroCall = this.parsePrimary(); 191 this.advanceAfterBlockEnd(callTok.value); 192 var body = this.parseUntilBlocks('endcall'); 193 this.advanceAfterBlockEnd(); 194 var callerName = new nodes.Symbol(callTok.lineno, callTok.colno, 'caller'); 195 var callerNode = new nodes.Caller(callTok.lineno, callTok.colno, callerName, callerArgs, body); 196 197 // add the additional caller kwarg, adding kwargs if necessary 198 var args = macroCall.args.children; 199 if (!(args[args.length - 1] instanceof nodes.KeywordArgs)) { 200 args.push(new nodes.KeywordArgs()); 201 } 202 var kwargs = args[args.length - 1]; 203 kwargs.addChild(new nodes.Pair(callTok.lineno, callTok.colno, callerName, callerNode)); 204 return new nodes.Output(callTok.lineno, callTok.colno, [macroCall]); 205 }; 206 _proto.parseWithContext = function parseWithContext() { 207 var tok = this.peekToken(); 208 var withContext = null; 209 if (this.skipSymbol('with')) { 210 withContext = true; 211 } else if (this.skipSymbol('without')) { 212 withContext = false; 213 } 214 if (withContext !== null) { 215 if (!this.skipSymbol('context')) { 216 this.fail('parseFrom: expected context after with/without', tok.lineno, tok.colno); 217 } 218 } 219 return withContext; 220 }; 221 _proto.parseImport = function parseImport() { 222 var importTok = this.peekToken(); 223 if (!this.skipSymbol('import')) { 224 this.fail('parseImport: expected import', importTok.lineno, importTok.colno); 225 } 226 var template = this.parseExpression(); 227 if (!this.skipSymbol('as')) { 228 this.fail('parseImport: expected "as" keyword', importTok.lineno, importTok.colno); 229 } 230 var target = this.parseExpression(); 231 var withContext = this.parseWithContext(); 232 var node = new nodes.Import(importTok.lineno, importTok.colno, template, target, withContext); 233 this.advanceAfterBlockEnd(importTok.value); 234 return node; 235 }; 236 _proto.parseFrom = function parseFrom() { 237 var fromTok = this.peekToken(); 238 if (!this.skipSymbol('from')) { 239 this.fail('parseFrom: expected from'); 240 } 241 var template = this.parseExpression(); 242 if (!this.skipSymbol('import')) { 243 this.fail('parseFrom: expected import', fromTok.lineno, fromTok.colno); 244 } 245 var names = new nodes.NodeList(); 246 var withContext; 247 while (1) { 248 // eslint-disable-line no-constant-condition 249 var nextTok = this.peekToken(); 250 if (nextTok.type === lexer.TOKEN_BLOCK_END) { 251 if (!names.children.length) { 252 this.fail('parseFrom: Expected at least one import name', fromTok.lineno, fromTok.colno); 253 } 254 255 // Since we are manually advancing past the block end, 256 // need to keep track of whitespace control (normally 257 // this is done in `advanceAfterBlockEnd` 258 if (nextTok.value.charAt(0) === '-') { 259 this.dropLeadingWhitespace = true; 260 } 261 this.nextToken(); 262 break; 263 } 264 if (names.children.length > 0 && !this.skip(lexer.TOKEN_COMMA)) { 265 this.fail('parseFrom: expected comma', fromTok.lineno, fromTok.colno); 266 } 267 var name = this.parsePrimary(); 268 if (name.value.charAt(0) === '_') { 269 this.fail('parseFrom: names starting with an underscore cannot be imported', name.lineno, name.colno); 270 } 271 if (this.skipSymbol('as')) { 272 var alias = this.parsePrimary(); 273 names.addChild(new nodes.Pair(name.lineno, name.colno, name, alias)); 274 } else { 275 names.addChild(name); 276 } 277 withContext = this.parseWithContext(); 278 } 279 return new nodes.FromImport(fromTok.lineno, fromTok.colno, template, names, withContext); 280 }; 281 _proto.parseBlock = function parseBlock() { 282 var tag = this.peekToken(); 283 if (!this.skipSymbol('block')) { 284 this.fail('parseBlock: expected block', tag.lineno, tag.colno); 285 } 286 var node = new nodes.Block(tag.lineno, tag.colno); 287 node.name = this.parsePrimary(); 288 if (!(node.name instanceof nodes.Symbol)) { 289 this.fail('parseBlock: variable name expected', tag.lineno, tag.colno); 290 } 291 this.advanceAfterBlockEnd(tag.value); 292 node.body = this.parseUntilBlocks('endblock'); 293 this.skipSymbol('endblock'); 294 this.skipSymbol(node.name.value); 295 var tok = this.peekToken(); 296 if (!tok) { 297 this.fail('parseBlock: expected endblock, got end of file'); 298 } 299 this.advanceAfterBlockEnd(tok.value); 300 return node; 301 }; 302 _proto.parseExtends = function parseExtends() { 303 var tagName = 'extends'; 304 var tag = this.peekToken(); 305 if (!this.skipSymbol(tagName)) { 306 this.fail('parseTemplateRef: expected ' + tagName); 307 } 308 var node = new nodes.Extends(tag.lineno, tag.colno); 309 node.template = this.parseExpression(); 310 this.advanceAfterBlockEnd(tag.value); 311 return node; 312 }; 313 _proto.parseInclude = function parseInclude() { 314 var tagName = 'include'; 315 var tag = this.peekToken(); 316 if (!this.skipSymbol(tagName)) { 317 this.fail('parseInclude: expected ' + tagName); 318 } 319 var node = new nodes.Include(tag.lineno, tag.colno); 320 node.template = this.parseExpression(); 321 if (this.skipSymbol('ignore') && this.skipSymbol('missing')) { 322 node.ignoreMissing = true; 323 } 324 this.advanceAfterBlockEnd(tag.value); 325 return node; 326 }; 327 _proto.parseIf = function parseIf() { 328 var tag = this.peekToken(); 329 var node; 330 if (this.skipSymbol('if') || this.skipSymbol('elif') || this.skipSymbol('elseif')) { 331 node = new nodes.If(tag.lineno, tag.colno); 332 } else if (this.skipSymbol('ifAsync')) { 333 node = new nodes.IfAsync(tag.lineno, tag.colno); 334 } else { 335 this.fail('parseIf: expected if, elif, or elseif', tag.lineno, tag.colno); 336 } 337 node.cond = this.parseExpression(); 338 this.advanceAfterBlockEnd(tag.value); 339 node.body = this.parseUntilBlocks('elif', 'elseif', 'else', 'endif'); 340 var tok = this.peekToken(); 341 switch (tok && tok.value) { 342 case 'elseif': 343 case 'elif': 344 node.else_ = this.parseIf(); 345 break; 346 case 'else': 347 this.advanceAfterBlockEnd(); 348 node.else_ = this.parseUntilBlocks('endif'); 349 this.advanceAfterBlockEnd(); 350 break; 351 case 'endif': 352 node.else_ = null; 353 this.advanceAfterBlockEnd(); 354 break; 355 default: 356 this.fail('parseIf: expected elif, else, or endif, got end of file'); 357 } 358 return node; 359 }; 360 _proto.parseSet = function parseSet() { 361 var tag = this.peekToken(); 362 if (!this.skipSymbol('set')) { 363 this.fail('parseSet: expected set', tag.lineno, tag.colno); 364 } 365 var node = new nodes.Set(tag.lineno, tag.colno, []); 366 var target; 367 while (target = this.parsePrimary()) { 368 node.targets.push(target); 369 if (!this.skip(lexer.TOKEN_COMMA)) { 370 break; 371 } 372 } 373 if (!this.skipValue(lexer.TOKEN_OPERATOR, '=')) { 374 if (!this.skip(lexer.TOKEN_BLOCK_END)) { 375 this.fail('parseSet: expected = or block end in set tag', tag.lineno, tag.colno); 376 } else { 377 node.body = new nodes.Capture(tag.lineno, tag.colno, this.parseUntilBlocks('endset')); 378 node.value = null; 379 this.advanceAfterBlockEnd(); 380 } 381 } else { 382 node.value = this.parseExpression(); 383 this.advanceAfterBlockEnd(tag.value); 384 } 385 return node; 386 }; 387 _proto.parseSwitch = function parseSwitch() { 388 /* 389 * Store the tag names in variables in case someone ever wants to 390 * customize this. 391 */ 392 var switchStart = 'switch'; 393 var switchEnd = 'endswitch'; 394 var caseStart = 'case'; 395 var caseDefault = 'default'; 396 397 // Get the switch tag. 398 var tag = this.peekToken(); 399 400 // fail early if we get some unexpected tag. 401 if (!this.skipSymbol(switchStart) && !this.skipSymbol(caseStart) && !this.skipSymbol(caseDefault)) { 402 this.fail('parseSwitch: expected "switch," "case" or "default"', tag.lineno, tag.colno); 403 } 404 405 // parse the switch expression 406 var expr = this.parseExpression(); 407 408 // advance until a start of a case, a default case or an endswitch. 409 this.advanceAfterBlockEnd(switchStart); 410 this.parseUntilBlocks(caseStart, caseDefault, switchEnd); 411 412 // this is the first case. it could also be an endswitch, we'll check. 413 var tok = this.peekToken(); 414 415 // create new variables for our cases and default case. 416 var cases = []; 417 var defaultCase; 418 419 // while we're dealing with new cases nodes... 420 do { 421 // skip the start symbol and get the case expression 422 this.skipSymbol(caseStart); 423 var cond = this.parseExpression(); 424 this.advanceAfterBlockEnd(switchStart); 425 // get the body of the case node and add it to the array of cases. 426 var body = this.parseUntilBlocks(caseStart, caseDefault, switchEnd); 427 cases.push(new nodes.Case(tok.line, tok.col, cond, body)); 428 // get our next case 429 tok = this.peekToken(); 430 } while (tok && tok.value === caseStart); 431 432 // we either have a default case or a switch end. 433 switch (tok.value) { 434 case caseDefault: 435 this.advanceAfterBlockEnd(); 436 defaultCase = this.parseUntilBlocks(switchEnd); 437 this.advanceAfterBlockEnd(); 438 break; 439 case switchEnd: 440 this.advanceAfterBlockEnd(); 441 break; 442 default: 443 // otherwise bail because EOF 444 this.fail('parseSwitch: expected "case," "default" or "endswitch," got EOF.'); 445 } 446 447 // and return the switch node. 448 return new nodes.Switch(tag.lineno, tag.colno, expr, cases, defaultCase); 449 }; 450 _proto.parseStatement = function parseStatement() { 451 var tok = this.peekToken(); 452 var node; 453 if (tok.type !== lexer.TOKEN_SYMBOL) { 454 this.fail('tag name expected', tok.lineno, tok.colno); 455 } 456 if (this.breakOnBlocks && lib.indexOf(this.breakOnBlocks, tok.value) !== -1) { 457 return null; 458 } 459 switch (tok.value) { 460 case 'raw': 461 return this.parseRaw(); 462 case 'verbatim': 463 return this.parseRaw('verbatim'); 464 case 'if': 465 case 'ifAsync': 466 return this.parseIf(); 467 case 'for': 468 case 'asyncEach': 469 case 'asyncAll': 470 return this.parseFor(); 471 case 'block': 472 return this.parseBlock(); 473 case 'extends': 474 return this.parseExtends(); 475 case 'include': 476 return this.parseInclude(); 477 case 'set': 478 return this.parseSet(); 479 case 'macro': 480 return this.parseMacro(); 481 case 'call': 482 return this.parseCall(); 483 case 'import': 484 return this.parseImport(); 485 case 'from': 486 return this.parseFrom(); 487 case 'filter': 488 return this.parseFilterStatement(); 489 case 'switch': 490 return this.parseSwitch(); 491 default: 492 if (this.extensions.length) { 493 for (var i = 0; i < this.extensions.length; i++) { 494 var ext = this.extensions[i]; 495 if (lib.indexOf(ext.tags || [], tok.value) !== -1) { 496 return ext.parse(this, nodes, lexer); 497 } 498 } 499 } 500 this.fail('unknown block tag: ' + tok.value, tok.lineno, tok.colno); 501 } 502 return node; 503 }; 504 _proto.parseRaw = function parseRaw(tagName) { 505 tagName = tagName || 'raw'; 506 var endTagName = 'end' + tagName; 507 // Look for upcoming raw blocks (ignore all other kinds of blocks) 508 var rawBlockRegex = new RegExp('([\\s\\S]*?){%\\s*(' + tagName + '|' + endTagName + ')\\s*(?=%})%}'); 509 var rawLevel = 1; 510 var str = ''; 511 var matches = null; 512 513 // Skip opening raw token 514 // Keep this token to track line and column numbers 515 var begun = this.advanceAfterBlockEnd(); 516 517 // Exit when there's nothing to match 518 // or when we've found the matching "endraw" block 519 while ((matches = this.tokens._extractRegex(rawBlockRegex)) && rawLevel > 0) { 520 var all = matches[0]; 521 var pre = matches[1]; 522 var blockName = matches[2]; 523 524 // Adjust rawlevel 525 if (blockName === tagName) { 526 rawLevel += 1; 527 } else if (blockName === endTagName) { 528 rawLevel -= 1; 529 } 530 531 // Add to str 532 if (rawLevel === 0) { 533 // We want to exclude the last "endraw" 534 str += pre; 535 // Move tokenizer to beginning of endraw block 536 this.tokens.backN(all.length - pre.length); 537 } else { 538 str += all; 539 } 540 } 541 return new nodes.Output(begun.lineno, begun.colno, [new nodes.TemplateData(begun.lineno, begun.colno, str)]); 542 }; 543 _proto.parsePostfix = function parsePostfix(node) { 544 var lookup; 545 var tok = this.peekToken(); 546 while (tok) { 547 if (tok.type === lexer.TOKEN_LEFT_PAREN) { 548 // Function call 549 node = new nodes.FunCall(tok.lineno, tok.colno, node, this.parseSignature()); 550 } else if (tok.type === lexer.TOKEN_LEFT_BRACKET) { 551 // Reference 552 lookup = this.parseAggregate(); 553 if (lookup.children.length > 1) { 554 this.fail('invalid index'); 555 } 556 node = new nodes.LookupVal(tok.lineno, tok.colno, node, lookup.children[0]); 557 } else if (tok.type === lexer.TOKEN_OPERATOR && tok.value === '.') { 558 // Reference 559 this.nextToken(); 560 var val = this.nextToken(); 561 if (val.type !== lexer.TOKEN_SYMBOL) { 562 this.fail('expected name as lookup value, got ' + val.value, val.lineno, val.colno); 563 } 564 565 // Make a literal string because it's not a variable 566 // reference 567 lookup = new nodes.Literal(val.lineno, val.colno, val.value); 568 node = new nodes.LookupVal(tok.lineno, tok.colno, node, lookup); 569 } else { 570 break; 571 } 572 tok = this.peekToken(); 573 } 574 return node; 575 }; 576 _proto.parseExpression = function parseExpression() { 577 var node = this.parseInlineIf(); 578 return node; 579 }; 580 _proto.parseInlineIf = function parseInlineIf() { 581 var node = this.parseOr(); 582 if (this.skipSymbol('if')) { 583 var condNode = this.parseOr(); 584 var bodyNode = node; 585 node = new nodes.InlineIf(node.lineno, node.colno); 586 node.body = bodyNode; 587 node.cond = condNode; 588 if (this.skipSymbol('else')) { 589 node.else_ = this.parseOr(); 590 } else { 591 node.else_ = null; 592 } 593 } 594 return node; 595 }; 596 _proto.parseOr = function parseOr() { 597 var node = this.parseAnd(); 598 while (this.skipSymbol('or')) { 599 var node2 = this.parseAnd(); 600 node = new nodes.Or(node.lineno, node.colno, node, node2); 601 } 602 return node; 603 }; 604 _proto.parseAnd = function parseAnd() { 605 var node = this.parseNot(); 606 while (this.skipSymbol('and')) { 607 var node2 = this.parseNot(); 608 node = new nodes.And(node.lineno, node.colno, node, node2); 609 } 610 return node; 611 }; 612 _proto.parseNot = function parseNot() { 613 var tok = this.peekToken(); 614 if (this.skipSymbol('not')) { 615 return new nodes.Not(tok.lineno, tok.colno, this.parseNot()); 616 } 617 return this.parseIn(); 618 }; 619 _proto.parseIn = function parseIn() { 620 var node = this.parseIs(); 621 while (1) { 622 // eslint-disable-line no-constant-condition 623 // check if the next token is 'not' 624 var tok = this.nextToken(); 625 if (!tok) { 626 break; 627 } 628 var invert = tok.type === lexer.TOKEN_SYMBOL && tok.value === 'not'; 629 // if it wasn't 'not', put it back 630 if (!invert) { 631 this.pushToken(tok); 632 } 633 if (this.skipSymbol('in')) { 634 var node2 = this.parseIs(); 635 node = new nodes.In(node.lineno, node.colno, node, node2); 636 if (invert) { 637 node = new nodes.Not(node.lineno, node.colno, node); 638 } 639 } else { 640 // if we'd found a 'not' but this wasn't an 'in', put back the 'not' 641 if (invert) { 642 this.pushToken(tok); 643 } 644 break; 645 } 646 } 647 return node; 648 } 649 650 // I put this right after "in" in the operator precedence stack. That can 651 // obviously be changed to be closer to Jinja. 652 ; 653 _proto.parseIs = function parseIs() { 654 var node = this.parseCompare(); 655 // look for an is 656 if (this.skipSymbol('is')) { 657 // look for a not 658 var not = this.skipSymbol('not'); 659 // get the next node 660 var node2 = this.parseCompare(); 661 // create an Is node using the next node and the info from our Is node. 662 node = new nodes.Is(node.lineno, node.colno, node, node2); 663 // if we have a Not, create a Not node from our Is node. 664 if (not) { 665 node = new nodes.Not(node.lineno, node.colno, node); 666 } 667 } 668 // return the node. 669 return node; 670 }; 671 _proto.parseCompare = function parseCompare() { 672 var compareOps = ['==', '===', '!=', '!==', '<', '>', '<=', '>=']; 673 var expr = this.parseConcat(); 674 var ops = []; 675 while (1) { 676 // eslint-disable-line no-constant-condition 677 var tok = this.nextToken(); 678 if (!tok) { 679 break; 680 } else if (compareOps.indexOf(tok.value) !== -1) { 681 ops.push(new nodes.CompareOperand(tok.lineno, tok.colno, this.parseConcat(), tok.value)); 682 } else { 683 this.pushToken(tok); 684 break; 685 } 686 } 687 if (ops.length) { 688 return new nodes.Compare(ops[0].lineno, ops[0].colno, expr, ops); 689 } else { 690 return expr; 691 } 692 } 693 694 // finds the '~' for string concatenation 695 ; 696 _proto.parseConcat = function parseConcat() { 697 var node = this.parseAdd(); 698 while (this.skipValue(lexer.TOKEN_TILDE, '~')) { 699 var node2 = this.parseAdd(); 700 node = new nodes.Concat(node.lineno, node.colno, node, node2); 701 } 702 return node; 703 }; 704 _proto.parseAdd = function parseAdd() { 705 var node = this.parseSub(); 706 while (this.skipValue(lexer.TOKEN_OPERATOR, '+')) { 707 var node2 = this.parseSub(); 708 node = new nodes.Add(node.lineno, node.colno, node, node2); 709 } 710 return node; 711 }; 712 _proto.parseSub = function parseSub() { 713 var node = this.parseMul(); 714 while (this.skipValue(lexer.TOKEN_OPERATOR, '-')) { 715 var node2 = this.parseMul(); 716 node = new nodes.Sub(node.lineno, node.colno, node, node2); 717 } 718 return node; 719 }; 720 _proto.parseMul = function parseMul() { 721 var node = this.parseDiv(); 722 while (this.skipValue(lexer.TOKEN_OPERATOR, '*')) { 723 var node2 = this.parseDiv(); 724 node = new nodes.Mul(node.lineno, node.colno, node, node2); 725 } 726 return node; 727 }; 728 _proto.parseDiv = function parseDiv() { 729 var node = this.parseFloorDiv(); 730 while (this.skipValue(lexer.TOKEN_OPERATOR, '/')) { 731 var node2 = this.parseFloorDiv(); 732 node = new nodes.Div(node.lineno, node.colno, node, node2); 733 } 734 return node; 735 }; 736 _proto.parseFloorDiv = function parseFloorDiv() { 737 var node = this.parseMod(); 738 while (this.skipValue(lexer.TOKEN_OPERATOR, '//')) { 739 var node2 = this.parseMod(); 740 node = new nodes.FloorDiv(node.lineno, node.colno, node, node2); 741 } 742 return node; 743 }; 744 _proto.parseMod = function parseMod() { 745 var node = this.parsePow(); 746 while (this.skipValue(lexer.TOKEN_OPERATOR, '%')) { 747 var node2 = this.parsePow(); 748 node = new nodes.Mod(node.lineno, node.colno, node, node2); 749 } 750 return node; 751 }; 752 _proto.parsePow = function parsePow() { 753 var node = this.parseUnary(); 754 while (this.skipValue(lexer.TOKEN_OPERATOR, '**')) { 755 var node2 = this.parseUnary(); 756 node = new nodes.Pow(node.lineno, node.colno, node, node2); 757 } 758 return node; 759 }; 760 _proto.parseUnary = function parseUnary(noFilters) { 761 var tok = this.peekToken(); 762 var node; 763 if (this.skipValue(lexer.TOKEN_OPERATOR, '-')) { 764 node = new nodes.Neg(tok.lineno, tok.colno, this.parseUnary(true)); 765 } else if (this.skipValue(lexer.TOKEN_OPERATOR, '+')) { 766 node = new nodes.Pos(tok.lineno, tok.colno, this.parseUnary(true)); 767 } else { 768 node = this.parsePrimary(); 769 } 770 if (!noFilters) { 771 node = this.parseFilter(node); 772 } 773 return node; 774 }; 775 _proto.parsePrimary = function parsePrimary(noPostfix) { 776 var tok = this.nextToken(); 777 var val; 778 var node = null; 779 if (!tok) { 780 this.fail('expected expression, got end of file'); 781 } else if (tok.type === lexer.TOKEN_STRING) { 782 val = tok.value; 783 } else if (tok.type === lexer.TOKEN_INT) { 784 val = parseInt(tok.value, 10); 785 } else if (tok.type === lexer.TOKEN_FLOAT) { 786 val = parseFloat(tok.value); 787 } else if (tok.type === lexer.TOKEN_BOOLEAN) { 788 if (tok.value === 'true') { 789 val = true; 790 } else if (tok.value === 'false') { 791 val = false; 792 } else { 793 this.fail('invalid boolean: ' + tok.value, tok.lineno, tok.colno); 794 } 795 } else if (tok.type === lexer.TOKEN_NONE) { 796 val = null; 797 } else if (tok.type === lexer.TOKEN_REGEX) { 798 val = new RegExp(tok.value.body, tok.value.flags); 799 } 800 if (val !== undefined) { 801 node = new nodes.Literal(tok.lineno, tok.colno, val); 802 } else if (tok.type === lexer.TOKEN_SYMBOL) { 803 node = new nodes.Symbol(tok.lineno, tok.colno, tok.value); 804 } else { 805 // See if it's an aggregate type, we need to push the 806 // current delimiter token back on 807 this.pushToken(tok); 808 node = this.parseAggregate(); 809 } 810 if (!noPostfix) { 811 node = this.parsePostfix(node); 812 } 813 if (node) { 814 return node; 815 } else { 816 throw this.error("unexpected token: " + tok.value, tok.lineno, tok.colno); 817 } 818 }; 819 _proto.parseFilterName = function parseFilterName() { 820 var tok = this.expect(lexer.TOKEN_SYMBOL); 821 var name = tok.value; 822 while (this.skipValue(lexer.TOKEN_OPERATOR, '.')) { 823 name += '.' + this.expect(lexer.TOKEN_SYMBOL).value; 824 } 825 return new nodes.Symbol(tok.lineno, tok.colno, name); 826 }; 827 _proto.parseFilterArgs = function parseFilterArgs(node) { 828 if (this.peekToken().type === lexer.TOKEN_LEFT_PAREN) { 829 // Get a FunCall node and add the parameters to the 830 // filter 831 var call = this.parsePostfix(node); 832 return call.args.children; 833 } 834 return []; 835 }; 836 _proto.parseFilter = function parseFilter(node) { 837 while (this.skip(lexer.TOKEN_PIPE)) { 838 var name = this.parseFilterName(); 839 node = new nodes.Filter(name.lineno, name.colno, name, new nodes.NodeList(name.lineno, name.colno, [node].concat(this.parseFilterArgs(node)))); 840 } 841 return node; 842 }; 843 _proto.parseFilterStatement = function parseFilterStatement() { 844 var filterTok = this.peekToken(); 845 if (!this.skipSymbol('filter')) { 846 this.fail('parseFilterStatement: expected filter'); 847 } 848 var name = this.parseFilterName(); 849 var args = this.parseFilterArgs(name); 850 this.advanceAfterBlockEnd(filterTok.value); 851 var body = new nodes.Capture(name.lineno, name.colno, this.parseUntilBlocks('endfilter')); 852 this.advanceAfterBlockEnd(); 853 var node = new nodes.Filter(name.lineno, name.colno, name, new nodes.NodeList(name.lineno, name.colno, [body].concat(args))); 854 return new nodes.Output(name.lineno, name.colno, [node]); 855 }; 856 _proto.parseAggregate = function parseAggregate() { 857 var tok = this.nextToken(); 858 var node; 859 switch (tok.type) { 860 case lexer.TOKEN_LEFT_PAREN: 861 node = new nodes.Group(tok.lineno, tok.colno); 862 break; 863 case lexer.TOKEN_LEFT_BRACKET: 864 node = new nodes.Array(tok.lineno, tok.colno); 865 break; 866 case lexer.TOKEN_LEFT_CURLY: 867 node = new nodes.Dict(tok.lineno, tok.colno); 868 break; 869 default: 870 return null; 871 } 872 while (1) { 873 // eslint-disable-line no-constant-condition 874 var type = this.peekToken().type; 875 if (type === lexer.TOKEN_RIGHT_PAREN || type === lexer.TOKEN_RIGHT_BRACKET || type === lexer.TOKEN_RIGHT_CURLY) { 876 this.nextToken(); 877 break; 878 } 879 if (node.children.length > 0) { 880 if (!this.skip(lexer.TOKEN_COMMA)) { 881 this.fail('parseAggregate: expected comma after expression', tok.lineno, tok.colno); 882 } 883 } 884 if (node instanceof nodes.Dict) { 885 // TODO: check for errors 886 var key = this.parsePrimary(); 887 888 // We expect a key/value pair for dicts, separated by a 889 // colon 890 if (!this.skip(lexer.TOKEN_COLON)) { 891 this.fail('parseAggregate: expected colon after dict key', tok.lineno, tok.colno); 892 } 893 894 // TODO: check for errors 895 var value = this.parseExpression(); 896 node.addChild(new nodes.Pair(key.lineno, key.colno, key, value)); 897 } else { 898 // TODO: check for errors 899 var expr = this.parseExpression(); 900 node.addChild(expr); 901 } 902 } 903 return node; 904 }; 905 _proto.parseSignature = function parseSignature(tolerant, noParens) { 906 var tok = this.peekToken(); 907 if (!noParens && tok.type !== lexer.TOKEN_LEFT_PAREN) { 908 if (tolerant) { 909 return null; 910 } else { 911 this.fail('expected arguments', tok.lineno, tok.colno); 912 } 913 } 914 if (tok.type === lexer.TOKEN_LEFT_PAREN) { 915 tok = this.nextToken(); 916 } 917 var args = new nodes.NodeList(tok.lineno, tok.colno); 918 var kwargs = new nodes.KeywordArgs(tok.lineno, tok.colno); 919 var checkComma = false; 920 while (1) { 921 // eslint-disable-line no-constant-condition 922 tok = this.peekToken(); 923 if (!noParens && tok.type === lexer.TOKEN_RIGHT_PAREN) { 924 this.nextToken(); 925 break; 926 } else if (noParens && tok.type === lexer.TOKEN_BLOCK_END) { 927 break; 928 } 929 if (checkComma && !this.skip(lexer.TOKEN_COMMA)) { 930 this.fail('parseSignature: expected comma after expression', tok.lineno, tok.colno); 931 } else { 932 var arg = this.parseExpression(); 933 if (this.skipValue(lexer.TOKEN_OPERATOR, '=')) { 934 kwargs.addChild(new nodes.Pair(arg.lineno, arg.colno, arg, this.parseExpression())); 935 } else { 936 args.addChild(arg); 937 } 938 } 939 checkComma = true; 940 } 941 if (kwargs.children.length) { 942 args.addChild(kwargs); 943 } 944 return args; 945 }; 946 _proto.parseUntilBlocks = function parseUntilBlocks() { 947 var prev = this.breakOnBlocks; 948 for (var _len = arguments.length, blockNames = new Array(_len), _key = 0; _key < _len; _key++) { 949 blockNames[_key] = arguments[_key]; 950 } 951 this.breakOnBlocks = blockNames; 952 var ret = this.parse(); 953 this.breakOnBlocks = prev; 954 return ret; 955 }; 956 _proto.parseNodes = function parseNodes() { 957 var tok; 958 var buf = []; 959 while (tok = this.nextToken()) { 960 if (tok.type === lexer.TOKEN_DATA) { 961 var data = tok.value; 962 var nextToken = this.peekToken(); 963 var nextVal = nextToken && nextToken.value; 964 965 // If the last token has "-" we need to trim the 966 // leading whitespace of the data. This is marked with 967 // the `dropLeadingWhitespace` variable. 968 if (this.dropLeadingWhitespace) { 969 // TODO: this could be optimized (don't use regex) 970 data = data.replace(/^\s*/, ''); 971 this.dropLeadingWhitespace = false; 972 } 973 974 // Same for the succeeding block start token 975 if (nextToken && (nextToken.type === lexer.TOKEN_BLOCK_START && nextVal.charAt(nextVal.length - 1) === '-' || nextToken.type === lexer.TOKEN_VARIABLE_START && nextVal.charAt(this.tokens.tags.VARIABLE_START.length) === '-' || nextToken.type === lexer.TOKEN_COMMENT && nextVal.charAt(this.tokens.tags.COMMENT_START.length) === '-')) { 976 // TODO: this could be optimized (don't use regex) 977 data = data.replace(/\s*$/, ''); 978 } 979 buf.push(new nodes.Output(tok.lineno, tok.colno, [new nodes.TemplateData(tok.lineno, tok.colno, data)])); 980 } else if (tok.type === lexer.TOKEN_BLOCK_START) { 981 this.dropLeadingWhitespace = false; 982 var n = this.parseStatement(); 983 if (!n) { 984 break; 985 } 986 buf.push(n); 987 } else if (tok.type === lexer.TOKEN_VARIABLE_START) { 988 var e = this.parseExpression(); 989 this.dropLeadingWhitespace = false; 990 this.advanceAfterVariableEnd(); 991 buf.push(new nodes.Output(tok.lineno, tok.colno, [e])); 992 } else if (tok.type === lexer.TOKEN_COMMENT) { 993 this.dropLeadingWhitespace = tok.value.charAt(tok.value.length - this.tokens.tags.COMMENT_END.length - 1) === '-'; 994 } else { 995 // Ignore comments, otherwise this should be an error 996 this.fail('Unexpected token at top-level: ' + tok.type, tok.lineno, tok.colno); 997 } 998 } 999 return buf; 1000 }; 1001 _proto.parse = function parse() { 1002 return new nodes.NodeList(0, 0, this.parseNodes()); 1003 }; 1004 _proto.parseAsRoot = function parseAsRoot() { 1005 return new nodes.Root(0, 0, this.parseNodes()); 1006 }; 1007 return Parser; 1008}(Obj); // var util = require('util'); 1009// var l = lexer.lex('{%- if x -%}\n hello {% endif %}'); 1010// var t; 1011// while((t = l.nextToken())) { 1012// console.log(util.inspect(t)); 1013// } 1014// var p = new Parser(lexer.lex('hello {% filter title %}' + 1015// 'Hello madam how are you' + 1016// '{% endfilter %}')); 1017// var n = p.parseAsRoot(); 1018// nodes.printNodes(n); 1019module.exports = { 1020 parse: function parse(src, extensions, opts) { 1021 var p = new Parser(lexer.lex(src, opts)); 1022 if (extensions !== undefined) { 1023 p.extensions = extensions; 1024 } 1025 return p.parseAsRoot(); 1026 }, 1027 Parser: Parser 1028};