1 | 'use strict';
|
2 |
|
3 | var Lexer = require('./lexer');
|
4 | var nodes = require('./nodes');
|
5 | var utils = require('./utils');
|
6 | var filters = require('./filters');
|
7 | var path = require('path');
|
8 | var constantinople = require('constantinople');
|
9 | var parseJSExpression = require('character-parser').parseMax;
|
10 | var extname = path.extname;
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 | var Parser = exports = module.exports = function Parser(str, filename, options){
|
22 |
|
23 | this.input = str.replace(/^\uFEFF/, '');
|
24 | this.lexer = new Lexer(this.input, filename);
|
25 | this.filename = filename;
|
26 | this.blocks = {};
|
27 | this.mixins = {};
|
28 | this.options = options;
|
29 | this.contexts = [this];
|
30 | this.inMixin = 0;
|
31 | this.dependencies = [];
|
32 | this.inBlock = 0;
|
33 | };
|
34 |
|
35 |
|
36 |
|
37 |
|
38 |
|
39 | Parser.prototype = {
|
40 |
|
41 | |
42 |
|
43 |
|
44 |
|
45 | constructor: Parser,
|
46 |
|
47 | /**
|
48 | * Push `parser` onto the context stack,
|
49 | * or pop and return a `Parser`.
|
50 | */
|
51 |
|
52 | context: function(parser){
|
53 | if (parser) {
|
54 | this.contexts.push(parser);
|
55 | } else {
|
56 | return this.contexts.pop();
|
57 | }
|
58 | },
|
59 |
|
60 | |
61 |
|
62 |
|
63 |
|
64 |
|
65 |
|
66 |
|
67 | advance: function(){
|
68 | return this.lexer.advance();
|
69 | },
|
70 |
|
71 | |
72 |
|
73 |
|
74 |
|
75 |
|
76 |
|
77 |
|
78 | peek: function() {
|
79 | return this.lookahead(1);
|
80 | },
|
81 |
|
82 | |
83 |
|
84 |
|
85 |
|
86 |
|
87 |
|
88 |
|
89 | line: function() {
|
90 | return this.lexer.lineno;
|
91 | },
|
92 |
|
93 | |
94 |
|
95 |
|
96 |
|
97 |
|
98 |
|
99 |
|
100 |
|
101 | lookahead: function(n){
|
102 | return this.lexer.lookahead(n);
|
103 | },
|
104 |
|
105 | |
106 |
|
107 |
|
108 |
|
109 |
|
110 |
|
111 |
|
112 | parse: function(){
|
113 | var block = new nodes.Block, parser;
|
114 | block.line = 0;
|
115 | block.filename = this.filename;
|
116 |
|
117 | while ('eos' != this.peek().type) {
|
118 | if ('newline' == this.peek().type) {
|
119 | this.advance();
|
120 | } else {
|
121 | var next = this.peek();
|
122 | var expr = this.parseExpr();
|
123 | expr.filename = expr.filename || this.filename;
|
124 | expr.line = next.line;
|
125 | block.push(expr);
|
126 | }
|
127 | }
|
128 |
|
129 | if (parser = this.extending) {
|
130 | this.context(parser);
|
131 | var ast = parser.parse();
|
132 | this.context();
|
133 |
|
134 |
|
135 | for (var name in this.mixins)
|
136 | ast.unshift(this.mixins[name]);
|
137 | return ast;
|
138 | }
|
139 |
|
140 | if (!this.extending && !this.included && Object.keys(this.blocks).length){
|
141 | var blocks = [];
|
142 | utils.walkAST(block, function (node) {
|
143 | if (node.type === 'Block' && node.name) {
|
144 | blocks.push(node.name);
|
145 | }
|
146 | });
|
147 | Object.keys(this.blocks).forEach(function (name) {
|
148 | if (blocks.indexOf(name) === -1 && !this.blocks[name].isSubBlock) {
|
149 | console.warn('Warning: Unexpected block "'
|
150 | + name
|
151 | + '" '
|
152 | + ' on line '
|
153 | + this.blocks[name].line
|
154 | + ' of '
|
155 | + (this.blocks[name].filename)
|
156 | + '. This block is never used. This warning will be an error in v2.0.0');
|
157 | }
|
158 | }.bind(this));
|
159 | }
|
160 |
|
161 | return block;
|
162 | },
|
163 |
|
164 | |
165 |
|
166 |
|
167 |
|
168 |
|
169 |
|
170 |
|
171 | expect: function(type){
|
172 | if (this.peek().type === type) {
|
173 | return this.advance();
|
174 | } else {
|
175 | throw new Error('expected "' + type + '", but got "' + this.peek().type + '"');
|
176 | }
|
177 | },
|
178 |
|
179 | |
180 |
|
181 |
|
182 |
|
183 |
|
184 |
|
185 |
|
186 | accept: function(type){
|
187 | if (this.peek().type === type) {
|
188 | return this.advance();
|
189 | }
|
190 | },
|
191 |
|
192 | |
193 |
|
194 |
|
195 |
|
196 |
|
197 |
|
198 |
|
199 |
|
200 |
|
201 |
|
202 |
|
203 |
|
204 |
|
205 |
|
206 |
|
207 |
|
208 | parseExpr: function(){
|
209 | switch (this.peek().type) {
|
210 | case 'tag':
|
211 | return this.parseTag();
|
212 | case 'mixin':
|
213 | return this.parseMixin();
|
214 | case 'block':
|
215 | return this.parseBlock();
|
216 | case 'mixin-block':
|
217 | return this.parseMixinBlock();
|
218 | case 'case':
|
219 | return this.parseCase();
|
220 | case 'extends':
|
221 | return this.parseExtends();
|
222 | case 'include':
|
223 | return this.parseInclude();
|
224 | case 'doctype':
|
225 | return this.parseDoctype();
|
226 | case 'filter':
|
227 | return this.parseFilter();
|
228 | case 'comment':
|
229 | return this.parseComment();
|
230 | case 'text':
|
231 | return this.parseText();
|
232 | case 'each':
|
233 | return this.parseEach();
|
234 | case 'code':
|
235 | return this.parseCode();
|
236 | case 'blockCode':
|
237 | return this.parseBlockCode();
|
238 | case 'call':
|
239 | return this.parseCall();
|
240 | case 'interpolation':
|
241 | return this.parseInterpolation();
|
242 | case 'yield':
|
243 | this.advance();
|
244 | var block = new nodes.Block;
|
245 | block.yield = true;
|
246 | return block;
|
247 | case 'id':
|
248 | case 'class':
|
249 | var tok = this.advance();
|
250 | this.lexer.defer(this.lexer.tok('tag', 'div'));
|
251 | this.lexer.defer(tok);
|
252 | return this.parseExpr();
|
253 | default:
|
254 | throw new Error('unexpected token "' + this.peek().type + '"');
|
255 | }
|
256 | },
|
257 |
|
258 | |
259 |
|
260 |
|
261 |
|
262 | parseText: function(){
|
263 | var tok = this.expect('text');
|
264 | var tokens = this.parseInlineTagsInText(tok.val);
|
265 | if (tokens.length === 1) return tokens[0];
|
266 | var node = new nodes.Block;
|
267 | for (var i = 0; i < tokens.length; i++) {
|
268 | node.push(tokens[i]);
|
269 | };
|
270 | return node;
|
271 | },
|
272 |
|
273 | |
274 |
|
275 |
|
276 |
|
277 |
|
278 | parseBlockExpansion: function(){
|
279 | if (':' == this.peek().type) {
|
280 | this.advance();
|
281 | return new nodes.Block(this.parseExpr());
|
282 | } else {
|
283 | return this.block();
|
284 | }
|
285 | },
|
286 |
|
287 | |
288 |
|
289 |
|
290 |
|
291 | parseCase: function(){
|
292 | var val = this.expect('case').val;
|
293 | var node = new nodes.Case(val);
|
294 | node.line = this.line();
|
295 |
|
296 | var block = new nodes.Block;
|
297 | block.line = this.line();
|
298 | block.filename = this.filename;
|
299 | this.expect('indent');
|
300 | while ('outdent' != this.peek().type) {
|
301 | switch (this.peek().type) {
|
302 | case 'comment':
|
303 | case 'newline':
|
304 | this.advance();
|
305 | break;
|
306 | case 'when':
|
307 | block.push(this.parseWhen());
|
308 | break;
|
309 | case 'default':
|
310 | block.push(this.parseDefault());
|
311 | break;
|
312 | default:
|
313 | throw new Error('Unexpected token "' + this.peek().type
|
314 | + '", expected "when", "default" or "newline"');
|
315 | }
|
316 | }
|
317 | this.expect('outdent');
|
318 |
|
319 | node.block = block;
|
320 |
|
321 | return node;
|
322 | },
|
323 |
|
324 | |
325 |
|
326 |
|
327 |
|
328 | parseWhen: function(){
|
329 | var val = this.expect('when').val;
|
330 | if (this.peek().type !== 'newline')
|
331 | return new nodes.Case.When(val, this.parseBlockExpansion());
|
332 | else
|
333 | return new nodes.Case.When(val);
|
334 | },
|
335 |
|
336 | |
337 |
|
338 |
|
339 |
|
340 | parseDefault: function(){
|
341 | this.expect('default');
|
342 | return new nodes.Case.When('default', this.parseBlockExpansion());
|
343 | },
|
344 |
|
345 | |
346 |
|
347 |
|
348 |
|
349 | parseCode: function(afterIf){
|
350 | var tok = this.expect('code');
|
351 | var node = new nodes.Code(tok.val, tok.buffer, tok.escape);
|
352 | var block;
|
353 | node.line = this.line();
|
354 |
|
355 |
|
356 | if (tok.isElse && !tok.hasIf) {
|
357 | throw new Error('Unexpected else without if');
|
358 | }
|
359 |
|
360 |
|
361 | block = 'indent' == this.peek().type;
|
362 | if (block) {
|
363 | node.block = this.block();
|
364 | }
|
365 |
|
366 |
|
367 | if (tok.requiresBlock && !block) {
|
368 | node.block = new nodes.Block();
|
369 | }
|
370 |
|
371 |
|
372 | if (tok.isIf && this.peek().isElse) {
|
373 | this.peek().hasIf = true;
|
374 | } else if (tok.isIf && this.peek().type === 'newline' && this.lookahead(2).isElse) {
|
375 | this.lookahead(2).hasIf = true;
|
376 | }
|
377 |
|
378 | return node;
|
379 | },
|
380 |
|
381 | |
382 |
|
383 |
|
384 |
|
385 | parseBlockCode: function(){
|
386 | var tok = this.expect('blockCode');
|
387 | var node;
|
388 | var body = this.peek();
|
389 | var text;
|
390 | if (body.type === 'pipeless-text') {
|
391 | this.advance();
|
392 | text = body.val.join('\n');
|
393 | } else {
|
394 | text = '';
|
395 | }
|
396 | node = new nodes.Code(text, false, false);
|
397 | return node;
|
398 | },
|
399 |
|
400 | |
401 |
|
402 |
|
403 |
|
404 | parseComment: function(){
|
405 | var tok = this.expect('comment');
|
406 | var node;
|
407 |
|
408 | var block;
|
409 | if (block = this.parseTextBlock()) {
|
410 | node = new nodes.BlockComment(tok.val, block, tok.buffer);
|
411 | } else {
|
412 | node = new nodes.Comment(tok.val, tok.buffer);
|
413 | }
|
414 |
|
415 | node.line = this.line();
|
416 | return node;
|
417 | },
|
418 |
|
419 | |
420 |
|
421 |
|
422 |
|
423 | parseDoctype: function(){
|
424 | var tok = this.expect('doctype');
|
425 | var node = new nodes.Doctype(tok.val);
|
426 | node.line = this.line();
|
427 | return node;
|
428 | },
|
429 |
|
430 | |
431 |
|
432 |
|
433 |
|
434 | parseFilter: function(){
|
435 | var tok = this.expect('filter');
|
436 | var attrs = this.accept('attrs');
|
437 | var block;
|
438 |
|
439 | block = this.parseTextBlock() || new nodes.Block();
|
440 |
|
441 | var options = {};
|
442 | if (attrs) {
|
443 | attrs.attrs.forEach(function (attribute) {
|
444 | options[attribute.name] = constantinople.toConstant(attribute.val);
|
445 | });
|
446 | }
|
447 |
|
448 | var node = new nodes.Filter(tok.val, block, options);
|
449 | node.line = this.line();
|
450 | return node;
|
451 | },
|
452 |
|
453 | |
454 |
|
455 |
|
456 |
|
457 | parseEach: function(){
|
458 | var tok = this.expect('each');
|
459 | var node = new nodes.Each(tok.code, tok.val, tok.key);
|
460 | node.line = this.line();
|
461 | node.block = this.block();
|
462 | if (this.peek().type == 'code' && this.peek().val == 'else') {
|
463 | this.advance();
|
464 | node.alternative = this.block();
|
465 | }
|
466 | return node;
|
467 | },
|
468 |
|
469 | |
470 |
|
471 |
|
472 |
|
473 |
|
474 |
|
475 |
|
476 |
|
477 |
|
478 |
|
479 | resolvePath: function (path, purpose) {
|
480 | var p = require('path');
|
481 | var dirname = p.dirname;
|
482 | var basename = p.basename;
|
483 | var join = p.join;
|
484 |
|
485 | if (path[0] !== '/' && !this.filename)
|
486 | throw new Error('the "filename" option is required to use "' + purpose + '" with "relative" paths');
|
487 |
|
488 | if (path[0] === '/' && !this.options.basedir)
|
489 | throw new Error('the "basedir" option is required to use "' + purpose + '" with "absolute" paths');
|
490 |
|
491 | path = join(path[0] === '/' ? this.options.basedir : dirname(this.filename), path);
|
492 |
|
493 | if (basename(path).indexOf('.') === -1) path += '.jade';
|
494 |
|
495 | return path;
|
496 | },
|
497 |
|
498 | |
499 |
|
500 |
|
501 |
|
502 | parseExtends: function(){
|
503 | var fs = require('fs');
|
504 |
|
505 | var path = this.resolvePath(this.expect('extends').val.trim(), 'extends');
|
506 | if ('.jade' != path.substr(-5)) path += '.jade';
|
507 |
|
508 | this.dependencies.push(path);
|
509 | var str = fs.readFileSync(path, 'utf8');
|
510 | var parser = new this.constructor(str, path, this.options);
|
511 | parser.dependencies = this.dependencies;
|
512 |
|
513 | parser.blocks = this.blocks;
|
514 | parser.included = this.included;
|
515 | parser.contexts = this.contexts;
|
516 | this.extending = parser;
|
517 |
|
518 |
|
519 | return new nodes.Literal('');
|
520 | },
|
521 |
|
522 | |
523 |
|
524 |
|
525 |
|
526 | parseBlock: function(){
|
527 | var block = this.expect('block');
|
528 | var mode = block.mode;
|
529 | var name = block.val.trim();
|
530 |
|
531 | var line = block.line;
|
532 |
|
533 | this.inBlock++;
|
534 | block = 'indent' == this.peek().type
|
535 | ? this.block()
|
536 | : new nodes.Block(new nodes.Literal(''));
|
537 | this.inBlock--;
|
538 | block.name = name;
|
539 | block.line = line;
|
540 |
|
541 | var prev = this.blocks[name] || {prepended: [], appended: []}
|
542 | if (prev.mode === 'replace') return this.blocks[name] = prev;
|
543 |
|
544 | var allNodes = prev.prepended.concat(block.nodes).concat(prev.appended);
|
545 |
|
546 | switch (mode) {
|
547 | case 'append':
|
548 | prev.appended = prev.parser === this ?
|
549 | prev.appended.concat(block.nodes) :
|
550 | block.nodes.concat(prev.appended);
|
551 | break;
|
552 | case 'prepend':
|
553 | prev.prepended = prev.parser === this ?
|
554 | block.nodes.concat(prev.prepended) :
|
555 | prev.prepended.concat(block.nodes);
|
556 | break;
|
557 | }
|
558 | block.nodes = allNodes;
|
559 | block.appended = prev.appended;
|
560 | block.prepended = prev.prepended;
|
561 | block.mode = mode;
|
562 | block.parser = this;
|
563 |
|
564 | block.isSubBlock = this.inBlock > 0;
|
565 |
|
566 | return this.blocks[name] = block;
|
567 | },
|
568 |
|
569 | parseMixinBlock: function () {
|
570 | var block = this.expect('mixin-block');
|
571 | if (!this.inMixin) {
|
572 | throw new Error('Anonymous blocks are not allowed unless they are part of a mixin.');
|
573 | }
|
574 | return new nodes.MixinBlock();
|
575 | },
|
576 |
|
577 | |
578 |
|
579 |
|
580 |
|
581 | parseInclude: function(){
|
582 | var fs = require('fs');
|
583 | var tok = this.expect('include');
|
584 |
|
585 | var path = this.resolvePath(tok.val.trim(), 'include');
|
586 | this.dependencies.push(path);
|
587 |
|
588 | if (tok.filter) {
|
589 | var str = fs.readFileSync(path, 'utf8').replace(/\r/g, '');
|
590 | var options = {filename: path};
|
591 | if (tok.attrs) {
|
592 | tok.attrs.attrs.forEach(function (attribute) {
|
593 | options[attribute.name] = constantinople.toConstant(attribute.val);
|
594 | });
|
595 | }
|
596 | str = filters(tok.filter, str, options);
|
597 | return new nodes.Literal(str);
|
598 | }
|
599 |
|
600 |
|
601 | if ('.jade' != path.substr(-5)) {
|
602 | var str = fs.readFileSync(path, 'utf8').replace(/\r/g, '');
|
603 | return new nodes.Literal(str);
|
604 | }
|
605 |
|
606 | var str = fs.readFileSync(path, 'utf8');
|
607 | var parser = new this.constructor(str, path, this.options);
|
608 | parser.dependencies = this.dependencies;
|
609 |
|
610 | parser.blocks = utils.merge({}, this.blocks);
|
611 | parser.included = true;
|
612 |
|
613 | parser.mixins = this.mixins;
|
614 |
|
615 | this.context(parser);
|
616 | var ast = parser.parse();
|
617 | this.context();
|
618 | ast.filename = path;
|
619 |
|
620 | if ('indent' == this.peek().type) {
|
621 | ast.includeBlock().push(this.block());
|
622 | }
|
623 |
|
624 | return ast;
|
625 | },
|
626 |
|
627 | |
628 |
|
629 |
|
630 |
|
631 | parseCall: function(){
|
632 | var tok = this.expect('call');
|
633 | var name = tok.val;
|
634 | var args = tok.args;
|
635 | var mixin = new nodes.Mixin(name, args, new nodes.Block, true);
|
636 |
|
637 | this.tag(mixin);
|
638 | if (mixin.code) {
|
639 | mixin.block.push(mixin.code);
|
640 | mixin.code = null;
|
641 | }
|
642 | if (mixin.block.isEmpty()) mixin.block = null;
|
643 | return mixin;
|
644 | },
|
645 |
|
646 | |
647 |
|
648 |
|
649 |
|
650 | parseMixin: function(){
|
651 | var tok = this.expect('mixin');
|
652 | var name = tok.val;
|
653 | var args = tok.args;
|
654 | var mixin;
|
655 |
|
656 |
|
657 | if ('indent' == this.peek().type) {
|
658 | this.inMixin++;
|
659 | mixin = new nodes.Mixin(name, args, this.block(), false);
|
660 | this.mixins[name] = mixin;
|
661 | this.inMixin--;
|
662 | return mixin;
|
663 |
|
664 | } else {
|
665 | return new nodes.Mixin(name, args, null, true);
|
666 | }
|
667 | },
|
668 |
|
669 | parseInlineTagsInText: function (str) {
|
670 | var line = this.line();
|
671 |
|
672 | var match = /(\\)?#\[((?:.|\n)*)$/.exec(str);
|
673 | if (match) {
|
674 | if (match[1]) {
|
675 | var text = new nodes.Text(str.substr(0, match.index) + '#[');
|
676 | text.line = line;
|
677 | var rest = this.parseInlineTagsInText(match[2]);
|
678 | if (rest[0].type === 'Text') {
|
679 | text.val += rest[0].val;
|
680 | rest.shift();
|
681 | }
|
682 | return [text].concat(rest);
|
683 | } else {
|
684 | var text = new nodes.Text(str.substr(0, match.index));
|
685 | text.line = line;
|
686 | var buffer = [text];
|
687 | var rest = match[2];
|
688 | var range = parseJSExpression(rest);
|
689 | var inner = new Parser(range.src, this.filename, this.options);
|
690 | buffer.push(inner.parse());
|
691 | return buffer.concat(this.parseInlineTagsInText(rest.substr(range.end + 1)));
|
692 | }
|
693 | } else {
|
694 | var text = new nodes.Text(str);
|
695 | text.line = line;
|
696 | return [text];
|
697 | }
|
698 | },
|
699 |
|
700 | |
701 |
|
702 |
|
703 |
|
704 | parseTextBlock: function(){
|
705 | var block = new nodes.Block;
|
706 | block.line = this.line();
|
707 | var body = this.peek();
|
708 | if (body.type !== 'pipeless-text') return;
|
709 | this.advance();
|
710 | block.nodes = body.val.reduce(function (accumulator, text) {
|
711 | return accumulator.concat(this.parseInlineTagsInText(text));
|
712 | }.bind(this), []);
|
713 | return block;
|
714 | },
|
715 |
|
716 | |
717 |
|
718 |
|
719 |
|
720 | block: function(){
|
721 | var block = new nodes.Block;
|
722 | block.line = this.line();
|
723 | block.filename = this.filename;
|
724 | this.expect('indent');
|
725 | while ('outdent' != this.peek().type) {
|
726 | if ('newline' == this.peek().type) {
|
727 | this.advance();
|
728 | } else {
|
729 | var expr = this.parseExpr();
|
730 | expr.filename = this.filename;
|
731 | block.push(expr);
|
732 | }
|
733 | }
|
734 | this.expect('outdent');
|
735 | return block;
|
736 | },
|
737 |
|
738 | |
739 |
|
740 |
|
741 |
|
742 | parseInterpolation: function(){
|
743 | var tok = this.advance();
|
744 | var tag = new nodes.Tag(tok.val);
|
745 | tag.buffer = true;
|
746 | return this.tag(tag);
|
747 | },
|
748 |
|
749 | |
750 |
|
751 |
|
752 |
|
753 | parseTag: function(){
|
754 | var tok = this.advance();
|
755 | var tag = new nodes.Tag(tok.val);
|
756 |
|
757 | tag.selfClosing = tok.selfClosing;
|
758 |
|
759 | return this.tag(tag);
|
760 | },
|
761 |
|
762 | |
763 |
|
764 |
|
765 |
|
766 | tag: function(tag){
|
767 | tag.line = this.line();
|
768 |
|
769 | var seenAttrs = false;
|
770 |
|
771 | out:
|
772 | while (true) {
|
773 | switch (this.peek().type) {
|
774 | case 'id':
|
775 | case 'class':
|
776 | var tok = this.advance();
|
777 | tag.setAttribute(tok.type, "'" + tok.val + "'");
|
778 | continue;
|
779 | case 'attrs':
|
780 | if (seenAttrs) {
|
781 | console.warn(this.filename + ', line ' + this.peek().line + ':\nYou should not have jade tags with multiple attributes.');
|
782 | }
|
783 | seenAttrs = true;
|
784 | var tok = this.advance();
|
785 | var attrs = tok.attrs;
|
786 |
|
787 | if (tok.selfClosing) tag.selfClosing = true;
|
788 |
|
789 | for (var i = 0; i < attrs.length; i++) {
|
790 | tag.setAttribute(attrs[i].name, attrs[i].val, attrs[i].escaped);
|
791 | }
|
792 | continue;
|
793 | case '&attributes':
|
794 | var tok = this.advance();
|
795 | tag.addAttributes(tok.val);
|
796 | break;
|
797 | default:
|
798 | break out;
|
799 | }
|
800 | }
|
801 |
|
802 |
|
803 | if ('dot' == this.peek().type) {
|
804 | tag.textOnly = true;
|
805 | this.advance();
|
806 | }
|
807 |
|
808 |
|
809 | switch (this.peek().type) {
|
810 | case 'text':
|
811 | tag.block.push(this.parseText());
|
812 | break;
|
813 | case 'code':
|
814 | tag.code = this.parseCode();
|
815 | break;
|
816 | case ':':
|
817 | this.advance();
|
818 | tag.block = new nodes.Block;
|
819 | tag.block.push(this.parseExpr());
|
820 | break;
|
821 | case 'newline':
|
822 | case 'indent':
|
823 | case 'outdent':
|
824 | case 'eos':
|
825 | case 'pipeless-text':
|
826 | break;
|
827 | default:
|
828 | throw new Error('Unexpected token `' + this.peek().type + '` expected `text`, `code`, `:`, `newline` or `eos`')
|
829 | }
|
830 |
|
831 |
|
832 | while ('newline' == this.peek().type) this.advance();
|
833 |
|
834 |
|
835 | if (tag.textOnly) {
|
836 | tag.block = this.parseTextBlock() || new nodes.Block();
|
837 | } else if ('indent' == this.peek().type) {
|
838 | var block = this.block();
|
839 | for (var i = 0, len = block.nodes.length; i < len; ++i) {
|
840 | tag.block.push(block.nodes[i]);
|
841 | }
|
842 | }
|
843 |
|
844 | return tag;
|
845 | }
|
846 | };
|