| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520 | 
							- 'use strict';
 
- var assert = require('assert');
 
- var isExpression = require('is-expression');
 
- var characterParser = require('character-parser');
 
- var error = require('pug-error');
 
- module.exports = lex;
 
- module.exports.Lexer = Lexer;
 
- function lex(str, options) {
 
-   var lexer = new Lexer(str, options);
 
-   return JSON.parse(JSON.stringify(lexer.getTokens()));
 
- }
 
- /**
 
-  * Initialize `Lexer` with the given `str`.
 
-  *
 
-  * @param {String} str
 
-  * @param {String} filename
 
-  * @api private
 
-  */
 
- function Lexer(str, options) {
 
-   options = options || {};
 
-   if (typeof str !== 'string') {
 
-     throw new Error('Expected source code to be a string but got "' + (typeof str) + '"')
 
-   }
 
-   if (typeof options !== 'object') {
 
-     throw new Error('Expected "options" to be an object but got "' + (typeof options) + '"')
 
-   }
 
-   //Strip any UTF-8 BOM off of the start of `str`, if it exists.
 
-   str = str.replace(/^\uFEFF/, '');
 
-   this.input = str.replace(/\r\n|\r/g, '\n');
 
-   this.originalInput = this.input;
 
-   this.filename = options.filename;
 
-   this.interpolated = options.interpolated || false;
 
-   this.lineno = options.startingLine || 1;
 
-   this.colno = options.startingColumn || 1;
 
-   this.plugins = options.plugins || [];
 
-   this.indentStack = [0];
 
-   this.indentRe = null;
 
-   // If #{}, !{} or #[] syntax is allowed when adding text
 
-   this.interpolationAllowed = true;
 
-   this.whitespaceRe = /[ \n\t]/;
 
-   this.tokens = [];
 
-   this.ended = false;
 
- };
 
- /**
 
-  * Lexer prototype.
 
-  */
 
