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};