Ein Projekt das es ermöglicht Beerpong über das Internet von zwei unabhängigen positionen aus zu spielen. Entstehung im Rahmen einer Praktikumsaufgabe im Fach Interaktion.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

lexer.js 21KB

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