- Lexer.prototype = {
 
-   constructor: Lexer,
 
-   error: function (code, message) {
 
-     var err = error(code, message, {line: this.lineno, column: this.colno, filename: this.filename, src: this.originalInput});
 
-     throw err;
 
-   },
 
-   assert: function (value, message) {
 
-     if (!value) this.error('ASSERT_FAILED', message);
 
-   },
 
-   isExpression: function (exp) {
 
-     return isExpression(exp, {
 
-       throw: true
 
-     });
 
-   },
 
-   assertExpression: function (exp, noThrow) {
 
-     //this verifies that a JavaScript expression is valid
 
-     try {
 
-       this.callLexerFunction('isExpression', exp);
 
-       return true;
 
-     } catch (ex) {
 
-       if (noThrow) return false;
 
-       // not coming from acorn
 
-       if (!ex.loc) throw ex;
 
-       this.incrementLine(ex.loc.line - 1);
 
-       this.incrementColumn(ex.loc.column);
 
-       var msg = 'Syntax Error: ' + ex.message.replace(/ \([0-9]+:[0-9]+\)$/, '');
 
-       this.error('SYNTAX_ERROR', msg);
 
-     }
 
-   },
 
-   assertNestingCorrect: function (exp) {
 
-     //this verifies that code is properly nested, but allows
 
-     //invalid JavaScript such as the contents of `attributes`
 
-     var res = characterParser(exp)
 
-     if (res.isNesting()) {
 
-       this.error('INCORRECT_NESTING', 'Nesting must match on expression `' + exp + '`')
 
-     }
 
-   },
 
-   /**
 
-    * Construct a token with the given `type` and `val`.
 
-    *
 
-    * @param {String} type
 
-    * @param {String} val
 
-    * @return {Object}
 
-    * @api private
 
-    */
 
-   tok: function(type, val){
 
-     var res = {
 
-       type: type, 
 
-       loc: {
 
-         start: {
 
-           line: this.lineno, 
 
-           column: this.colno
 
-         },
 
-         filename: this.filename
 
-       }
 
-     };
 
-     if (val !== undefined) res.val = val;
 
-     return res;
 
-   },
 
-   
 
-   /**
 
-    * Set the token's `loc.end` value.
 
-    * 
 
-    * @param {Object} tok
 
-    * @returns {Object}
 
-    * @api private
 
-    */
 
-   
 
-   tokEnd: function(tok){
 
-     tok.loc.end = {
 
-       line: this.lineno,
 
-       column: this.colno
 
-     };
 
-     return tok;
 
-   },
 
-   /**
 
-    * Increment `this.lineno` and reset `this.colno`.
 
-    *
 
-    * @param {Number} increment
 
-    * @api private
 
-    */
 
-   incrementLine: function(increment){
 
-     this.lineno += increment;
 
-     if (increment) this.colno = 1;
 
-   },
 
-   /**
 
-    * Increment `this.colno`.
 
-    *
 
-    * @param {Number} increment
 
-    * @api private
 
-    */
 
-   incrementColumn: function(increment){
 
-     this.colno += increment
 
-   },
 
-   /**
 
-    * Consume the given `len` of input.
 
-    *
 
-    * @param {Number} len
 
-    * @api private
 
-    */
 
-   consume: function(len){
 
-     this.input = this.input.substr(len);
 
-   },
 
-   /**
 
-    * Scan for `type` with the given `regexp`.
 
-    *
 
-    * @param {String} type
 
-    * @param {RegExp} regexp
 
-    * @return {Object}
 
-    * @api private
 
-    */
 
-   scan: function(regexp, type){
 
-     var captures;
 
-     if (captures = regexp.exec(this.input)) {
 
-       var len = captures[0].length;
 
-       var val = captures[1];
 
-       var diff = len - (val ? val.length : 0);
 
-       var tok = this.tok(type, val);
 
-       this.consume(len);
 
-       this.incrementColumn(diff);
 
-       return tok;
 
-     }
 
-   },
 
-   scanEndOfLine: function (regexp, type) {
 
-     var captures;
 
-     if (captures = regexp.exec(this.input)) {
 
-       var whitespaceLength = 0;
 
-       var whitespace;
 
-       var tok;
 
-       if (whitespace = /^([ ]+)([^ ]*)/.exec(captures[0])) {
 
-         whitespaceLength = whitespace[1].length;
 
-         this.incrementColumn(whitespaceLength);
 
-       }
 
-       var newInput = this.input.substr(captures[0].length);
 
-       if (newInput[0] === ':') {
 
-         this.input = newInput;
 
-         tok = this.tok(type, captures[1]);
 
-         this.incrementColumn(captures[0].length - whitespaceLength);
 
-         return tok;
 
-       }
 
-       if (/^[ \t]*(\n|$)/.test(newInput)) {
 
-         this.input = newInput.substr(/^[ \t]*/.exec(newInput)[0].length);
 
-         tok = this.tok(type, captures[1]);
 
-         this.incrementColumn(captures[0].length - whitespaceLength);
 
-         return tok;
 
-       }
 
-     }
 
-   },
 
-   /**
 
-    * Return the indexOf `(` or `{` or `[` / `)` or `}` or `]` delimiters.
 
-    *
 
-    * Make sure that when calling this function, colno is at the character
 
-    * immediately before the beginning.
 
-    *
 
-    * @return {Number}
 
-    * @api private
 
-    */
 
-   bracketExpression: function(skip){
 
-     skip = skip || 0;
 
-     var start = this.input[skip];
 
-     assert(start === '(' || start === '{' || start === '[',
 
-            'The start character should be "(", "{" or "["');
 
-     var end = characterParser.BRACKETS[start];
 
-     var range;
 
-     try {
 
-       range = characterParser.parseUntil(this.input, end, {start: skip + 1});
 
-     } catch (ex) {
 
-       if (ex.index !== undefined) {
 
-         var idx = ex.index;
 
-         // starting from this.input[skip]
 
-         var tmp = this.input.substr(skip).indexOf('\n');
 
-         // starting from this.input[0]
 
-         var nextNewline = tmp + skip;
 
-         var ptr = 0;
 
-         while (idx > nextNewline && tmp !== -1) {
 
-           this.incrementLine(1);
 
-           idx -= nextNewline + 1;
 
-           ptr += nextNewline + 1;
 
-           tmp = nextNewline = this.input.substr(ptr).indexOf('\n');
 
-         };
 
-         this.incrementColumn(idx);
 
-       }
 
-       if (ex.code === 'CHARACTER_PARSER:END_OF_STRING_REACHED') {
 
-         this.error('NO_END_BRACKET', 'The end of the string reached with no closing bracket ' + end + ' found.');
 
-       } else if (ex.code === 'CHARACTER_PARSER:MISMATCHED_BRACKET') {
 
-         this.error('BRACKET_MISMATCH', ex.message);
 
-       }
 
-       throw ex;
 
-     }
 
-     return range;
 
-   },
 
-   scanIndentation: function() {
 
-     var captures, re;
 
-     // established regexp
 
-     if (this.indentRe) {
 
-       captures = this.indentRe.exec(this.input);
 
-     // determine regexp
 
-     } else {
 
-       // tabs
 
-       re = /^\n(\t*) */;
 
-       captures = re.exec(this.input);
 
-       // spaces
 
-       if (captures && !captures[1].length) {
 
-         re = /^\n( *)/;
 
-         captures = re.exec(this.input);
 
-       }
 
-       // established
 
-       if (captures && captures[1].length) this.indentRe = re;
 
-     }
 
-     return captures;
 
-   },
 
-   /**
 
-    * end-of-source.
 
-    */
 
-   eos: function() {
 
-     if (this.input.length) return;
 
-     if (this.interpolated) {
 
-       this.error('NO_END_BRACKET', 'End of line was reached with no closing bracket for interpolation.');
 
-     }
 
-     for (var i = 0; this.indentStack[i]; i++) {
 
-       this.tokens.push(this.tokEnd(this.tok('outdent')));
 
-     }
 
-     this.tokens.push(this.tokEnd(this.tok('eos')));
 
-     this.ended = true;
 
-     return true;
 
-   },
 
-   /**
 
-    * Blank line.
 
-    */
 
-   blank: function() {
 
-     var captures;
 
-     if (captures = /^\n[ \t]*\n/.exec(this.input)) {
 
-       this.consume(captures[0].length - 1);
 
-       this.incrementLine(1);
 
-       return true;
 
-     }
 
-   },
 
-   /**
 
-    * Comment.
 
-    */
 
-   comment: function() {
 
-     var captures;
 
-     if (captures = /^\/\/(-)?([^\n]*)/.exec(this.input)) {
 
-       this.consume(captures[0].length);
 
-       var tok = this.tok('comment', captures[2]);
 
-       tok.buffer = '-' != captures[1];
 
-       this.interpolationAllowed = tok.buffer;
 
-       this.tokens.push(tok);
 
-       this.incrementColumn(captures[0].length);
 
-       this.tokEnd(tok);
 
-       this.callLexerFunction('pipelessText');
 
-       return true;
 
-     }
 
-   },
 
-   /**
 
-    * Interpolated tag.
 
-    */
 
-   interpolation: function() {
 
-     if (/^#\{/.test(this.input)) {
 
-       var match = this.bracketExpression(1);
 
-       this.consume(match.end + 1);
 
-       var tok = this.tok('interpolation', match.src);
 
-       this.tokens.push(tok);
 
-       this.incrementColumn(2); // '#{'
 
-       this.assertExpression(match.src);
 
-       var splitted = match.src.split('\n');
 
-       var lines = splitted.length - 1;
 
-       this.incrementLine(lines);
 
-       this.incrementColumn(splitted[lines].length + 1); // + 1 → '}'
 
-       this.tokEnd(tok);
 
-       return true;
 
-     }
 
-   },
 
-   /**
 
-    * Tag.
 
-    */
 
-   tag: function() {
 
-     var captures;
 
-     if (captures = /^(\w(?:[-:\w]*\w)?)/.exec(this.input)) {
 
-       var tok, name = captures[1], len = captures[0].length;
 
-       this.consume(len);
 
-       tok = this.tok('tag', name);
 
-       this.tokens.push(tok);
 
-       this.incrementColumn(len);
 
-       this.tokEnd(tok);
 
-       return true;
 
-     }
 
-   },
 
-   /**
 
-    * Filter.
 
-    */
 
-   filter: function(opts) {
 
-     var tok = this.scan(/^:([\w\-]+)/, 'filter');
 
-     var inInclude = opts && opts.inInclude;
 
-     if (tok) {
 
-       this.tokens.push(tok);
 
-       this.incrementColumn(tok.val.length);
 
-       this.tokEnd(tok);
 
-       this.callLexerFunction('attrs');
 
-       if (!inInclude) {
 
-         this.interpolationAllowed = false;
 
-         this.callLexerFunction('pipelessText');
 
-       }
 
-       return true;
 
-     }
 
-   },
 
-   /**
 
-    * Doctype.
 
-    */
 
-   doctype: function() {
 
-     var node = this.scanEndOfLine(/^doctype *([^\n]*)/, 'doctype');
 
-     if (node) {
 
-       this.tokens.push(this.tokEnd(node));
 
-       return true;
 
-     }
 
-   },
 
-   /**
 
-    * Id.
 
-    */
 
-   id: function() {
 
-     var tok = this.scan(/^#([\w-]+)/, 'id');
 
-     if (tok) {
 
-       this.tokens.push(tok);
 
-       this.incrementColumn(tok.val.length);
 
-       this.tokEnd(tok);
 
-       return true;
 
-     }
 
-     if (/^#/.test(this.input)) {
 
-       this.error('INVALID_ID', '"' + /.[^ \t\(\#\.\:]*/.exec(this.input.substr(1))[0] + '" is not a valid ID.');
 
-     }
 
-   },
 
-   /**
 
-    * Class.
 
-    */
 
-   className: function() {
 
-     var tok = this.scan(/^\.([_a-z0-9\-]*[_a-z][_a-z0-9\-]*)/i, 'class');
 
-     if (tok) {
 
-       this.tokens.push(tok);
 
-       this.incrementColumn(tok.val.length);
 
-       this.tokEnd(tok);
 
-       return true;
 
-     }
 
-     if (/^\.[_a-z0-9\-]+/i.test(this.input)) {
 
-       this.error('INVALID_CLASS_NAME', 'Class names must contain at least one letter or underscore.');
 
-     }
 
-     if (/^\./.test(this.input)) {
 
-       this.error('INVALID_CLASS_NAME', '"' + /.[^ \t\(\#\.\:]*/.exec(this.input.substr(1))[0] + '" is not a valid class name.  Class names can only contain "_", "-", a-z and 0-9, and must contain at least one of "_", or a-z');
 
-     }
 
-   },
 
-   /**
 
-    * Text.
 
-    */
 
-   endInterpolation: function () {
 
-     if (this.interpolated && this.input[0] === ']') {
 
-       this.input = this.input.substr(1);
 
-       this.ended = true;
 
-       return true;
 
-     }
 
-   },
 
-   addText: function (type, value, prefix, escaped) {
 
-     var tok;
 
-     if (value + prefix === '') return;
 
-     prefix = prefix || '';
 
-     escaped = escaped || 0;
 
-     var indexOfEnd = this.interpolated ? value.indexOf(']') : -1;
 
-     var indexOfStart = this.interpolationAllowed ? value.indexOf('#[') : -1;
 
-     var indexOfEscaped = this.interpolationAllowed ? value.indexOf('\\#[') : -1;
 
-     var matchOfStringInterp = /(\\)?([#!]){((?:.|\n)*)$/.exec(value);
 
-     var indexOfStringInterp = this.interpolationAllowed && matchOfStringInterp ? matchOfStringInterp.index : Infinity;
 
-     if (indexOfEnd === -1) indexOfEnd = Infinity;
 
-     if (indexOfStart === -1) indexOfStart = Infinity;
 
-     if (indexOfEscaped === -1) indexOfEscaped = Infinity;
 
-     if (indexOfEscaped !== Infinity && indexOfEscaped < indexOfEnd && indexOfEscaped < indexOfStart && indexOfEscaped < indexOfStringInterp) {
 
-       prefix = prefix + value.substring(0, indexOfEscaped) + '#[';
 
-       return this.addText(type, value.substring(indexOfEscaped + 3), prefix, escaped + 1);
 
-     }
 
-     if (indexOfStart !== Infinity && indexOfStart < indexOfEnd && indexOfStart < indexOfEscaped && indexOfStart < indexOfStringInterp) {
 
-       tok = this.tok(type, prefix + value.substring(0, indexOfStart));
 
-       this.incrementColumn(prefix.length + indexOfStart + escaped);
 
-       this.tokens.push(this.tokEnd(tok));
 
-       tok = this.tok('start-pug-interpolation');
 
-       this.incrementColumn(2);
 
-       this.tokens.push(this.tokEnd(tok));
 
-       var child = new this.constructor(value.substr(indexOfStart + 2), {
 
-         filename: this.filename,
 
-         interpolated: true,
 
-         startingLine: this.lineno,
 
-         startingColumn: this.colno
 
-       });
 
-       var interpolated;
 
-       try {
 
-         interpolated = child.getTokens();
 
-       } catch (ex) {
 
-         if (ex.code && /^PUG:/.test(ex.code)) {
 
-           this.colno = ex.column;
 
-           this.error(ex.code.substr(4), ex.msg);
 
-         }
 
-         throw ex;
 
-       }
 
-       this.colno = child.colno;
 
-       this.tokens = this.tokens.concat(interpolated);
 
-       tok = this.tok('end-pug-interpolation');
 
-       this.incrementColumn(1);
 
-       this.tokens.push(this.tokEnd(tok));
 
-       this.addText(type, child.input);
 
-       return;
 
-     }
 
-     if (indexOfEnd !== Infinity && indexOfEnd < indexOfStart && indexOfEnd < indexOfEscaped && indexOfEnd < indexOfStringInterp) {
 
-       if (prefix + value.substring(0, indexOfEnd)) {
 
-         this.addText(type, value.substring(0, indexOfEnd), prefix);
 
-       }
 
-       this.ended = true;
 
-       this.input = value.substr(value.indexOf(']') + 1) + this.input;
 
-       return;
 
-     }
 
-     if (indexOfStringInterp !== Infinity) {
 
-       if (matchOfStringInterp[1]) {
 
-         prefix = prefix + value.substring(0, indexOfStringInterp) + '#{';
 
-         return this.addText(type, value.substring(indexOfStringInterp + 3), prefix, escaped + 1);
 
-       }
 
-       var before = value.substr(0, indexOfStringInterp);
 
-       if (prefix || before) {
 
-         before = prefix + before;
 
-         tok = this.tok(type, before);
 
-         this.incrementColumn(before.length + escaped);
 
-         this.tokens.push(this.tokEnd(tok));
 
-       }
 
-       var rest = matchOfStringInterp[3];
 
-       var range;
 
-       tok = this.tok('interpolated-code');
 
-       this.incrementColumn(2);
 
-       try {
 
-         range = characterParser.parseUntil(rest, '}');
 
-       } catch (ex) {
 
-         if (ex.index !== undefined) {
 
-           this.incrementColumn(ex.index);
 
-         }
 
-         if (ex.code === 'CHARACTER_PARSER:END_OF_STRING_REACHED') {
 
-           this.error('NO_END_BRACKET', 'End of line was reached with no closing bracket for interpolation.');
 
-         } else if (ex.code === 'CHARACTER_PARSER:MISMATCHED_BRACKET') {
 
-           this.error('BRACKET_MISMATCH', ex.message);
 
-         } else {
 
-           throw ex;
 
-         }
 
-       }
 
-       tok.mustEscape = matchOfStringInterp[2] === '#';
 
-       tok.buffer = true;
 
-       tok.val = range.src;
 
-       this.assertExpression(range.src);
 
-       if (range.end + 1 < rest.length) {
 
-         rest = rest.substr(range.end + 1);
 
-         this.incrementColumn(range.end + 1);
 
-         this.tokens.push(this.tokEnd(tok));
 
-         this.addText(type, rest);
 
-       } else {
 
-         this.incrementColumn(rest.length);
 
-         this.tokens.push(this.tokEnd(tok));
 
-       }
 
-       return;
 
-     }
 
-     value = prefix + value;
 
-     tok = this.tok(type, value);
 
-     this.incrementColumn(value.length + escaped);
 
-     this.tokens.push(this.tokEnd(tok));
 
-   },
 
-   text: function() {
 
-     var tok = this.scan(/^(?:\| ?| )([^\n]+)/, 'text') ||
 
-       this.scan(/^( )/, 'text') ||
 
-       this.scan(/^\|( ?)/, 'text');
 
-     if (tok) {
 
-       this.addText('text', tok.val);
 
-       return true;
 
-     }
 
-   },
 
-   textHtml: function () {
 
-     var tok = this.scan(/^(<[^\n]*)/, 'text-html');
 
-     if (tok) {
 
-       this.addText('text-html', tok.val);
 
-       return true;
 
-     }
 
-   },
 
-   /**
 
-    * Dot.
 
-    */
 
-   dot: function() {
 
-     var tok;
 
-     if (tok = this.scanEndOfLine(/^\./, 'dot')) {
 
-       this.tokens.push(this.tokEnd(tok));
 
-       this.callLexerFunction('pipelessText');
 
-       return true;
 
-     }
 
-   },
 
-   /**
 
-    * Extends.
 
-    */
 
-   "extends": function() {
 
-     var tok = this.scan(/^extends?(?= |$|\n)/, 'extends');
 
-     if (tok) {
 
-       this.tokens.push(this.tokEnd(tok));
 
-       if (!this.callLexerFunction('path')) {
 
-         this.error('NO_EXTENDS_PATH', 'missing path for extends');
 
-       }
 
-       return true;
 
-     }
 
-     if (this.scan(/^extends?\b/)) {
 
-       this.error('MALFORMED_EXTENDS', 'malformed extends');
 
-     }
 
-   },
 
-   /**
 
-    * Block prepend.
 
-    */
 
-   prepend: function() {
 
-     var captures;
 
-     if (captures = /^(?:block +)?prepend +([^\n]+)/.exec(this.input)) {
 
-       var name = captures[1].trim();
 
-       var comment = '';
 
-       if (name.indexOf('//') !== -1) {
 
-         comment = '//' + name.split('//').slice(1).join('//');
 
-         name = name.split('//')[0].trim();
 
-       }
 
-       if (!name) return;
 
-       var tok = this.tok('block', name);
 
-       var len = captures[0].length - comment.length;
 
-       while(this.whitespaceRe.test(this.input.charAt(len - 1))) len--;
 
-       this.incrementColumn(len);
 
-       tok.mode = 'prepend';
 
-       this.tokens.push(this.tokEnd(tok));
 
-       this.consume(captures[0].length - comment.length);
 
-       this.incrementColumn(captures[0].length - comment.length - len);
 
-       return true;
 
-     }
 
-   },
 
-   /**
 
-    * Block append.
 
-    */
 
-   append: function() {
 
-     var captures;
 
-     if (captures = /^(?:block +)?append +([^\n]+)/.exec(this.input)) {
 
-       var name = captures[1].trim();
 
-       var comment = '';
 
-       if (name.indexOf('//') !== -1) {
 
-         comment = '//' + name.split('//').slice(1).join('//');
 
-         name = name.split('//')[0].trim();
 
-       }
 
-       if (!name) return;
 
-       var tok = this.tok('block', name);
 
-       var len = captures[0].length - comment.length;
 
-       while(this.whitespaceRe.test(this.input.charAt(len - 1))) len--;
 
-       this.incrementColumn(len);
 
-       tok.mode = 'append';
 
-       this.tokens.push(this.tokEnd(tok));
 
-       this.consume(captures[0].length - comment.length);
 
-       this.incrementColumn(captures[0].length - comment.length - len);
 
-       return true;
 
-     }
 
-   },
 
-   /**
 
-    * Block.
 
-    */
 
-   block: function() {
 
-     var captures;
 
-     if (captures = /^block +([^\n]+)/.exec(this.input)) {
 
-       var name = captures[1].trim();
 
-       var comment = '';
 
-       if (name.indexOf('//') !== -1) {
 
-         comment = '//' + name.split('//').slice(1).join('//');
 
-         name = name.split('//')[0].trim();
 
-       }
 
-       if (!name) return;
 
-       var tok = this.tok('block', name);
 
-       var len = captures[0].length - comment.length;
 
-       while(this.whitespaceRe.test(this.input.charAt(len - 1))) len--;
 
-       this.incrementColumn(len);
 
-       tok.mode = 'replace';
 
-       this.tokens.push(this.tokEnd(tok));
 
-       this.consume(captures[0].length - comment.length);
 
-       this.incrementColumn(captures[0].length - comment.length - len);
 
-       return true;
 
-     }
 
-   },
 
-   /**
 
-    * Mixin Block.
 
-    */
 
-   mixinBlock: function() {
 
-     var tok;
 
-     if (tok = this.scanEndOfLine(/^block/, 'mixin-block')) {
 
-       this.tokens.push(this.tokEnd(tok));
 
-       return true;
 
-     }
 
-   },
 
-   /**
 
-    * Yield.
 
-    */
 
-   'yield': function() {
 
-     var tok = this.scanEndOfLine(/^yield/, 'yield');
 
-     if (tok) {
 
-       this.tokens.push(this.tokEnd(tok));
 
-       return true;
 
-     }
 
-   },
 
-   /**
 
-    * Include.
 
-    */
 
-   include: function() {
 
-     var tok = this.scan(/^include(?=:| |$|\n)/, 'include');
 
-     if (tok) {
 
-       this.tokens.push(this.tokEnd(tok));
 
-       while (this.callLexerFunction('filter', { inInclude: true }));
 
-       if (!this.callLexerFunction('path')) {
 
-         if (/^[^ \n]+/.test(this.input)) {
 
-           // if there is more text
 
-           this.fail();
 
-         } else {
 
-           // if not
 
-           this.error('NO_INCLUDE_PATH', 'missing path for include');
 
-         }
 
-       }
 
-       return true;
 
-     }
 
-     if (this.scan(/^include\b/)) {
 
-       this.error('MALFORMED_INCLUDE', 'malformed include');
 
-     }
 
-   },
 
-   /**
 
-    * Path
 
-    */
 
-   path: function() {
 
-     var tok = this.scanEndOfLine(/^ ([^\n]+)/, 'path');
 
-     if (tok && (tok.val = tok.val.trim())) {
 
-       this.tokens.push(this.tokEnd(tok));
 
-       return true;
 
-     }
 
-   },
 
-   /**
 
-    * Case.
 
-    */
 
-   "case": function() {
 
-     var tok = this.scanEndOfLine(/^case +([^\n]+)/, 'case');
 
-     if (tok) {
 
-       this.incrementColumn(-tok.val.length);
 
-       this.assertExpression(tok.val);
 
-       this.incrementColumn(tok.val.length);
 
-       this.tokens.push(this.tokEnd(tok));
 
-       return true;
 
-     }
 
-     if (this.scan(/^case\b/)) {
 
-       this.error('NO_CASE_EXPRESSION', 'missing expression for case');
 
-     }
 
-   },
 
-   /**
 
-    * When.
 
-    */
 
-   when: function() {
 
-     var tok = this.scanEndOfLine(/^when +([^:\n]+)/, 'when');
 
-     if (tok) {
 
-       var parser = characterParser(tok.val);
 
-       while (parser.isNesting() || parser.isString()) {
 
-         var rest = /:([^:\n]+)/.exec(this.input);
 
-         if (!rest) break;
 
-         tok.val += rest[0];
 
-         this.consume(rest[0].length);
 
-         this.incrementColumn(rest[0].length);
 
-         parser = characterParser(tok.val);
 
-       }
 
-       this.incrementColumn(-tok.val.length);
 
-       this.assertExpression(tok.val);
 
-       this.incrementColumn(tok.val.length);
 
-       this.tokens.push(this.tokEnd(tok));
 
-       return true;
 
-     }
 
-     if (this.scan(/^when\b/)) {
 
-       this.error('NO_WHEN_EXPRESSION', 'missing expression for when');
 
-     }
 
-   },
 
-   /**
 
-    * Default.
 
-    */
 
-   "default": function() {
 
-     var tok = this.scanEndOfLine(/^default/, 'default');
 
-     if (tok) {
 
-       this.tokens.push(this.tokEnd(tok));
 
-       return true;
 
-     }
 
-     if (this.scan(/^default\b/)) {
 
-       this.error('DEFAULT_WITH_EXPRESSION', 'default should not have an expression');
 
-     }
 
-   },
 
-   /**
 
-    * Call mixin.
 
-    */
 
-   call: function(){
 
-     var tok, captures, increment;
 
-     if (captures = /^\+(\s*)(([-\w]+)|(#\{))/.exec(this.input)) {
 
-       // try to consume simple or interpolated call
 
-       if (captures[3]) {
 
-         // simple call
 
-         increment = captures[0].length;
 
-         this.consume(increment);
 
-         tok = this.tok('call', captures[3]);
 
-       } else {
 
-         // interpolated call
 
-         var match = this.bracketExpression(2 + captures[1].length);
 
-         increment = match.end + 1;
 
-         this.consume(increment);
 
-         this.assertExpression(match.src);
 
-         tok = this.tok('call', '#{'+match.src+'}');
 
-       }
 
-       this.incrementColumn(increment);
 
-       tok.args = null;
 
-       // Check for args (not attributes)
 
-       if (captures = /^ *\(/.exec(this.input)) {
 
-         var range = this.bracketExpression(captures[0].length - 1);
 
-         if (!/^\s*[-\w]+ *=/.test(range.src)) { // not attributes
 
-           this.incrementColumn(1);
 
-           this.consume(range.end + 1);
 
-           tok.args = range.src;
 
-           this.assertExpression('[' + tok.args + ']');
 
-           for (var i = 0; i <= tok.args.length; i++) {
 
-             if (tok.args[i] === '\n') {
 
-               this.incrementLine(1);
 
-             } else {
 
-               this.incrementColumn(1);
 
-             }
 
-           }
 
-         }
 
-       }
 
-       this.tokens.push(this.tokEnd(tok));
 
-       return true;
 
-     }
 
-   },
 
-   /**
 
-    * Mixin.
 
-    */
 
-   mixin: function(){
 
-     var captures;
 
-     if (captures = /^mixin +([-\w]+)(?: *\((.*)\))? */.exec(this.input)) {
 
-       this.consume(captures[0].length);
 
-       var tok = this.tok('mixin', captures[1]);
 
-       tok.args = captures[2] || null;
 
-       this.incrementColumn(captures[0].length);
 
-       this.tokens.push(this.tokEnd(tok));
 
-       return true;
 
-     }
 
-   },
 
-   /**
 
-    * Conditional.
 
-    */
 
-   conditional: function() {
 
-     var captures;
 
-     if (captures = /^(if|unless|else if|else)\b([^\n]*)/.exec(this.input)) {
 
-       this.consume(captures[0].length);
 
-       var type = captures[1].replace(/ /g, '-');
 
-       var js = captures[2] && captures[2].trim();
 
-       // type can be "if", "else-if" and "else"
 
-       var tok = this.tok(type, js);
 
-       this.incrementColumn(captures[0].length - js.length);
 
-       switch (type) {
 
-         case 'if':
 
-         case 'else-if':
 
-           this.assertExpression(js);
 
-           break;
 
-         case 'unless':
 
-           this.assertExpression(js);
 
-           tok.val = '!(' + js + ')';
 
-           tok.type = 'if';
 
-           break;
 
-         case 'else':
 
-           if (js) {
 
-             this.error(
 
-               'ELSE_CONDITION',
 
-               '`else` cannot have a condition, perhaps you meant `else if`'
 
-             );
 
-           }
 
-           break;
 
-       }
 
-       this.incrementColumn(js.length);
 
-       this.tokens.push(this.tokEnd(tok));
 
-       return true;
 
-     }
 
-   },
 
-   /**
 
-    * While.
 
-    */
 
-   "while": function() {
 
-     var captures, tok;
 
-     if (captures = /^while +([^\n]+)/.exec(this.input)) {
 
-       this.consume(captures[0].length);
 
-       this.assertExpression(captures[1]);
 
-       tok = this.tok('while', captures[1]);
 
-       this.incrementColumn(captures[0].length);
 
-       this.tokens.push(this.tokEnd(tok));
 
-       return true;
 
-     }
 
-     if (this.scan(/^while\b/)) {
 
-       this.error('NO_WHILE_EXPRESSION', 'missing expression for while');
 
-     }
 
-   },
 
-   /**
 
-    * Each.
 
-    */
 
-   each: function() {
 
-     var captures;
 
-     if (captures = /^(?:each|for) +([a-zA-Z_$][\w$]*)(?: *, *([a-zA-Z_$][\w$]*))? * in *([^\n]+)/.exec(this.input)) {
 
-       this.consume(captures[0].length);
 
-       var tok = this.tok('each', captures[1]);
 
-       tok.key = captures[2] || null;
 
-       this.incrementColumn(captures[0].length - captures[3].length);
 
-       this.assertExpression(captures[3])
 
-       tok.code = captures[3];
 
-       this.incrementColumn(captures[3].length);
 
-       this.tokens.push(this.tokEnd(tok));
 
-       return true;
 
-     }
 
-     if (this.scan(/^(?:each|for)\b/)) {
 
-       this.error('MALFORMED_EACH', 'malformed each');
 
-     }
 
-     if (captures = /^- *(?:each|for) +([a-zA-Z_$][\w$]*)(?: *, *([a-zA-Z_$][\w$]*))? +in +([^\n]+)/.exec(this.input)) {
 
-       this.error(
 
-         'MALFORMED_EACH',
 
-         'Pug each and for should no longer be prefixed with a dash ("-"). They are pug keywords and not part of JavaScript.'
 
-       );
 
-     }
 
-   },
 
-   /**
 
-    * Code.
 
-    */
 
-   code: function() {
 
-     var captures;
 
-     if (captures = /^(!?=|-)[ \t]*([^\n]+)/.exec(this.input)) {
 
-       var flags = captures[1];
 
-       var code = captures[2];
 
-       var shortened = 0;
 
-       if (this.interpolated) {
 
-         var parsed;
 
-         try {
 
-           parsed = characterParser.parseUntil(code, ']');
 
-         } catch (err) {
 
-           if (err.index !== undefined) {
 
-             this.incrementColumn(captures[0].length - code.length + err.index);
 
-           }
 
-           if (err.code === 'CHARACTER_PARSER:END_OF_STRING_REACHED') {
 
-             this.error('NO_END_BRACKET', 'End of line was reached with no closing bracket for interpolation.');
 
-           } else if (err.code === 'CHARACTER_PARSER:MISMATCHED_BRACKET') {
 
-             this.error('BRACKET_MISMATCH', err.message);
 
-           } else {
 
-             throw err;
 
-           }
 
-         }
 
-         shortened = code.length - parsed.end;
 
-         code = parsed.src;
 
-       }
 
-       var consumed = captures[0].length - shortened;
 
-       this.consume(consumed);
 
-       var tok = this.tok('code', code);
 
-       tok.mustEscape = flags.charAt(0) === '=';
 
-       tok.buffer = flags.charAt(0) === '=' || flags.charAt(1) === '=';
 
-       // p #[!=    abc] hey
 
-       //     ^              original colno
 
-       //     -------------- captures[0]
 
-       //           -------- captures[2]
 
-       //     ------         captures[0] - captures[2]
 
-       //           ^        after colno
 
-       // =   abc
 
-       // ^                  original colno
 
-       // -------            captures[0]
 
-       //     ---            captures[2]
 
-       // ----               captures[0] - captures[2]
 
-       //     ^              after colno
 
-       this.incrementColumn(captures[0].length - captures[2].length);
 
-       if (tok.buffer) this.assertExpression(code);
 
-       this.tokens.push(tok);
 
-       // p #[!=    abc] hey
 
-       //           ^        original colno
 
-       //              ----- shortened
 
-       //           ---      code
 
-       //              ^     after colno
 
-       // =   abc
 
-       //     ^              original colno
 
-       //                    shortened
 
-       //     ---            code
 
-       //        ^           after colno
 
-       this.incrementColumn(code.length);
 
-       this.tokEnd(tok);
 
-       return true;
 
-     }
 
-   },
 
-   /**
 
-    * Block code.
 
-    */
 
-   blockCode: function() {
 
-     var tok
 
-     if (tok = this.scanEndOfLine(/^-/, 'blockcode')) {
 
-       this.tokens.push(this.tokEnd(tok));
 
-       this.interpolationAllowed = false;
 
-       this.callLexerFunction('pipelessText');
 
-       return true;
 
-     }
 
-   },
 
-   
 
-   /**
 
-    * Attribute Name.
 
-    */
 
-   attribute: function(str){
 
-     var quote = '';
 
-     var quoteRe = /['"]/;
 
-     var key = '';
 
-     var i;
 
-     
 
-     // consume all whitespace before the key
 
-     for(i = 0; i < str.length; i++){
 
-       if(!this.whitespaceRe.test(str[i])) break;
 
-       if(str[i] === '\n'){
 
-         this.incrementLine(1);
 
-       } else {
 
-         this.incrementColumn(1);
 
-       }
 
-     }
 
-     
 
-     if(i === str.length){
 
-       return '';
 
-     }
 
-     
 
-     var tok = this.tok('attribute');
 
-     
 
-     // quote?
 
-     if(quoteRe.test(str[i])){
 
-       quote = str[i];
 
-       this.incrementColumn(1);
 
-       i++;
 
-     }
 
-     
 
-     // start looping through the key
 
-     for (; i < str.length; i++) {
 
-       
 
-       if(quote){
 
-         if (str[i] === quote) {
 
-           this.incrementColumn(1);
 
-           i++;
 
-           break;
 
-         }
 
-       } else {
 
-         if(this.whitespaceRe.test(str[i]) || str[i] === '!' || str[i] === '=' || str[i] === ',') {
 
-           break;
 
-         }
 
-       }
 
-       
 
-       key += str[i];
 
-         
 
-       if (str[i] === '\n') {
 
-         this.incrementLine(1);
 
-       } else {
 
-         this.incrementColumn(1);
 
-       }
 
-     }
 
-     
 
-     tok.name = key;
 
-     
 
-     var valueResponse = this.attributeValue(str.substr(i));
 
-     
 
-     if (valueResponse.val) {
 
-       tok.val = valueResponse.val;
 
-       tok.mustEscape = valueResponse.mustEscape;
 
-     } else {
 
-       // was a boolean attribute (ex: `input(disabled)`)
 
-       tok.val = true;
 
-       tok.mustEscape = true;
 
-     }
 
-     
 
-     str = valueResponse.remainingSource;
 
-     
 
-     this.tokens.push(this.tokEnd(tok));
 
-     
 
-     for(i = 0; i < str.length; i++){
 
-       if(!this.whitespaceRe.test(str[i])) {
 
-         break;
 
-       }
 
-       if(str[i] === '\n'){
 
-         this.incrementLine(1);
 
-       } else {
 
-         this.incrementColumn(1);
 
-       }
 
-     }
 
-     
 
-     if(str[i] === ','){
 
-       this.incrementColumn(1);
 
-       i++;
 
-     }
 
-     
 
-     return str.substr(i);
 
-   },
 
-   
 
-   /**
 
-    * Attribute Value.
 
-    */
 
-   attributeValue: function(str){
 
-     var quoteRe = /['"]/;
 
-     var val = '';
 
-     var done, i, x;
 
-     var escapeAttr = true;
 
-     var state = characterParser.defaultState();
 
-     var col = this.colno;
 
-     var line = this.lineno;
 
-     
 
-     // consume all whitespace before the equals sign
 
-     for(i = 0; i < str.length; i++){
 
-       if(!this.whitespaceRe.test(str[i])) break;
 
-       if(str[i] === '\n'){
 
-         line++;
 
-         col = 1;
 
-       } else {
 
-         col++;
 
-       }
 
-     }
 
-     
 
-     if(i === str.length){
 
-       return { remainingSource: str };
 
-     }
 
-     
 
-     if(str[i] === '!'){
 
-       escapeAttr = false;
 
-       col++;
 
-       i++;
 
-       if (str[i] !== '=') this.error('INVALID_KEY_CHARACTER', 'Unexpected character ' + str[i] + ' expected `=`');
 
-     }
 
-     
 
-     if(str[i] !== '='){
 
-       // check for anti-pattern `div("foo"bar)`
 
-       if (i === 0 && str && !this.whitespaceRe.test(str[0]) && str[0] !== ','){
 
-         this.error('INVALID_KEY_CHARACTER', 'Unexpected character ' + str[0] + ' expected `=`');
 
-       } else {
 
-         return { remainingSource: str };
 
-       }
 
-     }
 
-     
 
-     this.lineno = line;
 
-     this.colno = col + 1;
 
-     i++;
 
-     
 
-     // consume all whitespace before the value
 
-     for(; i < str.length; i++){
 
-       if(!this.whitespaceRe.test(str[i])) break;
 
-       if(str[i] === '\n'){
 
-         this.incrementLine(1);
 
-       } else {
 
-         this.incrementColumn(1);
 
-       }
 
-     }
 
-     
 
-     line = this.lineno;
 
-     col = this.colno;
 
-     
 
-     // start looping through the value
 
-     for (; i < str.length; i++) {
 
-       // if the character is in a string or in parentheses/brackets/braces
 
-       if (!(state.isNesting() || state.isString())){
 
-         
 
-         if (this.whitespaceRe.test(str[i])) {
 
-           done = false;
 
-           
 
-           // find the first non-whitespace character
 
-           for (x = i; x < str.length; x++) {
 
-             if (!this.whitespaceRe.test(str[x])) {
 
-               // if it is a JavaScript punctuator, then assume that it is
 
-               // a part of the value
 
-               const isNotPunctuator = !characterParser.isPunctuator(str[x])
 
-               const isQuote = quoteRe.test(str[x])
 
-               const isColon = str[x] === ':'
 
-               const isSpreadOperator = str[x] + str[x + 1] + str[x + 2] === '...'
 
-               if ((isNotPunctuator || isQuote || isColon || isSpreadOperator) && this.assertExpression(val, true)) {
 
-                 done = true;
 
-               }
 
-               break;
 
-             }
 
-           }
 
-           
 
-           // if everything else is whitespace, return now so last attribute
 
-           // does not include trailing whitespace
 
-           if(done || x === str.length){
 
-             break;
 
-           }
 
-         }
 
-         
 
-         // if there's no whitespace and the character is not ',', the
 
-         // attribute did not end.
 
-         if(str[i] === ',' && this.assertExpression(val, true)){
 
-           break;
 
-         }
 
-       }
 
-       
 
-       state = characterParser.parseChar(str[i], state);
 
-       val += str[i];
 
-       
 
-       if (str[i] === '\n') {
 
-         line++;
 
-         col = 1;
 
-       } else {
 
-         col++;
 
-       }
 
-     }
 
-     
 
-     this.assertExpression(val);
 
-     
 
-     this.lineno = line;
 
-     this.colno = col;
 
-     
 
-     return { val: val, mustEscape: escapeAttr, remainingSource: str.substr(i) };
 
-   },
 
-   /**
 
-    * Attributes.
 
-    */
 
-   
 
-   attrs: function() {
 
-     var tok;
 
-     
 
-     if ('(' == this.input.charAt(0)) {
 
-       tok = this.tok('start-attributes');
 
-       var index = this.bracketExpression().end;
 
-       var str = this.input.substr(1, index-1);
 
-       this.incrementColumn(1);
 
-       this.tokens.push(this.tokEnd(tok));
 
-       this.assertNestingCorrect(str);
 
-       this.consume(index + 1);
 
-       while(str){
 
-         str = this.attribute(str);
 
-       }
 
-       tok = this.tok('end-attributes');
 
-       this.incrementColumn(1);
 
-       this.tokens.push(this.tokEnd(tok));
 
-       return true;
 
-     }
 
-   },
 
-   /**
 
-    * &attributes block
 
-    */
 
-   attributesBlock: function () {
 
-     if (/^&attributes\b/.test(this.input)) {
 
-       var consumed = 11;
 
-       this.consume(consumed);
 
-       var tok = this.tok('&attributes');
 
-       this.incrementColumn(consumed);
 
-       var args = this.bracketExpression();
 
-       consumed = args.end + 1;
 
-       this.consume(consumed);
 
-       tok.val = args.src;
 
-       this.incrementColumn(consumed);
 
-       this.tokens.push(this.tokEnd(tok));
 
-       return true;
 
-     }
 
-   },
 
-   /**
 
-    * Indent | Outdent | Newline.
 
-    */
 
-   indent: function() {
 
-     var captures = this.scanIndentation();
 
-     var tok;
 
-     if (captures) {
 
-       var indents = captures[1].length;
 
-       this.incrementLine(1);
 
-       this.consume(indents + 1);
 
-       if (' ' == this.input[0] || '\t' == this.input[0]) {
 
-         this.error('INVALID_INDENTATION', 'Invalid indentation, you can use tabs or spaces but not both');
 
-       }
 
-       // blank line
 
-       if ('\n' == this.input[0]) {
 
-         this.interpolationAllowed = true;
 
-         return this.tokEnd(this.tok('newline'));
 
-       }
 
-       // outdent
 
-       if (indents < this.indentStack[0]) {
 
-         var outdent_count = 0;
 
-         while (this.indentStack[0] > indents) {
 
-           if (this.indentStack[1] < indents) {
 
-             this.error('INCONSISTENT_INDENTATION', 'Inconsistent indentation. Expecting either ' + this.indentStack[1] + ' or ' + this.indentStack[0] + ' spaces/tabs.');
 
-           }
 
-           outdent_count++;
 
-           this.indentStack.shift();
 
-         }
 
-         while(outdent_count--){
 
-           this.colno = 1;
 
-           tok = this.tok('outdent');
 
-           this.colno = this.indentStack[0] + 1;
 
-           this.tokens.push(this.tokEnd(tok));
 
-         }
 
-       // indent
 
-       } else if (indents && indents != this.indentStack[0]) {
 
-         tok = this.tok('indent', indents);
 
-         this.colno = 1 + indents;
 
-         this.tokens.push(this.tokEnd(tok));
 
-         this.indentStack.unshift(indents);
 
-       // newline
 
-       } else {
 
-         tok = this.tok('newline');
 
-         this.colno = 1 + Math.min(this.indentStack[0] || 0, indents);
 
-         this.tokens.push(this.tokEnd(tok));
 
-       }
 
-       this.interpolationAllowed = true;
 
-       return true;
 
-     }
 
-   },
 
-   pipelessText: function pipelessText(indents) {
 
-     while (this.callLexerFunction('blank'));
 
-     var captures = this.scanIndentation();
 
-     indents = indents || captures && captures[1].length;
 
-     if (indents > this.indentStack[0]) {
 
-       this.tokens.push(this.tokEnd(this.tok('start-pipeless-text')));
 
-       var tokens = [];
 
-       var token_indent = [];
 
-       var isMatch;
 
-       // Index in this.input. Can't use this.consume because we might need to
 
-       // retry lexing the block.
 
-       var stringPtr = 0;
 
-       do {
 
-         // text has `\n` as a prefix
 
-         var i = this.input.substr(stringPtr + 1).indexOf('\n');
 
-         if (-1 == i) i = this.input.length - stringPtr - 1;
 
-         var str = this.input.substr(stringPtr + 1, i);
 
-         var lineCaptures = this.indentRe.exec('\n' + str);
 
-         var lineIndents = lineCaptures && lineCaptures[1].length;
 
-         isMatch = lineIndents >= indents;
 
-         token_indent.push(isMatch);
 
-         isMatch = isMatch || !str.trim();
 
-         if (isMatch) {
 
-           // consume test along with `\n` prefix if match
 
-           stringPtr += str.length + 1;
 
-           tokens.push(str.substr(indents));
 
-         } else if (lineIndents > this.indentStack[0]) {
 
-           // line is indented less than the first line but is still indented
 
-           // need to retry lexing the text block
 
-           this.tokens.pop();
 
-           return pipelessText.call(this, lineCaptures[1].length);
 
-         }
 
-       } while((this.input.length - stringPtr) && isMatch);
 
-       this.consume(stringPtr);
 
-       while (this.input.length === 0 && tokens[tokens.length - 1] === '') tokens.pop();
 
-       tokens.forEach(function (token, i) {
 
-         var tok;
 
-         this.incrementLine(1);
 
-         if (i !== 0) tok = this.tok('newline');
 
-         if (token_indent[i]) this.incrementColumn(indents);
 
-         if (tok) this.tokens.push(this.tokEnd(tok));
 
-         this.addText('text', token);
 
-       }.bind(this));
 
-       this.tokens.push(this.tokEnd(this.tok('end-pipeless-text')));
 
-       return true;
 
-     }
 
-   },
 
-   /**
 
-    * Slash.
 
-    */
 
-   slash: function() {
 
-     var tok = this.scan(/^\//, 'slash');
 
-     if (tok) {
 
-       this.tokens.push(this.tokEnd(tok));
 
-       return true;
 
-     }
 
-   },
 
-   /**
 
-    * ':'
 
-    */
 
-   colon: function() {
 
-     var tok = this.scan(/^: +/, ':');
 
-     if (tok) {
 
-       this.tokens.push(this.tokEnd(tok));
 
-       return true;
 
-     }
 
-   },
 
-   fail: function () {
 
-     this.error('UNEXPECTED_TEXT', 'unexpected text "' + this.input.substr(0, 5) + '"');
 
-   },
 
-   callLexerFunction: function (func) {
 
-     var rest = [];
 
-     for (var i = 1; i < arguments.length; i++) {
 
-       rest.push(arguments[i]);
 
-     }
 
-     var pluginArgs = [this].concat(rest);
 
-     for (var i = 0; i < this.plugins.length; i++) {
 
-       var plugin = this.plugins[i];
 
-       if (plugin[func] && plugin[func].apply(plugin, pluginArgs)) {
 
-         return true;
 
-       }
 
-     }
 
-     return this[func].apply(this, rest);
 
-   },
 
-   /**
 
-    * Move to the next token
 
-    *
 
-    * @api private
 
-    */
 
-   advance: function() {
 
-     return this.callLexerFunction('blank')
 
-       || this.callLexerFunction('eos')
 
-       || this.callLexerFunction('endInterpolation')
 
-       || this.callLexerFunction('yield')
 
-       || this.callLexerFunction('doctype')
 
-       || this.callLexerFunction('interpolation')
 
-       || this.callLexerFunction('case')
 
-       || this.callLexerFunction('when')
 
-       || this.callLexerFunction('default')
 
-       || this.callLexerFunction('extends')
 
-       || this.callLexerFunction('append')
 
-       || this.callLexerFunction('prepend')
 
-       || this.callLexerFunction('block')
 
-       || this.callLexerFunction('mixinBlock')
 
-       || this.callLexerFunction('include')
 
-       || this.callLexerFunction('mixin')
 
-       || this.callLexerFunction('call')
 
-       || this.callLexerFunction('conditional')
 
-       || this.callLexerFunction('each')
 
-       || this.callLexerFunction('while')
 
-       || this.callLexerFunction('tag')
 
-       || this.callLexerFunction('filter')
 
-       || this.callLexerFunction('blockCode')
 
-       || this.callLexerFunction('code')
 
-       || this.callLexerFunction('id')
 
-       || this.callLexerFunction('dot')
 
-       || this.callLexerFunction('className')
 
-       || this.callLexerFunction('attrs')
 
-       || this.callLexerFunction('attributesBlock')
 
-       || this.callLexerFunction('indent')
 
-       || this.callLexerFunction('text')
 
-       || this.callLexerFunction('textHtml')
 
-       || this.callLexerFunction('comment')
 
-       || this.callLexerFunction('slash')
 
-       || this.callLexerFunction('colon')
 
-       || this.fail();
 
-   },
 
-   /**
 
-    * Return an array of tokens for the current file
 
-    *
 
-    * @returns {Array.<Token>}
 
-    * @api public
 
-    */
 
-   getTokens: function () {
 
-     while (!this.ended) {
 
-       this.callLexerFunction('advance');
 
-     }
 
-     return this.tokens;
 
-   }
 
- };
 
 
  |