UNPKG

30.6 kBJavaScriptView Raw
1
2var debug = require('debug');
3
4var tks = require('./tokens');
5var nodestuff = require('./nodestuff');
6var error = require('./error');
7var namer = require('./util/fn-namer');
8
9var ProgramNode = namer(require('./nodes/program'));
10var TextNode = namer(require('./nodes/text'));
11var MarkupNode = namer(require('./nodes/markup'));
12var MarkupCommentNode = namer(require('./nodes/markupcomment'));
13var MarkupContentNode = namer(require('./nodes/markupcontent'));
14var MarkupAttributeNode = namer(require('./nodes/markupattribute'));
15var ExpressionNode = namer(require('./nodes/expression'));
16var ExplicitExpressionNode = namer(require('./nodes/explicitexpression'));
17var IndexExpressionNode = namer(require('./nodes/indexexpression'));
18var LocationNode = namer(require('./nodes/location'));
19var BlockNode = namer(require('./nodes/block'));
20var CommentNode = namer(require('./nodes/comment'));
21var RegexNode = namer(require('./nodes/regex'));
22
23function Parser(opts) {
24 this.lg = debug('vash:parser');
25 this.tokens = [];
26 this.deferredTokens = [];
27 this.node = null;
28 this.stack = [];
29 this.inputText = '';
30 this.opts = opts || {};
31 this.previousNonWhitespace = null
32}
33
34module.exports = Parser;
35
36Parser.prototype.decorateError = function(err, line, column) {
37 err.message = ''
38 + err.message
39 + ' at template line ' + line
40 + ', column ' + column + '\n\n'
41 + 'Context: \n'
42 + error.context(this.inputText, line, column, '\n')
43 + '\n';
44
45 return err;
46}
47
48Parser.prototype.write = function(tokens) {
49 if (!Array.isArray(tokens)) tokens = [tokens];
50 this.inputText += tokens.map(function(tok) { return tok.val; }).join('');
51 this.tokens.unshift.apply(this.tokens, tokens.reverse());
52}
53
54Parser.prototype.read = function() {
55 if (!this.tokens.length && !this.deferredTokens.length) return null;
56
57 if (!this.node) {
58 this.openNode(new ProgramNode());
59 this.openNode(new MarkupNode(), this.node.body);
60 this.node._finishedOpen = true;
61 this.node.name = 'text';
62 updateLoc(this.node, { line: 0, chr: 0 })
63 this.openNode(new MarkupContentNode(), this.node.values);
64 updateLoc(this.node, { line: 0, chr: 0 })
65 }
66
67 var curr = this.deferredTokens.pop() || this.tokens.pop();
68
69 // To find this value we must search through both deferred and
70 // non-deferred tokens, since there could be more than just 3
71 // deferred tokens.
72 // nextNonWhitespaceOrNewline
73 var nnwon = null;
74
75 for (var i = this.deferredTokens.length-1; i >= 0; i--) {
76 if (
77 nnwon
78 && nnwon.type !== tks.WHITESPACE
79 && nnwon.type !== tks.NEWLINE
80 ) break;
81 nnwon = this.deferredTokens[i];
82 }
83
84 for (var i = this.tokens.length-1; i >= 0; i--) {
85 if (
86 nnwon
87 && nnwon.type !== tks.WHITESPACE
88 && nnwon.type !== tks.NEWLINE
89 ) break;
90 nnwon = this.tokens[i];
91 }
92
93 var next = this.deferredTokens.pop() || this.tokens.pop();
94 var ahead = this.deferredTokens.pop() || this.tokens.pop();
95
96 var dispatch = 'continue' + this.node.constructor.name;
97
98 this.lg('Read: %s', dispatch);
99 this.lg(' curr %s', curr);
100 this.lg(' next %s', next);
101 this.lg(' ahead %s', ahead);
102 this.lg(' nnwon %s', nnwon);
103
104 if (curr._considerEscaped) {
105 this.lg(' Previous token was marked as escaping');
106 }
107
108 var consumed = this[dispatch](this.node, curr, next, ahead, nnwon);
109
110 if (ahead) {
111 // ahead may be undefined when about to run out of tokens.
112 this.deferredTokens.push(ahead);
113 }
114
115 if (next) {
116 // Next may be undefined when about to run out of tokens.
117 this.deferredTokens.push(next);
118 }
119
120 if (!consumed) {
121 this.lg('Deferring curr %s', curr);
122 this.deferredTokens.push(curr);
123 } else {
124
125 if (curr.type !== tks.WHITESPACE) {
126 this.lg('set previousNonWhitespace %s', curr);
127 this.previousNonWhitespace = curr;
128 }
129
130 // Poor man's ASI.
131 if (curr.type === tks.NEWLINE) {
132 this.lg('set previousNonWhitespace %s', null);
133 this.previousNonWhitespace = null;
134 }
135
136 if (!curr._considerEscaped && curr.type === tks.BACKSLASH) {
137 next._considerEscaped = true;
138 }
139 }
140}
141
142Parser.prototype.checkStack = function() {
143 // Throw if something is unclosed that should be.
144 var i = this.stack.length-1;
145 var node;
146 var msg;
147 // A full AST is always:
148 // Program, Markup, MarkupContent, ...
149 while(i >= 2) {
150 node = this.stack[i];
151 if (node.endOk && !node.endOk()) {
152 // Attempt to make the error readable
153 delete node.values;
154 msg = 'Found unclosed ' + node.type;
155 var err = new Error(msg);
156 err.name = 'UnclosedNodeError';
157 throw this.decorateError(
158 err,
159 node.startloc.line,
160 node.startloc.column);
161 }
162 i--;
163 }
164}
165
166// This is purely a utility for debugging, to more easily inspect
167// what happened while parsing something.
168Parser.prototype.flag = function(node, name, value) {
169 var printVal = (value && typeof value === 'object')
170 ? value.type
171 : value;
172 this.lg('Flag %s on node %s was %s now %s',
173 name, node.type, node[name], printVal);
174 node[name] = value;
175}
176
177Parser.prototype.dumpAST = function() {
178 if (!this.stack.length) {
179 var msg = 'No AST to dump.';
180 throw new Error(msg);
181 }
182
183 return JSON.stringify(this.stack[0], null, ' ');
184}
185
186Parser.prototype.openNode = function(node, opt_insertArr) {
187 this.stack.push(node);
188 this.lg('Opened node %s from %s',
189 node.type, (this.node ? this.node.type : null));
190 this.node = node;
191
192 if (opt_insertArr) {
193 opt_insertArr.push(node);
194 }
195
196 return node;
197}
198
199Parser.prototype.closeNode = function(node) {
200 var toClose = this.stack[this.stack.length-1];
201 if (node !== toClose) {
202 var msg = 'InvalidCloseAction: '
203 + 'Expected ' + node.type + ' in stack, instead found '
204 + toClose.type;
205 throw new Error(msg);
206 }
207
208 this.stack.pop();
209 var last = this.stack[this.stack.length-1];
210
211 this.lg('Closing node %s (%s), returning to node %s',
212 node.type, node.name, last.type)
213
214 this.node = last;
215}
216
217Parser.prototype.continueCommentNode = function(node, curr, next) {
218 var valueNode = ensureTextNode(node.values);
219
220 if (curr.type === tks.AT_STAR_OPEN && !node._waitingForClose) {
221 this.flag(node, '_waitingForClose', tks.AT_STAR_CLOSE)
222 updateLoc(node, curr);
223 return true;
224 }
225
226 if (curr.type === node._waitingForClose) {
227 this.flag(node, '_waitingForClose', null)
228 updateLoc(node, curr);
229 this.closeNode(node);
230 return true;
231 }
232
233 if (curr.type === tks.DOUBLE_FORWARD_SLASH && !node._waitingForClose){
234 this.flag(node, '_waitingForClose', tks.NEWLINE);
235 updateLoc(node, curr);
236 return true;
237 }
238
239 appendTextValue(valueNode, curr);
240 return true;
241}
242
243Parser.prototype.continueMarkupNode = function(node, curr, next) {
244 var valueNode = node.values[node.values.length-1];
245
246 if (curr.type === tks.LT_SIGN && !node._finishedOpen) {
247 updateLoc(node, curr);
248 return true;
249 }
250
251 if (
252 !node._finishedOpen
253 && curr.type !== tks.GT_SIGN
254 && curr.type !== tks.LT_SIGN
255 && curr.type !== tks.WHITESPACE
256 && curr.type !== tks.NEWLINE
257 && curr.type !== tks.HTML_TAG_VOID_CLOSE
258 ) {
259
260 // Assume tag name
261
262 if (
263 curr.type === tks.AT
264 && !curr._considerEscaped
265 && next
266 && next.type === tks.AT
267 ) {
268 next._considerEscaped = true;
269 return true;
270 }
271
272 if (curr.type === tks.AT && !curr._considerEscaped) {
273 this.flag(node, 'expression', this.openNode(new ExpressionNode()));
274 updateLoc(node.expression, curr);
275 return true;
276 }
277
278 node.name = node.name
279 ? node.name + curr.val
280 : curr.val;
281 updateLoc(node, curr);
282 return true;
283 }
284
285 if (curr.type === tks.GT_SIGN && !node._waitingForFinishedClose) {
286 this.flag(node, '_finishedOpen', true);
287
288 if (MarkupNode.isVoid(node.name)) {
289 this.flag(node, 'isVoid', true);
290 this.closeNode(node);
291 updateLoc(node, curr);
292 } else {
293 valueNode = this.openNode(new MarkupContentNode(), node.values);
294 updateLoc(valueNode, curr);
295 }
296
297 return true;
298 }
299
300 if (curr.type === tks.GT_SIGN && node._waitingForFinishedClose) {
301 this.flag(node, '_waitingForFinishedClose', false);
302 this.closeNode(node);
303 updateLoc(node, curr);
304 return true;
305 }
306
307 // </VOID
308 if (
309 curr.type === tks.HTML_TAG_CLOSE
310 && next
311 && next.type === tks.IDENTIFIER
312 && MarkupNode.isVoid(next.val)
313 ) {
314 throw newUnexpectedClosingTagError(this, curr, curr.val + next.val);
315 }
316
317 // </
318 if (curr.type === tks.HTML_TAG_CLOSE) {
319 this.flag(node, '_waitingForFinishedClose', true);
320 this.flag(node, 'isClosed', true);
321 return true;
322 }
323
324 // -->
325 if (curr.type === tks.HTML_COMMENT_CLOSE) {
326 this.flag(node, '_waitingForFinishedClose', false);
327 this.closeNode(node);
328 return false;
329 }
330
331 if (curr.type === tks.HTML_TAG_VOID_CLOSE) {
332 this.closeNode(node);
333 this.flag(node, 'isVoid', true);
334 this.flag(node, 'voidClosed', true);
335 this.flag(node, 'isClosed', true);
336 updateLoc(node, curr);
337 return true;
338 }
339
340 if (node._waitingForFinishedClose) {
341 this.lg('Ignoring %s while waiting for closing GT_SIGN',
342 curr);
343 return true;
344 }
345
346 if (
347 (curr.type === tks.WHITESPACE || curr.type === tks.NEWLINE)
348 && !node._finishedOpen
349 && next.type !== tks.HTML_TAG_VOID_CLOSE
350 && next.type !== tks.GT_SIGN
351 && next.type !== tks.NEWLINE
352 && next.type !== tks.WHITESPACE
353 ) {
354 // enter attribute
355 valueNode = this.openNode(new MarkupAttributeNode(), node.attributes);
356 updateLoc(valueNode, curr);
357 return true;
358 }
359
360 // Whitespace between attributes should be ignored.
361 if (
362 (curr.type === tks.WHITESPACE || curr.type === tks.NEWLINE)
363 && !node._finishedOpen
364 ) {
365 updateLoc(node, curr);
366 return true;
367 }
368
369 // Can't really have non-markupcontent within markup, so implicitly open
370 // a node. #68.
371 if (node._finishedOpen) {
372 valueNode = this.openNode(new MarkupContentNode(), this.node.values);
373 updateLoc(valueNode, curr);
374 return false; // defer
375 }
376
377 // Default
378
379 //valueNode = ensureTextNode(node.values);
380 //appendTextValue(valueNode, curr);
381 //return true;
382}
383
384Parser.prototype.continueMarkupAttributeNode = function(node, curr, next) {
385
386 var valueNode;
387
388 if (
389 curr.type === tks.AT
390 && !curr._considerEscaped
391 && next
392 && next.type === tks.AT
393 ) {
394 next._considerEscaped = true;
395 return true;
396 }
397
398 if (curr.type === tks.AT && !curr._considerEscaped) {
399 // To expression
400
401 valueNode = this.openNode(new ExpressionNode(), !node._finishedLeft
402 ? node.left
403 : node.right);
404
405 updateLoc(valueNode, curr);
406 return true;
407 }
408
409 // End of left, value only
410 if (
411 !node._expectRight
412 && (curr.type === tks.WHITESPACE
413 || curr.type === tks.GT_SIGN
414 || curr.type === tks.HTML_TAG_VOID_CLOSE)
415 ) {
416 this.flag(node, '_finishedLeft', true);
417 updateLoc(node, curr);
418 this.closeNode(node);
419 return false; // defer
420 }
421
422 // End of left.
423 if (curr.type === tks.EQUAL_SIGN && !node._finishedLeft) {
424 this.flag(node, '_finishedLeft', true);
425 this.flag(node, '_expectRight', true);
426 return true;
427 }
428
429 // Beginning of quoted value.
430 if (
431 node._expectRight
432 && !node.rightIsQuoted
433 && (curr.type === tks.DOUBLE_QUOTE
434 || curr.type === tks.SINGLE_QUOTE)
435 ) {
436 this.flag(node, 'rightIsQuoted', curr.val);
437 return true;
438 }
439
440 // End of quoted value.
441 if (node.rightIsQuoted === curr.val) {
442 updateLoc(node, curr);
443 this.closeNode(node);
444 return true;
445 }
446
447 // Default
448
449 if (!node._finishedLeft) {
450 valueNode = ensureTextNode(node.left);
451 } else {
452 valueNode = ensureTextNode(node.right);
453 }
454
455 appendTextValue(valueNode, curr);
456 return true;
457}
458
459Parser.prototype.continueMarkupContentNode = function(node, curr, next, ahead) {
460 var valueNode = ensureTextNode(node.values);
461
462 if (curr.type === tks.HTML_COMMENT_OPEN) {
463 valueNode = this.openNode(new MarkupCommentNode(), node.values);
464 updateLoc(valueNode, curr);
465 return false;
466 }
467
468 if (curr.type === tks.HTML_COMMENT_CLOSE) {
469 updateLoc(node, curr);
470 this.closeNode(node);
471 return false;
472 }
473
474 if (curr.type === tks.AT_COLON && !curr._considerEscaped) {
475 this.flag(node, '_waitingForNewline', true);
476 updateLoc(valueNode, curr);
477 return true;
478 }
479
480 if (curr.type === tks.NEWLINE && node._waitingForNewline === true) {
481 this.flag(node, '_waitingForNewline', false);
482 appendTextValue(valueNode, curr);
483 updateLoc(node, curr);
484 this.closeNode(node);
485 return true;
486 }
487
488 if (
489 curr.type === tks.AT
490 && !curr._considerEscaped
491 && next.type === tks.BRACE_OPEN
492 ) {
493 valueNode = this.openNode(new BlockNode(), node.values);
494 updateLoc(valueNode, curr);
495 return true;
496 }
497
498 if (
499 curr.type === tks.AT
500 && !curr._considerEscaped
501 && (next.type === tks.BLOCK_KEYWORD
502 || next.type === tks.FUNCTION)
503 ) {
504 valueNode = this.openNode(new BlockNode(), node.values);
505 updateLoc(valueNode, curr);
506 return true;
507 }
508
509 // Mark @@: or @@ as escaped.
510 if (
511 curr.type === tks.AT
512 && !curr._considerEscaped
513 && next
514 && (
515 next.type === tks.AT_COLON
516 || next.type === tks.AT
517 || next.type === tks.AT_STAR_OPEN
518 )
519 ) {
520 next._considerEscaped = true;
521 return true;
522 }
523
524 // @something
525 if (curr.type === tks.AT && !curr._considerEscaped) {
526 valueNode = this.openNode(new ExpressionNode(), node.values);
527 updateLoc(valueNode, curr);
528 return true;
529 }
530
531 if (curr.type === tks.AT_STAR_OPEN && !curr._considerEscaped) {
532 this.openNode(new CommentNode(), node.values);
533 return false;
534 }
535
536 var parent = this.stack[this.stack.length-2];
537
538 // If this MarkupContent is the direct child of a block, it has no way to
539 // know when to close. So in this case it should assume a } means it's
540 // done. Or if it finds a closing html tag, of course.
541 if (
542 curr.type === tks.HTML_TAG_CLOSE
543 || (curr.type === tks.BRACE_CLOSE
544 && parent && parent.type === 'VashBlock')
545 ) {
546 this.closeNode(node);
547 updateLoc(node, curr);
548 return false;
549 }
550
551 if (
552 curr.type === tks.LT_SIGN
553 && next
554 && (
555 // If next is an IDENTIFIER, then try to ensure that it's likely an HTML
556 // tag, which really can only be something like:
557 // <identifier>
558 // <identifer morestuff (whitespace)
559 // <identifier\n
560 // <identifier@
561 // <identifier-
562 // <identifier:identifier // XML namespaces etc etc
563 (next.type === tks.IDENTIFIER
564 && ahead
565 && (
566 ahead.type === tks.GT_SIGN
567 || ahead.type === tks.WHITESPACE
568 || ahead.type === tks.NEWLINE
569 || ahead.type === tks.AT
570 || ahead.type === tks.UNARY_OPERATOR
571 || ahead.type === tks.COLON
572 )
573 )
574 || next.type === tks.AT)
575 ) {
576 // TODO: possibly check for same tag name, and if HTML5 incompatible,
577 // such as p within p, then close current.
578 valueNode = this.openNode(new MarkupNode(), node.values);
579 updateLoc(valueNode, curr);
580 return false;
581 }
582
583 // Ignore whitespace if the direct parent is a block. This is for backwards
584 // compatibility with { @what() }, where the ' ' between ) and } should not
585 // be included as content. This rule should not be followed if the
586 // whitespace is contained within an @: escape or within favorText mode.
587 if (
588 curr.type === tks.WHITESPACE
589 && !node._waitingForNewline
590 && !this.opts.favorText
591 && parent
592 && parent.type === 'VashBlock'
593 ) {
594 return true;
595 }
596
597 appendTextValue(valueNode, curr);
598 return true;
599}
600
601Parser.prototype.continueMarkupCommentNode = function(node, curr, next) {
602 var valueNode = node.values[node.values.length-1];
603
604 if (curr.type === tks.HTML_COMMENT_OPEN) {
605 this.flag(node, '_finishedOpen', true);
606 this.flag(node, '_waitingForClose', tks.HTML_COMMENT_CLOSE);
607 updateLoc(node, curr);
608 valueNode = this.openNode(new MarkupContentNode(), node.values);
609 return true;
610 }
611
612 if (curr.type === tks.HTML_COMMENT_CLOSE && node._finishedOpen) {
613 this.flag(node, '_waitingForClose', null);
614 updateLoc(node, curr);
615 this.closeNode(node);
616 return true;
617 }
618
619 valueNode = ensureTextNode(node.values);
620 appendTextValue(valueNode, curr);
621 return true;
622}
623
624Parser.prototype.continueExpressionNode = function(node, curr, next) {
625 var valueNode = node.values[node.values.length-1];
626 var pnw = this.previousNonWhitespace;
627
628 if (
629 curr.type === tks.AT
630 && next.type === tks.HARD_PAREN_OPEN
631 ) {
632 // LEGACY: @[], which means a legacy escape to content.
633 updateLoc(node, curr);
634 this.closeNode(node);
635 return true;
636 }
637
638 if (curr.type === tks.PAREN_OPEN) {
639 this.openNode(new ExplicitExpressionNode(), node.values);
640 return false;
641 }
642
643 if (
644 curr.type === tks.HARD_PAREN_OPEN
645 && node.values[0]
646 && node.values[0].type === 'VashExplicitExpression'
647 ) {
648 // @()[0], hard parens should be content
649 updateLoc(node, curr);
650 this.closeNode(node);
651 return false;
652 }
653
654 if (
655 curr.type === tks.HARD_PAREN_OPEN
656 && next.type === tks.HARD_PAREN_CLOSE
657 ) {
658 // [], empty index should be content (php forms...)
659 updateLoc(node, curr);
660 this.closeNode(node);
661 return false;
662 }
663
664 if (curr.type === tks.HARD_PAREN_OPEN) {
665 this.openNode(new IndexExpressionNode(), node.values);
666 return false;
667 }
668
669 if (
670 curr.type === tks.FORWARD_SLASH
671 && pnw
672 && pnw.type === tks.AT
673 ) {
674 this.openNode(new RegexNode(), node.values)
675 return false;
676 }
677
678 // Default
679 // Consume only specific cases, otherwise close.
680
681 if (curr.type === tks.PERIOD && next && next.type === tks.IDENTIFIER) {
682 valueNode = ensureTextNode(node.values);
683 appendTextValue(valueNode, curr);
684 return true;
685 }
686
687 if (curr.type === tks.IDENTIFIER) {
688
689 if (node.values.length > 0 && valueNode && valueNode.type !== 'VashText') {
690 // Assume we just ended an explicit expression.
691 this.closeNode(node);
692 return false;
693 }
694
695 valueNode = ensureTextNode(node.values);
696 appendTextValue(valueNode, curr);
697 return true;
698 } else {
699 this.closeNode(node);
700 return false;
701 }
702}
703
704Parser.prototype.continueExplicitExpressionNode = function(node, curr, next) {
705
706 var valueNode = node.values[node.values.length-1];
707
708 if (
709 node.values.length === 0
710 && (curr.type === tks.AT || curr.type === tks.PAREN_OPEN)
711 ) {
712 // This is the beginning of the explicit (mark as consumed)
713 this.flag(node, '_waitingForParenClose', true);
714 updateLoc(node, curr);
715 return true;
716 }
717
718 if (curr.type === tks.PAREN_OPEN && !node._waitingForEndQuote) {
719 // New explicit expression
720 valueNode = this.openNode(new ExplicitExpressionNode(), node.values);
721 updateLoc(valueNode, curr);
722 // And do nothing with the token (mark as consumed)
723 return true;
724 }
725
726 if (curr.type === tks.PAREN_CLOSE && !node._waitingForEndQuote) {
727 // Close current explicit expression
728 this.flag(node, '_waitingForParenClose', false);
729 updateLoc(node, curr);
730 this.closeNode(node);
731 // And do nothing with the token (mark as consumed)
732 return true;
733 }
734
735 if (curr.type === tks.FUNCTION && !node._waitingForEndQuote) {
736 valueNode = this.openNode(new BlockNode(), node.values);
737 updateLoc(valueNode, curr);
738 return false;
739 }
740
741 if (
742 curr.type === tks.LT_SIGN
743 && next.type === tks.IDENTIFIER
744 && !node._waitingForEndQuote
745 ) {
746 // Markup within expression
747 valueNode = this.openNode(new MarkupNode(), node.values);
748 updateLoc(valueNode, curr);
749 return false;
750 }
751
752 var pnw = this.previousNonWhitespace;
753
754 if (
755 curr.type === tks.FORWARD_SLASH
756 && !node._waitingForEndQuote
757 && pnw
758 && pnw.type !== tks.IDENTIFIER
759 && pnw.type !== tks.NUMERAL
760 && pnw.type !== tks.PAREN_CLOSE
761 ) {
762 valueNode = this.openNode(new RegexNode(), node.values);
763 updateLoc(valueNode, curr);
764 return false;
765 }
766
767 // Default
768 valueNode = ensureTextNode(node.values);
769
770 if (
771 !node._waitingForEndQuote
772 && (curr.type === tks.SINGLE_QUOTE || curr.type === tks.DOUBLE_QUOTE)
773 ) {
774 this.flag(node, '_waitingForEndQuote', curr.val);
775 appendTextValue(valueNode, curr);
776 return true;
777 }
778
779 if (
780 curr.val === node._waitingForEndQuote
781 && !curr._considerEscaped
782 ) {
783 this.flag(node, '_waitingForEndQuote', null);
784 appendTextValue(valueNode, curr);
785 return true;
786 }
787
788 appendTextValue(valueNode, curr);
789 return true;
790}
791
792Parser.prototype.continueRegexNode = function(node, curr, next) {
793 var valueNode = ensureTextNode(node.values);
794
795 if (
796 curr.type === tks.FORWARD_SLASH
797 && !node._waitingForForwardSlash
798 && !curr._considerEscaped
799 ) {
800 // Start of regex.
801 this.flag(node, '_waitingForForwardSlash', true);
802 appendTextValue(valueNode, curr);
803 return true;
804 }
805
806 if (
807 curr.type === tks.FORWARD_SLASH
808 && node._waitingForForwardSlash
809 && !curr._considerEscaped
810 ) {
811 // "End" of regex.
812 this.flag(node, '_waitingForForwardSlash', null);
813 this.flag(node, '_waitingForFlags', true);
814 appendTextValue(valueNode, curr);
815 return true;
816 }
817
818 if (node._waitingForFlags) {
819 this.flag(node, '_waitingForFlags', null);
820 this.closeNode(node);
821
822 if (curr.type === tks.IDENTIFIER) {
823 appendTextValue(valueNode, curr);
824 return true;
825 } else {
826 return false;
827 }
828 }
829
830 if (
831 curr.type === tks.BACKSLASH
832 && !curr._considerEscaped
833 ) {
834 next._considerEscaped = true;
835 }
836
837 appendTextValue(valueNode, curr);
838 return true;
839}
840
841Parser.prototype.continueBlockNode = function(node, curr, next, ahead, nnwon) {
842
843 var valueNode = node.values[node.values.length-1];
844
845 if (curr.type === tks.AT_STAR_OPEN) {
846 this.openNode(new CommentNode(), node.body);
847 return false;
848 }
849
850 if (curr.type === tks.DOUBLE_FORWARD_SLASH && !node._waitingForEndQuote) {
851 this.openNode(new CommentNode(), node.body);
852 return false;
853 }
854
855 if (
856 curr.type === tks.AT_COLON
857 && (!node.hasBraces || node._reachedOpenBrace)
858 ) {
859 valueNode = this.openNode(new MarkupContentNode(), node.values);
860 return false;
861 }
862
863 if (
864 (curr.type === tks.BLOCK_KEYWORD || curr.type === tks.FUNCTION)
865 && !node._reachedOpenBrace
866 && !node.keyword
867 ) {
868 this.flag(node, 'keyword', curr.val);
869 return true;
870 }
871
872 if (
873 (curr.type === tks.BLOCK_KEYWORD || curr.type === tks.FUNCTION)
874 && !node._reachedOpenBrace
875 ) {
876 // Assume something like if (test) expressionstatement;
877 this.flag(node, 'hasBraces', false);
878 valueNode = this.openNode(new BlockNode(), node.values);
879 updateLoc(valueNode, curr);
880 return false;
881 }
882
883 if (
884 (curr.type === tks.BLOCK_KEYWORD || curr.type === tks.FUNCTION)
885 && !node._reachedCloseBrace
886 && node.hasBraces
887 && !node._waitingForEndQuote
888 ) {
889 valueNode = this.openNode(new BlockNode(), node.values);
890 updateLoc(valueNode, curr);
891 return false;
892 }
893
894 if (
895 (curr.type === tks.BLOCK_KEYWORD || curr.type === tks.FUNCTION)
896 && node._reachedCloseBrace
897 && !node._waitingForEndQuote
898 ) {
899 valueNode = this.openNode(new BlockNode(), node.tail);
900 updateLoc(valueNode, curr);
901 return false;
902 }
903
904 if (
905 curr.type === tks.BRACE_OPEN
906 && !node._reachedOpenBrace
907 && !node._waitingForEndQuote
908 ) {
909 this.flag(node, '_reachedOpenBrace', true);
910 this.flag(node, 'hasBraces', true);
911 if (this.opts.favorText) {
912 valueNode = this.openNode(new MarkupContentNode(), node.values);
913 updateLoc(valueNode, curr);
914 }
915 return true;
916 }
917
918 if (
919 curr.type === tks.BRACE_OPEN
920 && !node._waitingForEndQuote
921 ) {
922 valueNode = this.openNode(new BlockNode(), node.values);
923 updateLoc(valueNode, curr);
924 return false;
925 }
926
927 if (
928 curr.type === tks.BRACE_CLOSE
929 && node.hasBraces
930 && !node._reachedCloseBrace
931 && !node._waitingForEndQuote
932 ) {
933 updateLoc(node, curr);
934 this.flag(node, '_reachedCloseBrace', true);
935
936 // Try to leave whitespace where it belongs, and allow `else {` to
937 // be continued as the tail of this block.
938 if (
939 nnwon
940 && nnwon.type !== tks.BLOCK_KEYWORD
941 ) {
942 this.closeNode(node);
943 }
944
945 return true;
946 }
947
948 if (
949 curr.type === tks.BRACE_CLOSE
950 && !node.hasBraces
951 ) {
952 // Probably something like:
953 // @{ if() <span></span> }
954 this.closeNode(node);
955 updateLoc(node, curr);
956 return false;
957 }
958
959 if (
960 curr.type === tks.LT_SIGN
961 && (next.type === tks.AT || next.type === tks.IDENTIFIER)
962 && !node._waitingForEndQuote
963 && node._reachedCloseBrace
964 ) {
965 this.closeNode(node);
966 updateLoc(node, curr);
967 return false;
968 }
969
970 if (
971 curr.type === tks.LT_SIGN
972 && (next.type === tks.AT || next.type === tks.IDENTIFIER)
973 && !node._waitingForEndQuote
974 && !node._reachedCloseBrace
975 ) {
976 valueNode = this.openNode(new MarkupNode(), node.values);
977 updateLoc(valueNode, curr);
978 return false;
979 }
980
981 if (curr.type === tks.HTML_TAG_CLOSE) {
982 if (
983 (node.hasBraces && node._reachedCloseBrace)
984 || !node._reachedOpenBrace
985 ) {
986 updateLoc(node, curr);
987 this.closeNode(node);
988 return false;
989 }
990
991 // This is likely an invalid markup configuration, something like:
992 // @if(bla) { <img></img> }
993 // where <img> is an implicit void. Try to help the user in this
994 // specific case.
995 if (
996 next
997 && next.type === tks.IDENTIFIER
998 && MarkupNode.isVoid(next.val)
999 ){
1000 throw newUnexpectedClosingTagError(this, curr, curr.val + next.val);
1001 }
1002 }
1003
1004 if (
1005 curr.type === tks.AT
1006 && (next.type === tks.BLOCK_KEYWORD
1007 || next.type === tks.BRACE_OPEN
1008 || next.type === tks.FUNCTION)
1009 ) {
1010 // Backwards compatibility, allowing for @for() { @for() { @{ } } }
1011 valueNode = this.openNode(new BlockNode(), node.values);
1012 updateLoc(valueNode, curr);
1013 // TODO: shouldn't this need a more accurate target (tail, values, head)?
1014 return true;
1015 }
1016
1017 if (
1018 curr.type === tks.AT && next.type === tks.PAREN_OPEN
1019 ) {
1020 // Backwards compatibility, allowing for @for() { @(exp) }
1021 valueNode = this.openNode(new ExpressionNode(), node.values);
1022 updateLoc(valueNode, curr);
1023 return true;
1024 }
1025
1026 var attachmentNode;
1027
1028 if (node._reachedOpenBrace && node._reachedCloseBrace) {
1029 attachmentNode = node.tail;
1030 } else if (!node._reachedOpenBrace) {
1031 attachmentNode = node.head;
1032 } else {
1033 attachmentNode = node.values;
1034 }
1035
1036 valueNode = attachmentNode[attachmentNode.length-1];
1037
1038 if (
1039 curr.type === tks.AT
1040 && next.type === tks.IDENTIFIER
1041 && !node._waitingForEndQuote
1042 ) {
1043
1044 if (node._reachedCloseBrace) {
1045 this.closeNode(node);
1046 return false;
1047 } else {
1048 // something like @for() { @i }
1049 valueNode = this.openNode(new MarkupContentNode(), attachmentNode);
1050 updateLoc(valueNode, curr);
1051 return false;
1052 }
1053 }
1054
1055 if (
1056 curr.type !== tks.BLOCK_KEYWORD
1057 && curr.type !== tks.PAREN_OPEN
1058 && curr.type !== tks.WHITESPACE
1059 && curr.type !== tks.NEWLINE
1060 && node.hasBraces
1061 && node._reachedCloseBrace
1062 ) {
1063 // Handle if (test) { } content
1064 updateLoc(node, curr);
1065 this.closeNode(node);
1066 return false;
1067 }
1068
1069 if (curr.type === tks.PAREN_OPEN) {
1070 valueNode = this.openNode(new ExplicitExpressionNode(), attachmentNode);
1071 updateLoc(valueNode, curr);
1072 return false;
1073 }
1074
1075 valueNode = ensureTextNode(attachmentNode);
1076
1077 if (
1078 curr.val === node._waitingForEndQuote
1079 && !curr._considerEscaped
1080 ) {
1081 this.flag(node, '_waitingForEndQuote', null);
1082 appendTextValue(valueNode, curr);
1083 return true;
1084 }
1085
1086 if (
1087 !node._waitingForEndQuote
1088 && (curr.type === tks.DOUBLE_QUOTE || curr.type === tks.SINGLE_QUOTE)
1089 ) {
1090 this.flag(node, '_waitingForEndQuote', curr.val);
1091 appendTextValue(valueNode, curr);
1092 return true;
1093 }
1094
1095 var pnw = this.previousNonWhitespace;
1096
1097 if (
1098 curr.type === tks.FORWARD_SLASH
1099 && !node._waitingForEndQuote
1100 && pnw
1101 && pnw.type !== tks.IDENTIFIER
1102 && pnw.type !== tks.NUMERAL
1103 ) {
1104 // OH GAWD IT MIGHT BE A REGEX.
1105 valueNode = this.openNode(new RegexNode(), attachmentNode);
1106 updateLoc(valueNode, curr);
1107 return false;
1108 }
1109
1110 appendTextValue(valueNode, curr);
1111 return true;
1112}
1113
1114// These are really only used when continuing on an expression (for now):
1115// @model.what[0]()
1116// And apparently work for array literals...
1117Parser.prototype.continueIndexExpressionNode = function(node, curr, next) {
1118 var valueNode = node.values[node.values.length-1];
1119
1120 if (node._waitingForEndQuote) {
1121 if (curr.val === node._waitingForEndQuote) {
1122 this.flag(node, '_waitingForEndQuote', null);
1123 }
1124
1125 appendTextValue(valueNode, curr);
1126 return true;
1127 }
1128
1129 if (
1130 curr.type === tks.HARD_PAREN_OPEN
1131 && !valueNode
1132 ) {
1133 this.flag(node, '_waitingForHardParenClose', true);
1134 updateLoc(node, curr);
1135 return true;
1136 }
1137
1138 if (curr.type === tks.HARD_PAREN_CLOSE) {
1139 this.flag(node, '_waitingForHardParenClose', false);
1140 this.closeNode(node);
1141 updateLoc(node, curr);
1142 return true;
1143 }
1144
1145 if (curr.type === tks.PAREN_OPEN) {
1146 valueNode = this.openNode(new ExplicitExpressionNode(), node.values);
1147 updateLoc(valueNode, curr);
1148 return false;
1149 }
1150
1151 valueNode = ensureTextNode(node.values);
1152
1153 if (!node._waitingForEndQuote
1154 && (curr.type === tks.DOUBLE_QUOTE
1155 || curr.type === tks.SINGLE_QUOTE)
1156 ) {
1157 this.flag(node, '_waitingForEndQuote', curr.val);
1158 appendTextValue(valueNode, curr);
1159 return true;
1160 }
1161
1162 // Default.
1163
1164 appendTextValue(valueNode, curr);
1165 return true;
1166}
1167
1168function updateLoc(node, token) {
1169 var loc;
1170 loc = new LocationNode();
1171 loc.line = token.line;
1172 loc.column = token.chr;
1173
1174 if (node.startloc === null) {
1175 node.startloc = loc;
1176 }
1177
1178 node.endloc = loc;
1179}
1180
1181function ensureTextNode(valueList) {
1182 var valueNode = valueList[valueList.length-1];
1183
1184 if (!valueNode || valueNode.type !== 'VashText') {
1185 valueNode = new TextNode();
1186 valueList.push(valueNode);
1187 }
1188
1189 return valueNode;
1190}
1191
1192function appendTextValue(textNode, token) {
1193 if (!('value' in textNode)) {
1194 var msg = 'Expected TextNode but found ' + textNode.type
1195 + ' when appending token ' + token;
1196 throw new Error(msg);
1197 }
1198
1199 textNode.value += token.val;
1200 updateLoc(textNode, token);
1201}
1202
1203function newUnexpectedClosingTagError(parser, tok, tagName) {
1204 var err = new Error(''
1205 + 'Found a closing tag for a known void HTML element: '
1206 + tagName + '.');
1207 err.name = 'UnexpectedClosingTagError';
1208 return parser.decorateError(
1209 err,
1210 tok.line,
1211 tok.chr);
1212}
1213