1 | 'use strict';
|
2 |
|
3 | var utils = require('./utils');
|
4 | var characterParser = require('character-parser');
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 | var Lexer = module.exports = function Lexer(str, filename) {
|
16 | this.input = str.replace(/\r\n|\r/g, '\n');
|
17 | this.filename = filename;
|
18 | this.deferredTokens = [];
|
19 | this.lastIndents = 0;
|
20 | this.lineno = 1;
|
21 | this.stash = [];
|
22 | this.indentStack = [];
|
23 | this.indentRe = null;
|
24 | this.pipeless = false;
|
25 | };
|
26 |
|
27 |
|
28 | function assertExpression(exp) {
|
29 |
|
30 | Function('', 'return (' + exp + ')');
|
31 | }
|
32 | function assertNestingCorrect(exp) {
|
33 |
|
34 |
|
35 | var res = characterParser(exp)
|
36 | if (res.isNesting()) {
|
37 | throw new Error('Nesting must match on expression `' + exp + '`')
|
38 | }
|
39 | }
|
40 |
|
41 |
|
42 |
|
43 |
|
44 |
|
45 | Lexer.prototype = {
|
46 |
|
47 | |
48 |
|
49 |
|
50 |
|
51 |
|
52 |
|
53 |
|
54 |
|
55 |
|
56 | tok: function(type, val){
|
57 | return {
|
58 | type: type
|
59 | , line: this.lineno
|
60 | , val: val
|
61 | }
|
62 | },
|
63 |
|
64 | |
65 |
|
66 |
|
67 |
|
68 |
|
69 |
|
70 |
|
71 | consume: function(len){
|
72 | this.input = this.input.substr(len);
|
73 | },
|
74 |
|
75 | |
76 |
|
77 |
|
78 |
|
79 |
|
80 |
|
81 |
|
82 |
|
83 |
|
84 | scan: function(regexp, type){
|
85 | var captures;
|
86 | if (captures = regexp.exec(this.input)) {
|
87 | this.consume(captures[0].length);
|
88 | return this.tok(type, captures[1]);
|
89 | }
|
90 | },
|
91 |
|
92 | |
93 |
|
94 |
|
95 |
|
96 |
|
97 |
|
98 |
|
99 | defer: function(tok){
|
100 | this.deferredTokens.push(tok);
|
101 | },
|
102 |
|
103 | |
104 |
|
105 |
|
106 |
|
107 |
|
108 |
|
109 |
|
110 |
|
111 | lookahead: function(n){
|
112 | var fetch = n - this.stash.length;
|
113 | while (fetch-- > 0) this.stash.push(this.next());
|
114 | return this.stash[--n];
|
115 | },
|
116 |
|
117 | |
118 |
|
119 |
|
120 |
|
121 |
|
122 |
|
123 |
|
124 | bracketExpression: function(skip){
|
125 | skip = skip || 0;
|
126 | var start = this.input[skip];
|
127 | if (start != '(' && start != '{' && start != '[') throw new Error('unrecognized start character');
|
128 | var end = ({'(': ')', '{': '}', '[': ']'})[start];
|
129 | var range = characterParser.parseMax(this.input, {start: skip + 1});
|
130 | if (this.input[range.end] !== end) throw new Error('start character ' + start + ' does not match end character ' + this.input[range.end]);
|
131 | return range;
|
132 | },
|
133 |
|
134 | |
135 |
|
136 |
|
137 |
|
138 | stashed: function() {
|
139 | return this.stash.length
|
140 | && this.stash.shift();
|
141 | },
|
142 |
|
143 | |
144 |
|
145 |
|
146 |
|
147 | deferred: function() {
|
148 | return this.deferredTokens.length
|
149 | && this.deferredTokens.shift();
|
150 | },
|
151 |
|
152 | |
153 |
|
154 |
|
155 |
|
156 | eos: function() {
|
157 | if (this.input.length) return;
|
158 | if (this.indentStack.length) {
|
159 | this.indentStack.shift();
|
160 | return this.tok('outdent');
|
161 | } else {
|
162 | return this.tok('eos');
|
163 | }
|
164 | },
|
165 |
|
166 | |
167 |
|
168 |
|
169 |
|
170 | blank: function() {
|
171 | var captures;
|
172 | if (captures = /^\n *\n/.exec(this.input)) {
|
173 | this.consume(captures[0].length - 1);
|
174 | ++this.lineno;
|
175 | if (this.pipeless) return this.tok('text', '');
|
176 | return this.next();
|
177 | }
|
178 | },
|
179 |
|
180 | |
181 |
|
182 |
|
183 |
|
184 | comment: function() {
|
185 | var captures;
|
186 | if (captures = /^\/\/(-)?([^\n]*)/.exec(this.input)) {
|
187 | this.consume(captures[0].length);
|
188 | var tok = this.tok('comment', captures[2]);
|
189 | tok.buffer = '-' != captures[1];
|
190 | this.pipeless = true;
|
191 | return tok;
|
192 | }
|
193 | },
|
194 |
|
195 | |
196 |
|
197 |
|
198 |
|
199 | interpolation: function() {
|
200 | if (/^#\{/.test(this.input)) {
|
201 | var match;
|
202 | try {
|
203 | match = this.bracketExpression(1);
|
204 | } catch (ex) {
|
205 | return;
|
206 | }
|
207 |
|
208 | this.consume(match.end + 1);
|
209 | return this.tok('interpolation', match.src);
|
210 | }
|
211 | },
|
212 |
|
213 | |
214 |
|
215 |
|
216 |
|
217 | tag: function() {
|
218 | var captures;
|
219 | if (captures = /^(\w[-:\w]*)(\/?)/.exec(this.input)) {
|
220 | this.consume(captures[0].length);
|
221 | var tok, name = captures[1];
|
222 | if (':' == name[name.length - 1]) {
|
223 | name = name.slice(0, -1);
|
224 | tok = this.tok('tag', name);
|
225 | this.defer(this.tok(':'));
|
226 | while (' ' == this.input[0]) this.input = this.input.substr(1);
|
227 | } else {
|
228 | tok = this.tok('tag', name);
|
229 | }
|
230 | tok.selfClosing = !!captures[2];
|
231 | return tok;
|
232 | }
|
233 | },
|
234 |
|
235 | |
236 |
|
237 |
|
238 |
|
239 | filter: function() {
|
240 | var tok = this.scan(/^:([\w\-]+)/, 'filter');
|
241 | if (tok) {
|
242 | this.pipeless = true;
|
243 | return tok;
|
244 | }
|
245 | },
|
246 |
|
247 | |
248 |
|
249 |
|
250 |
|
251 | doctype: function() {
|
252 | if (this.scan(/^!!! *([^\n]+)?/, 'doctype')) {
|
253 | throw new Error('`!!!` is deprecated, you must now use `doctype`');
|
254 | }
|
255 | var node = this.scan(/^(?:doctype) *([^\n]+)?/, 'doctype');
|
256 | if (node && node.val && node.val.trim() === '5') {
|
257 | throw new Error('`doctype 5` is deprecated, you must now use `doctype html`');
|
258 | }
|
259 | return node;
|
260 | },
|
261 |
|
262 | |
263 |
|
264 |
|
265 |
|
266 | id: function() {
|
267 | return this.scan(/^#([\w-]+)/, 'id');
|
268 | },
|
269 |
|
270 | |
271 |
|
272 |
|
273 |
|
274 | className: function() {
|
275 | return this.scan(/^\.([\w-]+)/, 'class');
|
276 | },
|
277 |
|
278 | |
279 |
|
280 |
|
281 |
|
282 | text: function() {
|
283 | return this.scan(/^(?:\| ?| )([^\n]+)/, 'text') ||
|
284 | this.scan(/^\|?( )/, 'text') ||
|
285 | this.scan(/^(<[^\n]*)/, 'text');
|
286 | },
|
287 |
|
288 | textFail: function () {
|
289 | var tok;
|
290 | if (tok = this.scan(/^([^\.\n][^\n]+)/, 'text')) {
|
291 | console.warn('Warning: missing space before text for line ' + this.lineno +
|
292 | ' of jade file "' + this.filename + '"');
|
293 | return tok;
|
294 | }
|
295 | },
|
296 |
|
297 | |
298 |
|
299 |
|
300 |
|
301 | dot: function() {
|
302 | var match;
|
303 | if (match = this.scan(/^\./, 'dot')) {
|
304 | this.pipeless = true;
|
305 | return match;
|
306 | }
|
307 | },
|
308 |
|
309 | |
310 |
|
311 |
|
312 |
|
313 | "extends": function() {
|
314 | return this.scan(/^extends? +([^\n]+)/, 'extends');
|
315 | },
|
316 |
|
317 | |
318 |
|
319 |
|
320 |
|
321 | prepend: function() {
|
322 | var captures;
|
323 | if (captures = /^prepend +([^\n]+)/.exec(this.input)) {
|
324 | this.consume(captures[0].length);
|
325 | var mode = 'prepend'
|
326 | , name = captures[1]
|
327 | , tok = this.tok('block', name);
|
328 | tok.mode = mode;
|
329 | return tok;
|
330 | }
|
331 | },
|
332 |
|
333 | |
334 |
|
335 |
|
336 |
|
337 | append: function() {
|
338 | var captures;
|
339 | if (captures = /^append +([^\n]+)/.exec(this.input)) {
|
340 | this.consume(captures[0].length);
|
341 | var mode = 'append'
|
342 | , name = captures[1]
|
343 | , tok = this.tok('block', name);
|
344 | tok.mode = mode;
|
345 | return tok;
|
346 | }
|
347 | },
|
348 |
|
349 | |
350 |
|
351 |
|
352 |
|
353 | block: function() {
|
354 | var captures;
|
355 | if (captures = /^block\b *(?:(prepend|append) +)?([^\n]+)/.exec(this.input)) {
|
356 | this.consume(captures[0].length);
|
357 | var mode = captures[1] || 'replace'
|
358 | , name = captures[2]
|
359 | , tok = this.tok('block', name);
|
360 |
|
361 | tok.mode = mode;
|
362 | return tok;
|
363 | }
|
364 | },
|
365 |
|
366 | |
367 |
|
368 |
|
369 |
|
370 | mixinBlock: function() {
|
371 | var captures;
|
372 | if (captures = /^block\s*(\n|$)/.exec(this.input)) {
|
373 | this.consume(captures[0].length - 1);
|
374 | return this.tok('mixin-block');
|
375 | }
|
376 | },
|
377 |
|
378 | |
379 |
|
380 |
|
381 |
|
382 | 'yield': function() {
|
383 | return this.scan(/^yield */, 'yield');
|
384 | },
|
385 |
|
386 | |
387 |
|
388 |
|
389 |
|
390 | include: function() {
|
391 | return this.scan(/^include +([^\n]+)/, 'include');
|
392 | },
|
393 |
|
394 | |
395 |
|
396 |
|
397 |
|
398 | includeFiltered: function() {
|
399 | var captures;
|
400 | if (captures = /^include:([\w\-]+)([\( ])/.exec(this.input)) {
|
401 | this.consume(captures[0].length - 1);
|
402 | var filter = captures[1];
|
403 | var attrs = captures[2] === '(' ? this.attrs() : null;
|
404 | if (!(captures[2] === ' ' || this.input[0] === ' ')) {
|
405 | throw new Error('expected space after include:filter but got ' + JSON.stringify(this.input[0]));
|
406 | }
|
407 | captures = /^ *([^\n]+)/.exec(this.input);
|
408 | if (!captures || captures[1].trim() === '') {
|
409 | throw new Error('missing path for include:filter');
|
410 | }
|
411 | this.consume(captures[0].length);
|
412 | var path = captures[1];
|
413 | var tok = this.tok('include', path);
|
414 | tok.filter = filter;
|
415 | tok.attrs = attrs;
|
416 | return tok;
|
417 | }
|
418 | },
|
419 |
|
420 | |
421 |
|
422 |
|
423 |
|
424 | "case": function() {
|
425 | return this.scan(/^case +([^\n]+)/, 'case');
|
426 | },
|
427 |
|
428 | |
429 |
|
430 |
|
431 |
|
432 | when: function() {
|
433 | return this.scan(/^when +([^:\n]+)/, 'when');
|
434 | },
|
435 |
|
436 | |
437 |
|
438 |
|
439 |
|
440 | "default": function() {
|
441 | return this.scan(/^default */, 'default');
|
442 | },
|
443 |
|
444 | |
445 |
|
446 |
|
447 |
|
448 | call: function(){
|
449 |
|
450 | var tok, captures;
|
451 | if (captures = /^\+(([-\w]+)|(#\{))/.exec(this.input)) {
|
452 |
|
453 | if (captures[2]) {
|
454 |
|
455 | this.consume(captures[0].length);
|
456 | tok = this.tok('call', captures[2]);
|
457 | } else {
|
458 |
|
459 | var match;
|
460 | try {
|
461 | match = this.bracketExpression(2);
|
462 | } catch (ex) {
|
463 | return;
|
464 | }
|
465 | this.consume(match.end + 1);
|
466 | assertExpression(match.src);
|
467 | tok = this.tok('call', '#{'+match.src+'}');
|
468 | }
|
469 |
|
470 |
|
471 | if (captures = /^ *\(/.exec(this.input)) {
|
472 | try {
|
473 | var range = this.bracketExpression(captures[0].length - 1);
|
474 | if (!/^\s*[-\w]+ *=/.test(range.src)) {
|
475 | this.consume(range.end + 1);
|
476 | tok.args = range.src;
|
477 | }
|
478 | } catch (ex) {
|
479 |
|
480 | }
|
481 | }
|
482 |
|
483 | return tok;
|
484 | }
|
485 | },
|
486 |
|
487 | |
488 |
|
489 |
|
490 |
|
491 | mixin: function(){
|
492 | var captures;
|
493 | if (captures = /^mixin +([-\w]+)(?: *\((.*)\))? */.exec(this.input)) {
|
494 | this.consume(captures[0].length);
|
495 | var tok = this.tok('mixin', captures[1]);
|
496 | tok.args = captures[2];
|
497 | return tok;
|
498 | }
|
499 | },
|
500 |
|
501 | |
502 |
|
503 |
|
504 |
|
505 | conditional: function() {
|
506 | var captures;
|
507 | if (captures = /^(if|unless|else if|else)\b([^\n]*)/.exec(this.input)) {
|
508 | this.consume(captures[0].length);
|
509 | var type = captures[1]
|
510 | var js = captures[2];
|
511 | var isIf = false;
|
512 | var isElse = false;
|
513 |
|
514 | switch (type) {
|
515 | case 'if':
|
516 | assertExpression(js)
|
517 | js = 'if (' + js + ')';
|
518 | isIf = true;
|
519 | break;
|
520 | case 'unless':
|
521 | assertExpression(js)
|
522 | js = 'if (!(' + js + '))';
|
523 | isIf = true;
|
524 | break;
|
525 | case 'else if':
|
526 | assertExpression(js)
|
527 | js = 'else if (' + js + ')';
|
528 | isIf = true;
|
529 | isElse = true;
|
530 | break;
|
531 | case 'else':
|
532 | if (js && js.trim()) {
|
533 | throw new Error('`else` cannot have a condition, perhaps you meant `else if`');
|
534 | }
|
535 | js = 'else';
|
536 | isElse = true;
|
537 | break;
|
538 | }
|
539 | var tok = this.tok('code', js);
|
540 | tok.isElse = isElse;
|
541 | tok.isIf = isIf;
|
542 | tok.requiresBlock = true;
|
543 | return tok;
|
544 | }
|
545 | },
|
546 |
|
547 | |
548 |
|
549 |
|
550 |
|
551 | "while": function() {
|
552 | var captures;
|
553 | if (captures = /^while +([^\n]+)/.exec(this.input)) {
|
554 | this.consume(captures[0].length);
|
555 | assertExpression(captures[1])
|
556 | var tok = this.tok('code', 'while (' + captures[1] + ')');
|
557 | tok.requiresBlock = true;
|
558 | return tok;
|
559 | }
|
560 | },
|
561 |
|
562 | |
563 |
|
564 |
|
565 |
|
566 | each: function() {
|
567 | var captures;
|
568 | if (captures = /^(?:- *)?(?:each|for) +([a-zA-Z_$][\w$]*)(?: *, *([a-zA-Z_$][\w$]*))? * in *([^\n]+)/.exec(this.input)) {
|
569 | this.consume(captures[0].length);
|
570 | var tok = this.tok('each', captures[1]);
|
571 | tok.key = captures[2] || '$index';
|
572 | assertExpression(captures[3])
|
573 | tok.code = captures[3];
|
574 | return tok;
|
575 | }
|
576 | },
|
577 |
|
578 | |
579 |
|
580 |
|
581 |
|
582 | code: function() {
|
583 | var captures;
|
584 | if (captures = /^(!?=|-)[ \t]*([^\n]+)/.exec(this.input)) {
|
585 | this.consume(captures[0].length);
|
586 | var flags = captures[1];
|
587 | captures[1] = captures[2];
|
588 | var tok = this.tok('code', captures[1]);
|
589 | tok.escape = flags.charAt(0) === '=';
|
590 | tok.buffer = flags.charAt(0) === '=' || flags.charAt(1) === '=';
|
591 | if (tok.buffer) assertExpression(captures[1])
|
592 | return tok;
|
593 | }
|
594 | },
|
595 |
|
596 | |
597 |
|
598 |
|
599 |
|
600 | attrs: function() {
|
601 | if ('(' == this.input.charAt(0)) {
|
602 | var index = this.bracketExpression().end
|
603 | , str = this.input.substr(1, index-1)
|
604 | , tok = this.tok('attrs');
|
605 |
|
606 | assertNestingCorrect(str);
|
607 |
|
608 | var quote = '';
|
609 | var interpolate = function (attr) {
|
610 | return attr.replace(/(\\)?#\{(.+)/g, function(_, escape, expr){
|
611 | if (escape) return _;
|
612 | try {
|
613 | var range = characterParser.parseMax(expr);
|
614 | if (expr[range.end] !== '}') return _.substr(0, 2) + interpolate(_.substr(2));
|
615 | assertExpression(range.src)
|
616 | return quote + " + (" + range.src + ") + " + quote + interpolate(expr.substr(range.end + 1));
|
617 | } catch (ex) {
|
618 | return _.substr(0, 2) + interpolate(_.substr(2));
|
619 | }
|
620 | });
|
621 | }
|
622 |
|
623 | this.consume(index + 1);
|
624 | tok.attrs = [];
|
625 |
|
626 | var escapedAttr = true
|
627 | var key = '';
|
628 | var val = '';
|
629 | var interpolatable = '';
|
630 | var state = characterParser.defaultState();
|
631 | var loc = 'key';
|
632 | var isEndOfAttribute = function (i) {
|
633 | if (key.trim() === '') return false;
|
634 | if (i === str.length) return true;
|
635 | if (loc === 'key') {
|
636 | if (str[i] === ' ' || str[i] === '\n') {
|
637 | for (var x = i; x < str.length; x++) {
|
638 | if (str[x] != ' ' && str[x] != '\n') {
|
639 | if (str[x] === '=' || str[x] === '!' || str[x] === ',') return false;
|
640 | else return true;
|
641 | }
|
642 | }
|
643 | }
|
644 | return str[i] === ','
|
645 | } else if (loc === 'value' && !state.isNesting()) {
|
646 | try {
|
647 | Function('', 'return (' + val + ');');
|
648 | if (str[i] === ' ' || str[i] === '\n') {
|
649 | for (var x = i; x < str.length; x++) {
|
650 | if (str[x] != ' ' && str[x] != '\n') {
|
651 | if (characterParser.isPunctuator(str[x]) && str[x] != '"' && str[x] != "'") return false;
|
652 | else return true;
|
653 | }
|
654 | }
|
655 | }
|
656 | return str[i] === ',';
|
657 | } catch (ex) {
|
658 | return false;
|
659 | }
|
660 | }
|
661 | }
|
662 |
|
663 | this.lineno += str.split("\n").length - 1;
|
664 |
|
665 | for (var i = 0; i <= str.length; i++) {
|
666 | if (isEndOfAttribute(i)) {
|
667 | val = val.trim();
|
668 | if (val) assertExpression(val)
|
669 | key = key.trim();
|
670 | key = key.replace(/^['"]|['"]$/g, '');
|
671 | tok.attrs.push({
|
672 | name: key,
|
673 | val: '' == val ? true : val,
|
674 | escaped: escapedAttr
|
675 | });
|
676 | key = val = '';
|
677 | loc = 'key';
|
678 | escapedAttr = false;
|
679 | } else {
|
680 | switch (loc) {
|
681 | case 'key-char':
|
682 | if (str[i] === quote) {
|
683 | loc = 'key';
|
684 | if (i + 1 < str.length && [' ', ',', '!', '=', '\n'].indexOf(str[i + 1]) === -1)
|
685 | throw new Error('Unexpected character ' + str[i + 1] + ' expected ` `, `\\n`, `,`, `!` or `=`');
|
686 | } else {
|
687 | key += str[i];
|
688 | }
|
689 | break;
|
690 | case 'key':
|
691 | if (key === '' && (str[i] === '"' || str[i] === "'")) {
|
692 | loc = 'key-char';
|
693 | quote = str[i];
|
694 | } else if (str[i] === '!' || str[i] === '=') {
|
695 | escapedAttr = str[i] !== '!';
|
696 | if (str[i] === '!') i++;
|
697 | if (str[i] !== '=') throw new Error('Unexpected character ' + str[i] + ' expected `=`');
|
698 | loc = 'value';
|
699 | state = characterParser.defaultState();
|
700 | } else {
|
701 | key += str[i]
|
702 | }
|
703 | break;
|
704 | case 'value':
|
705 | state = characterParser.parseChar(str[i], state);
|
706 | if (state.isString()) {
|
707 | loc = 'string';
|
708 | quote = str[i];
|
709 | interpolatable = str[i];
|
710 | } else {
|
711 | val += str[i];
|
712 | }
|
713 | break;
|
714 | case 'string':
|
715 | state = characterParser.parseChar(str[i], state);
|
716 | interpolatable += str[i];
|
717 | if (!state.isString()) {
|
718 | loc = 'value';
|
719 | val += interpolate(interpolatable);
|
720 | }
|
721 | break;
|
722 | }
|
723 | }
|
724 | }
|
725 |
|
726 | if ('/' == this.input.charAt(0)) {
|
727 | this.consume(1);
|
728 | tok.selfClosing = true;
|
729 | }
|
730 |
|
731 | return tok;
|
732 | }
|
733 | },
|
734 |
|
735 | |
736 |
|
737 |
|
738 | attributesBlock: function () {
|
739 | var captures;
|
740 | if (/^&attributes\b/.test(this.input)) {
|
741 | this.consume(11);
|
742 | var args = this.bracketExpression();
|
743 | this.consume(args.end + 1);
|
744 | return this.tok('&attributes', args.src);
|
745 | }
|
746 | },
|
747 |
|
748 | |
749 |
|
750 |
|
751 |
|
752 | indent: function() {
|
753 | var captures, re;
|
754 |
|
755 |
|
756 | if (this.indentRe) {
|
757 | captures = this.indentRe.exec(this.input);
|
758 |
|
759 | } else {
|
760 |
|
761 | re = /^\n(\t*) */;
|
762 | captures = re.exec(this.input);
|
763 |
|
764 |
|
765 | if (captures && !captures[1].length) {
|
766 | re = /^\n( *)/;
|
767 | captures = re.exec(this.input);
|
768 | }
|
769 |
|
770 |
|
771 | if (captures && captures[1].length) this.indentRe = re;
|
772 | }
|
773 |
|
774 | if (captures) {
|
775 | var tok
|
776 | , indents = captures[1].length;
|
777 |
|
778 | ++this.lineno;
|
779 | this.consume(indents + 1);
|
780 |
|
781 | if (' ' == this.input[0] || '\t' == this.input[0]) {
|
782 | throw new Error('Invalid indentation, you can use tabs or spaces but not both');
|
783 | }
|
784 |
|
785 |
|
786 | if ('\n' == this.input[0]) {
|
787 | this.pipeless = false;
|
788 | return this.tok('newline');
|
789 | }
|
790 |
|
791 |
|
792 | if (this.indentStack.length && indents < this.indentStack[0]) {
|
793 | while (this.indentStack.length && this.indentStack[0] > indents) {
|
794 | this.stash.push(this.tok('outdent'));
|
795 | this.indentStack.shift();
|
796 | }
|
797 | tok = this.stash.pop();
|
798 |
|
799 | } else if (indents && indents != this.indentStack[0]) {
|
800 | this.indentStack.unshift(indents);
|
801 | tok = this.tok('indent', indents);
|
802 |
|
803 | } else {
|
804 | tok = this.tok('newline');
|
805 | }
|
806 |
|
807 | this.pipeless = false;
|
808 | return tok;
|
809 | }
|
810 | },
|
811 |
|
812 | |
813 |
|
814 |
|
815 |
|
816 |
|
817 | pipelessText: function() {
|
818 | if (!this.pipeless) return;
|
819 | var captures, re;
|
820 |
|
821 |
|
822 | if (this.indentRe) {
|
823 | captures = this.indentRe.exec(this.input);
|
824 |
|
825 | } else {
|
826 |
|
827 | re = /^\n(\t*) */;
|
828 | captures = re.exec(this.input);
|
829 |
|
830 |
|
831 | if (captures && !captures[1].length) {
|
832 | re = /^\n( *)/;
|
833 | captures = re.exec(this.input);
|
834 | }
|
835 |
|
836 |
|
837 | if (captures && captures[1].length) this.indentRe = re;
|
838 | }
|
839 |
|
840 | var indents = captures && captures[1].length;
|
841 | if (indents && (this.indentStack.length === 0 || indents > this.indentStack[0])) {
|
842 | var indent = captures[1];
|
843 | var line;
|
844 | var tokens = [];
|
845 | var isMatch;
|
846 | do {
|
847 |
|
848 | var i = this.input.substr(1).indexOf('\n');
|
849 | if (-1 == i) i = this.input.length - 1;
|
850 | var str = this.input.substr(1, i);
|
851 | isMatch = str.substr(0, indent.length) === indent || !str.trim();
|
852 | if (isMatch) {
|
853 |
|
854 | this.consume(str.length + 1);
|
855 | tokens.push(str.substr(indent.length));
|
856 | }
|
857 | } while(this.input.length && isMatch);
|
858 | while (this.input.length === 0 && tokens[tokens.length - 1] === '') tokens.pop();
|
859 | return this.tok('pipeless-text', tokens);
|
860 | }
|
861 | },
|
862 |
|
863 | |
864 |
|
865 |
|
866 |
|
867 | colon: function() {
|
868 | return this.scan(/^: */, ':');
|
869 | },
|
870 |
|
871 | fail: function () {
|
872 | throw new Error('unexpected text ' + this.input.substr(0, 5));
|
873 | },
|
874 |
|
875 | |
876 |
|
877 |
|
878 |
|
879 |
|
880 |
|
881 |
|
882 |
|
883 | advance: function(){
|
884 | return this.stashed()
|
885 | || this.next();
|
886 | },
|
887 |
|
888 | |
889 |
|
890 |
|
891 |
|
892 |
|
893 |
|
894 |
|
895 | next: function() {
|
896 | return this.deferred()
|
897 | || this.blank()
|
898 | || this.eos()
|
899 | || this.pipelessText()
|
900 | || this.yield()
|
901 | || this.doctype()
|
902 | || this.interpolation()
|
903 | || this["case"]()
|
904 | || this.when()
|
905 | || this["default"]()
|
906 | || this["extends"]()
|
907 | || this.append()
|
908 | || this.prepend()
|
909 | || this.block()
|
910 | || this.mixinBlock()
|
911 | || this.include()
|
912 | || this.includeFiltered()
|
913 | || this.mixin()
|
914 | || this.call()
|
915 | || this.conditional()
|
916 | || this.each()
|
917 | || this["while"]()
|
918 | || this.tag()
|
919 | || this.filter()
|
920 | || this.code()
|
921 | || this.id()
|
922 | || this.className()
|
923 | || this.attrs()
|
924 | || this.attributesBlock()
|
925 | || this.indent()
|
926 | || this.text()
|
927 | || this.comment()
|
928 | || this.colon()
|
929 | || this.dot()
|
930 | || this.textFail()
|
931 | || this.fail();
|
932 | }
|
933 | };
|