!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.jade=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o" vs "/>", * and boolean attributes are not mirrored. * * @param {string} name * @api public */ setDoctype: function(name){ this.doctype = doctypes[name.toLowerCase()] || ''; this.terse = this.doctype.toLowerCase() == ''; this.xml = 0 == this.doctype.indexOf(' 1 && !escape && block.nodes[0].isText && block.nodes[1].isText) this.prettyIndent(1, true); for (var i = 0; i < len; ++i) { // Pretty print text if (pp && i > 0 && !escape && block.nodes[i].isText && block.nodes[i-1].isText) this.prettyIndent(1, false); this.visit(block.nodes[i]); // Multiple text nodes are separated by newlines if (block.nodes[i+1] && block.nodes[i].isText && block.nodes[i+1].isText) this.buffer('\n'); } }, /** * Visit a mixin's `block` keyword. * * @param {MixinBlock} block * @api public */ visitMixinBlock: function(block){ if (this.pp) this.buf.push("jade_indent.push('" + Array(this.indents + 1).join(this.pp) + "');"); this.buf.push('block && block();'); if (this.pp) this.buf.push("jade_indent.pop();"); }, /** * Visit `doctype`. Sets terse mode to `true` when html 5 * is used, causing self-closing tags to end with ">" vs "/>", * and boolean attributes are not mirrored. * * @param {Doctype} doctype * @api public */ visitDoctype: function(doctype){ if (doctype && (doctype.val || !this.doctype)) { this.setDoctype(doctype.val || 'default'); } if (this.doctype) this.buffer(this.doctype); this.hasCompiledDoctype = true; }, /** * Visit `mixin`, generating a function that * may be called within the template. * * @param {Mixin} mixin * @api public */ visitMixin: function(mixin){ var name = 'jade_mixins['; var args = mixin.args || ''; var block = mixin.block; var attrs = mixin.attrs; var attrsBlocks = mixin.attributeBlocks.slice(); var pp = this.pp; var dynamic = mixin.name[0]==='#'; var key = mixin.name; if (dynamic) this.dynamicMixins = true; name += (dynamic ? mixin.name.substr(2,mixin.name.length-3):'"'+mixin.name+'"')+']'; this.mixins[key] = this.mixins[key] || {used: false, instances: []}; if (mixin.call) { this.mixins[key].used = true; if (pp) this.buf.push("jade_indent.push('" + Array(this.indents + 1).join(pp) + "');") if (block || attrs.length || attrsBlocks.length) { this.buf.push(name + '.call({'); if (block) { this.buf.push('block: function(){'); // Render block with no indents, dynamically added when rendered this.parentIndents++; var _indents = this.indents; this.indents = 0; this.visit(mixin.block); this.indents = _indents; this.parentIndents--; if (attrs.length || attrsBlocks.length) { this.buf.push('},'); } else { this.buf.push('}'); } } if (attrsBlocks.length) { if (attrs.length) { var val = this.attrs(attrs); attrsBlocks.unshift(val); } this.buf.push('attributes: jade.merge([' + attrsBlocks.join(',') + '])'); } else if (attrs.length) { var val = this.attrs(attrs); this.buf.push('attributes: ' + val); } if (args) { this.buf.push('}, ' + args + ');'); } else { this.buf.push('});'); } } else { this.buf.push(name + '(' + args + ');'); } if (pp) this.buf.push("jade_indent.pop();") } else { var mixin_start = this.buf.length; args = args ? args.split(',') : []; var rest; if (args.length && /^\.\.\./.test(args[args.length - 1].trim())) { rest = args.pop().trim().replace(/^\.\.\./, ''); } this.buf.push(name + ' = function(' + args.join(',') + '){'); this.buf.push('var block = (this && this.block), attributes = (this && this.attributes) || {};'); if (rest) { this.buf.push('var ' + rest + ' = [];'); this.buf.push('for (jade_interp = ' + args.length + '; jade_interp < arguments.length; jade_interp++) {'); this.buf.push(' ' + rest + '.push(arguments[jade_interp]);'); this.buf.push('}'); } this.parentIndents++; this.visit(block); this.parentIndents--; this.buf.push('};'); var mixin_end = this.buf.length; this.mixins[key].instances.push({start: mixin_start, end: mixin_end}); } }, /** * Visit `tag` buffering tag markup, generating * attributes, visiting the `tag`'s code and block. * * @param {Tag} tag * @api public */ visitTag: function(tag){ this.indents++; var name = tag.name , pp = this.pp , self = this; function bufferName() { if (tag.buffer) self.bufferExpression(name); else self.buffer(name); } if ('pre' == tag.name) this.escape = true; if (!this.hasCompiledTag) { if (!this.hasCompiledDoctype && 'html' == name) { this.visitDoctype(); } this.hasCompiledTag = true; } // pretty print if (pp && !tag.isInline()) this.prettyIndent(0, true); if (tag.selfClosing || (!this.xml && selfClosing[tag.name])) { this.buffer('<'); bufferName(); this.visitAttributes(tag.attrs, tag.attributeBlocks.slice()); this.terse ? this.buffer('>') : this.buffer('/>'); // if it is non-empty throw an error if (tag.block && !(tag.block.type === 'Block' && tag.block.nodes.length === 0) && tag.block.nodes.some(function (tag) { return tag.type !== 'Text' || !/^\s*$/.test(tag.val) })) { throw errorAtNode(tag, new Error(name + ' is self closing and should not have content.')); } } else { // Optimize attributes buffering this.buffer('<'); bufferName(); this.visitAttributes(tag.attrs, tag.attributeBlocks.slice()); this.buffer('>'); if (tag.code) this.visitCode(tag.code); this.visit(tag.block); // pretty print if (pp && !tag.isInline() && 'pre' != tag.name && !tag.canInline()) this.prettyIndent(0, true); this.buffer(''); } if ('pre' == tag.name) this.escape = false; this.indents--; }, /** * Visit `filter`, throwing when the filter does not exist. * * @param {Filter} filter * @api public */ visitFilter: function(filter){ var text = filter.block.nodes.map( function(node){ return node.val; } ).join('\n'); filter.attrs.filename = this.options.filename; try { this.buffer(filters(filter.name, text, filter.attrs), true); } catch (err) { throw errorAtNode(filter, err); } }, /** * Visit `text` node. * * @param {Text} text * @api public */ visitText: function(text){ this.buffer(text.val, true); }, /** * Visit a `comment`, only buffering when the buffer flag is set. * * @param {Comment} comment * @api public */ visitComment: function(comment){ if (!comment.buffer) return; if (this.pp) this.prettyIndent(1, true); this.buffer(''); }, /** * Visit a `BlockComment`. * * @param {Comment} comment * @api public */ visitBlockComment: function(comment){ if (!comment.buffer) return; if (this.pp) this.prettyIndent(1, true); this.buffer(''); }, /** * Visit `code`, respecting buffer / escape flags. * If the code is followed by a block, wrap it in * a self-calling function. * * @param {Code} code * @api public */ visitCode: function(code){ // Wrap code blocks with {}. // we only wrap unbuffered code blocks ATM // since they are usually flow control // Buffer code if (code.buffer) { var val = code.val.trim(); val = 'null == (jade_interp = '+val+') ? "" : jade_interp'; if (code.escape) val = 'jade.escape(' + val + ')'; this.bufferExpression(val); } else { this.buf.push(code.val); } // Block support if (code.block) { if (!code.buffer) this.buf.push('{'); this.visit(code.block); if (!code.buffer) this.buf.push('}'); } }, /** * Visit `each` block. * * @param {Each} each * @api public */ visitEach: function(each){ this.buf.push('' + '// iterate ' + each.obj + '\n' + ';(function(){\n' + ' var $$obj = ' + each.obj + ';\n' + ' if (\'number\' == typeof $$obj.length) {\n'); if (each.alternative) { this.buf.push(' if ($$obj.length) {'); } this.buf.push('' + ' for (var ' + each.key + ' = 0, $$l = $$obj.length; ' + each.key + ' < $$l; ' + each.key + '++) {\n' + ' var ' + each.val + ' = $$obj[' + each.key + '];\n'); this.visit(each.block); this.buf.push(' }\n'); if (each.alternative) { this.buf.push(' } else {'); this.visit(each.alternative); this.buf.push(' }'); } this.buf.push('' + ' } else {\n' + ' var $$l = 0;\n' + ' for (var ' + each.key + ' in $$obj) {\n' + ' $$l++;' + ' var ' + each.val + ' = $$obj[' + each.key + '];\n'); this.visit(each.block); this.buf.push(' }\n'); if (each.alternative) { this.buf.push(' if ($$l === 0) {'); this.visit(each.alternative); this.buf.push(' }'); } this.buf.push(' }\n}).call(this);\n'); }, /** * Visit `attrs`. * * @param {Array} attrs * @api public */ visitAttributes: function(attrs, attributeBlocks){ if (attributeBlocks.length) { if (attrs.length) { var val = this.attrs(attrs); attributeBlocks.unshift(val); } this.bufferExpression('jade.attrs(jade.merge([' + attributeBlocks.join(',') + ']), ' + utils.stringify(this.terse) + ')'); } else if (attrs.length) { this.attrs(attrs, true); } }, /** * Compile attributes. */ attrs: function(attrs, buffer){ var buf = []; var classes = []; var classEscaping = []; attrs.forEach(function(attr){ var key = attr.name; var escaped = attr.escaped; if (key === 'class') { classes.push(attr.val); classEscaping.push(attr.escaped); } else if (isConstant(attr.val)) { if (buffer) { this.buffer(runtime.attr(key, toConstant(attr.val), escaped, this.terse)); } else { var val = toConstant(attr.val); if (key === 'style') val = runtime.style(val); if (escaped && !(key.indexOf('data') === 0 && typeof val !== 'string')) { val = runtime.escape(val); } buf.push(utils.stringify(key) + ': ' + utils.stringify(val)); } } else { if (buffer) { this.bufferExpression('jade.attr("' + key + '", ' + attr.val + ', ' + utils.stringify(escaped) + ', ' + utils.stringify(this.terse) + ')'); } else { var val = attr.val; if (key === 'style') { val = 'jade.style(' + val + ')'; } if (escaped && !(key.indexOf('data') === 0)) { val = 'jade.escape(' + val + ')'; } else if (escaped) { val = '(typeof (jade_interp = ' + val + ') == "string" ? jade.escape(jade_interp) : jade_interp)'; } buf.push(utils.stringify(key) + ': ' + val); } } }.bind(this)); if (buffer) { if (classes.every(isConstant)) { this.buffer(runtime.cls(classes.map(toConstant), classEscaping)); } else { this.bufferExpression('jade.cls([' + classes.join(',') + '], ' + utils.stringify(classEscaping) + ')'); } } else if (classes.length) { if (classes.every(isConstant)) { classes = utils.stringify(runtime.joinClasses(classes.map(toConstant).map(runtime.joinClasses).map(function (cls, i) { return classEscaping[i] ? runtime.escape(cls) : cls; }))); } else { classes = '(jade_interp = ' + utils.stringify(classEscaping) + ',' + ' jade.joinClasses([' + classes.join(',') + '].map(jade.joinClasses).map(function (cls, i) {' + ' return jade_interp[i] ? jade.escape(cls) : cls' + ' }))' + ')'; } if (classes.length) buf.push('"class": ' + classes); } return '{' + buf.join(',') + '}'; } }; },{"./doctypes":2,"./filters":3,"./nodes":16,"./runtime":24,"./utils":25,"character-parser":29,"constantinople":30,"void-elements":34}],2:[function(require,module,exports){ 'use strict'; module.exports = { 'default': '' , 'xml': '' , 'transitional': '' , 'strict': '' , 'frameset': '' , '1.1': '' , 'basic': '' , 'mobile': '' }; },{}],3:[function(require,module,exports){ 'use strict'; module.exports = filter; function filter(name, str, options) { if (typeof filter[name] === 'function') { return filter[name](str, options); } else { throw new Error('unknown filter ":' + name + '"'); } } },{}],4:[function(require,module,exports){ 'use strict'; /*! * Jade * Copyright(c) 2010 TJ Holowaychuk * MIT Licensed */ /** * Module dependencies. */ var Parser = require('./parser') , Lexer = require('./lexer') , Compiler = require('./compiler') , runtime = require('./runtime') , addWith = require('with') , fs = require('fs') , utils = require('./utils'); /** * Expose self closing tags. */ // FIXME: either stop exporting selfClosing in v2 or export the new object // form exports.selfClosing = Object.keys(require('void-elements')); /** * Default supported doctypes. */ exports.doctypes = require('./doctypes'); /** * Text filters. */ exports.filters = require('./filters'); /** * Utilities. */ exports.utils = utils; /** * Expose `Compiler`. */ exports.Compiler = Compiler; /** * Expose `Parser`. */ exports.Parser = Parser; /** * Expose `Lexer`. */ exports.Lexer = Lexer; /** * Nodes. */ exports.nodes = require('./nodes'); /** * Jade runtime helpers. */ exports.runtime = runtime; /** * Template function cache. */ exports.cache = {}; /** * Parse the given `str` of jade and return a function body. * * @param {String} str * @param {Object} options * @return {Object} * @api private */ function parse(str, options){ if (options.lexer) { console.warn('Using `lexer` as a local in render() is deprecated and ' + 'will be interpreted as an option in Jade 2.0.0'); } // Parse var parser = new (options.parser || Parser)(str, options.filename, options); var tokens; try { // Parse tokens = parser.parse(); } catch (err) { parser = parser.context(); runtime.rethrow(err, parser.filename, parser.lexer.lineno, parser.input); } // Compile var compiler = new (options.compiler || Compiler)(tokens, options); var js; try { js = compiler.compile(); } catch (err) { if (err.line && (err.filename || !options.filename)) { runtime.rethrow(err, err.filename, err.line, parser.input); } else { if (err instanceof Error) { err.message += '\n\nPlease report this entire error and stack trace to https://github.com/jadejs/jade/issues'; } throw err; } } // Debug compiler if (options.debug) { console.error('\nCompiled Function:\n\n\u001b[90m%s\u001b[0m', js.replace(/^/gm, ' ')); } var globals = []; if (options.globals) { globals = options.globals.slice(); } globals.push('jade'); globals.push('jade_mixins'); globals.push('jade_interp'); globals.push('jade_debug'); globals.push('buf'); var body = '' + 'var buf = [];\n' + 'var jade_mixins = {};\n' + 'var jade_interp;\n' + (options.self ? 'var self = locals || {};\n' + js : addWith('locals || {}', '\n' + js, globals)) + ';' + 'return buf.join("");'; return {body: body, dependencies: parser.dependencies}; } /** * Get the template from a string or a file, either compiled on-the-fly or * read from cache (if enabled), and cache the template if needed. * * If `str` is not set, the file specified in `options.filename` will be read. * * If `options.cache` is true, this function reads the file from * `options.filename` so it must be set prior to calling this function. * * @param {Object} options * @param {String=} str * @return {Function} * @api private */ function handleTemplateCache (options, str) { var key = options.filename; if (options.cache && exports.cache[key]) { return exports.cache[key]; } else { if (str === undefined) str = fs.readFileSync(options.filename, 'utf8'); var templ = exports.compile(str, options); if (options.cache) exports.cache[key] = templ; return templ; } } /** * Compile a `Function` representation of the given jade `str`. * * Options: * * - `compileDebug` when `false` debugging code is stripped from the compiled template, when it is explicitly `true`, the source code is included in the compiled template for better accuracy. * - `filename` used to improve errors when `compileDebug` is not `false` and to resolve imports/extends * * @param {String} str * @param {Options} options * @return {Function} * @api public */ exports.compile = function(str, options){ var options = options || {} , filename = options.filename ? utils.stringify(options.filename) : 'undefined' , fn; str = String(str); var parsed = parse(str, options); if (options.compileDebug !== false) { fn = [ 'var jade_debug = [{ lineno: 1, filename: ' + filename + ' }];' , 'try {' , parsed.body , '} catch (err) {' , ' jade.rethrow(err, jade_debug[0].filename, jade_debug[0].lineno' + (options.compileDebug === true ? ',' + utils.stringify(str) : '') + ');' , '}' ].join('\n'); } else { fn = parsed.body; } fn = new Function('locals, jade', fn) var res = function(locals){ return fn(locals, Object.create(runtime)) }; if (options.client) { res.toString = function () { var err = new Error('The `client` option is deprecated, use the `jade.compileClient` method instead'); err.name = 'Warning'; console.error(err.stack || /* istanbul ignore next */ err.message); return exports.compileClient(str, options); }; } res.dependencies = parsed.dependencies; return res; }; /** * Compile a JavaScript source representation of the given jade `str`. * * Options: * * - `compileDebug` When it is `true`, the source code is included in * the compiled template for better error messages. * - `filename` used to improve errors when `compileDebug` is not `true` and to resolve imports/extends * - `name` the name of the resulting function (defaults to "template") * * @param {String} str * @param {Options} options * @return {Object} * @api public */ exports.compileClientWithDependenciesTracked = function(str, options){ var options = options || {}; var name = options.name || 'template'; var filename = options.filename ? utils.stringify(options.filename) : 'undefined'; var fn; str = String(str); options.compileDebug = options.compileDebug ? true : false; var parsed = parse(str, options); if (options.compileDebug) { fn = [ 'var jade_debug = [{ lineno: 1, filename: ' + filename + ' }];' , 'try {' , parsed.body , '} catch (err) {' , ' jade.rethrow(err, jade_debug[0].filename, jade_debug[0].lineno, ' + utils.stringify(str) + ');' , '}' ].join('\n'); } else { fn = parsed.body; } return {body: 'function ' + name + '(locals) {\n' + fn + '\n}', dependencies: parsed.dependencies}; }; /** * Compile a JavaScript source representation of the given jade `str`. * * Options: * * - `compileDebug` When it is `true`, the source code is included in * the compiled template for better error messages. * - `filename` used to improve errors when `compileDebug` is not `true` and to resolve imports/extends * - `name` the name of the resulting function (defaults to "template") * * @param {String} str * @param {Options} options * @return {String} * @api public */ exports.compileClient = function (str, options) { return exports.compileClientWithDependenciesTracked(str, options).body; }; /** * Compile a `Function` representation of the given jade file. * * Options: * * - `compileDebug` when `false` debugging code is stripped from the compiled template, when it is explicitly `true`, the source code is included in the compiled template for better accuracy. * * @param {String} path * @param {Options} options * @return {Function} * @api public */ exports.compileFile = function (path, options) { options = options || {}; options.filename = path; return handleTemplateCache(options); }; /** * Render the given `str` of jade. * * Options: * * - `cache` enable template caching * - `filename` filename required for `include` / `extends` and caching * * @param {String} str * @param {Object|Function} options or fn * @param {Function|undefined} fn * @returns {String} * @api public */ exports.render = function(str, options, fn){ // support callback API if ('function' == typeof options) { fn = options, options = undefined; } if (typeof fn === 'function') { var res try { res = exports.render(str, options); } catch (ex) { return fn(ex); } return fn(null, res); } options = options || {}; // cache requires .filename if (options.cache && !options.filename) { throw new Error('the "filename" option is required for caching'); } return handleTemplateCache(options, str)(options); }; /** * Render a Jade file at the given `path`. * * @param {String} path * @param {Object|Function} options or callback * @param {Function|undefined} fn * @returns {String} * @api public */ exports.renderFile = function(path, options, fn){ // support callback API if ('function' == typeof options) { fn = options, options = undefined; } if (typeof fn === 'function') { var res try { res = exports.renderFile(path, options); } catch (ex) { return fn(ex); } return fn(null, res); } options = options || {}; options.filename = path; return handleTemplateCache(options)(options); }; /** * Compile a Jade file at the given `path` for use on the client. * * @param {String} path * @param {Object} options * @returns {String} * @api public */ exports.compileFileClient = function(path, options){ var key = path + ':client'; options = options || {}; options.filename = path; if (options.cache && exports.cache[key]) { return exports.cache[key]; } var str = fs.readFileSync(options.filename, 'utf8'); var out = exports.compileClient(str, options); if (options.cache) exports.cache[key] = out; return out; }; /** * Express support. */ exports.__express = exports.renderFile; },{"./compiler":1,"./doctypes":2,"./filters":3,"./lexer":6,"./nodes":16,"./parser":23,"./runtime":24,"./utils":25,"fs":26,"void-elements":34,"with":35}],5:[function(require,module,exports){ 'use strict'; module.exports = [ 'a' , 'abbr' , 'acronym' , 'b' , 'br' , 'code' , 'em' , 'font' , 'i' , 'img' , 'ins' , 'kbd' , 'map' , 'samp' , 'small' , 'span' , 'strong' , 'sub' , 'sup' ]; },{}],6:[function(require,module,exports){ 'use strict'; var utils = require('./utils'); var characterParser = require('character-parser'); /** * Initialize `Lexer` with the given `str`. * * @param {String} str * @param {String} filename * @api private */ var Lexer = module.exports = function Lexer(str, filename) { this.input = str.replace(/\r\n|\r/g, '\n'); this.filename = filename; this.deferredTokens = []; this.lastIndents = 0; this.lineno = 1; this.stash = []; this.indentStack = []; this.indentRe = null; this.pipeless = false; }; function assertExpression(exp) { //this verifies that a JavaScript expression is valid Function('', 'return (' + exp + ')'); } function assertNestingCorrect(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()) { throw new Error('Nesting must match on expression `' + exp + '`') } } /** * Lexer prototype. */ Lexer.prototype = { /** * Construct a token with the given `type` and `val`. * * @param {String} type * @param {String} val * @return {Object} * @api private */ tok: function(type, val){ return { type: type , line: this.lineno , val: val } }, /** * 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)) { this.consume(captures[0].length); return this.tok(type, captures[1]); } }, /** * Defer the given `tok`. * * @param {Object} tok * @api private */ defer: function(tok){ this.deferredTokens.push(tok); }, /** * Lookahead `n` tokens. * * @param {Number} n * @return {Object} * @api private */ lookahead: function(n){ var fetch = n - this.stash.length; while (fetch-- > 0) this.stash.push(this.next()); return this.stash[--n]; }, /** * Return the indexOf `(` or `{` or `[` / `)` or `}` or `]` delimiters. * * @return {Number} * @api private */ bracketExpression: function(skip){ skip = skip || 0; var start = this.input[skip]; if (start != '(' && start != '{' && start != '[') throw new Error('unrecognized start character'); var end = ({'(': ')', '{': '}', '[': ']'})[start]; var range = characterParser.parseMax(this.input, {start: skip + 1}); if (this.input[range.end] !== end) throw new Error('start character ' + start + ' does not match end character ' + this.input[range.end]); return range; }, /** * Stashed token. */ stashed: function() { return this.stash.length && this.stash.shift(); }, /** * Deferred token. */ deferred: function() { return this.deferredTokens.length && this.deferredTokens.shift(); }, /** * end-of-source. */ eos: function() { if (this.input.length) return; if (this.indentStack.length) { this.indentStack.shift(); return this.tok('outdent'); } else { return this.tok('eos'); } }, /** * Blank line. */ blank: function() { var captures; if (captures = /^\n *\n/.exec(this.input)) { this.consume(captures[0].length - 1); ++this.lineno; if (this.pipeless) return this.tok('text', ''); return this.next(); } }, /** * 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.pipeless = true; return tok; } }, /** * Interpolated tag. */ interpolation: function() { if (/^#\{/.test(this.input)) { var match = this.bracketExpression(1); this.consume(match.end + 1); return this.tok('interpolation', match.src); } }, /** * Tag. */ tag: function() { var captures; if (captures = /^(\w[-:\w]*)(\/?)/.exec(this.input)) { this.consume(captures[0].length); var tok, name = captures[1]; if (':' == name[name.length - 1]) { name = name.slice(0, -1); tok = this.tok('tag', name); this.defer(this.tok(':')); if (this.input[0] !== ' ') { console.warn('Warning: space required after `:` on line ' + this.lineno + ' of jade file "' + this.filename + '"'); } while (' ' == this.input[0]) this.input = this.input.substr(1); } else { tok = this.tok('tag', name); } tok.selfClosing = !!captures[2]; return tok; } }, /** * Filter. */ filter: function() { var tok = this.scan(/^:([\w\-]+)/, 'filter'); if (tok) { this.pipeless = true; return tok; } }, /** * Doctype. */ doctype: function() { if (this.scan(/^!!! *([^\n]+)?/, 'doctype')) { throw new Error('`!!!` is deprecated, you must now use `doctype`'); } var node = this.scan(/^(?:doctype) *([^\n]+)?/, 'doctype'); if (node && node.val && node.val.trim() === '5') { throw new Error('`doctype 5` is deprecated, you must now use `doctype html`'); } return node; }, /** * Id. */ id: function() { return this.scan(/^#([\w-]+)/, 'id'); }, /** * Class. */ className: function() { return this.scan(/^\.([\w-]+)/, 'class'); }, /** * Text. */ text: function() { return this.scan(/^(?:\| ?| )([^\n]+)/, 'text') || this.scan(/^\|?( )/, 'text') || this.scan(/^(<[^\n]*)/, 'text'); }, textFail: function () { var tok; if (tok = this.scan(/^([^\.\n][^\n]+)/, 'text')) { console.warn('Warning: missing space before text for line ' + this.lineno + ' of jade file "' + this.filename + '"'); return tok; } }, /** * Dot. */ dot: function() { var match; if (match = this.scan(/^\./, 'dot')) { this.pipeless = true; return match; } }, /** * Extends. */ "extends": function() { return this.scan(/^extends? +([^\n]+)/, 'extends'); }, /** * Block prepend. */ prepend: function() { var captures; if (captures = /^prepend +([^\n]+)/.exec(this.input)) { this.consume(captures[0].length); var mode = 'prepend' , name = captures[1] , tok = this.tok('block', name); tok.mode = mode; return tok; } }, /** * Block append. */ append: function() { var captures; if (captures = /^append +([^\n]+)/.exec(this.input)) { this.consume(captures[0].length); var mode = 'append' , name = captures[1] , tok = this.tok('block', name); tok.mode = mode; return tok; } }, /** * Block. */ block: function() { var captures; if (captures = /^block\b *(?:(prepend|append) +)?([^\n]+)/.exec(this.input)) { this.consume(captures[0].length); var mode = captures[1] || 'replace' , name = captures[2] , tok = this.tok('block', name); tok.mode = mode; return tok; } }, /** * Mixin Block. */ mixinBlock: function() { var captures; if (captures = /^block[ \t]*(\n|$)/.exec(this.input)) { this.consume(captures[0].length - captures[1].length); return this.tok('mixin-block'); } }, /** * Yield. */ 'yield': function() { return this.scan(/^yield */, 'yield'); }, /** * Include. */ include: function() { return this.scan(/^include +([^\n]+)/, 'include'); }, /** * Include with filter */ includeFiltered: function() { var captures; if (captures = /^include:([\w\-]+)([\( ])/.exec(this.input)) { this.consume(captures[0].length - 1); var filter = captures[1]; var attrs = captures[2] === '(' ? this.attrs() : null; if (!(captures[2] === ' ' || this.input[0] === ' ')) { throw new Error('expected space after include:filter but got ' + utils.stringify(this.input[0])); } captures = /^ *([^\n]+)/.exec(this.input); if (!captures || captures[1].trim() === '') { throw new Error('missing path for include:filter'); } this.consume(captures[0].length); var path = captures[1]; var tok = this.tok('include', path); tok.filter = filter; tok.attrs = attrs; return tok; } }, /** * Case. */ "case": function() { return this.scan(/^case +([^\n]+)/, 'case'); }, /** * When. */ when: function() { return this.scan(/^when +([^:\n]+)/, 'when'); }, /** * Default. */ "default": function() { return this.scan(/^default */, 'default'); }, /** * Call mixin. */ call: function(){ var tok, captures; if (captures = /^\+(\s*)(([-\w]+)|(#\{))/.exec(this.input)) { // try to consume simple or interpolated call if (captures[3]) { // simple call this.consume(captures[0].length); tok = this.tok('call', captures[3]); } else { // interpolated call var match = this.bracketExpression(2 + captures[1].length); this.consume(match.end + 1); assertExpression(match.src); tok = this.tok('call', '#{'+match.src+'}'); } // 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.consume(range.end + 1); tok.args = range.src; } if (tok.args) { assertExpression('[' + tok.args + ']'); } } return tok; } }, /** * 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]; return tok; } }, /** * 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] var js = captures[2]; var isIf = false; var isElse = false; switch (type) { case 'if': assertExpression(js) js = 'if (' + js + ')'; isIf = true; break; case 'unless': assertExpression(js) js = 'if (!(' + js + '))'; isIf = true; break; case 'else if': assertExpression(js) js = 'else if (' + js + ')'; isIf = true; isElse = true; break; case 'else': if (js && js.trim()) { throw new Error('`else` cannot have a condition, perhaps you meant `else if`'); } js = 'else'; isElse = true; break; } var tok = this.tok('code', js); tok.isElse = isElse; tok.isIf = isIf; tok.requiresBlock = true; return tok; } }, /** * While. */ "while": function() { var captures; if (captures = /^while +([^\n]+)/.exec(this.input)) { this.consume(captures[0].length); assertExpression(captures[1]) var tok = this.tok('code', 'while (' + captures[1] + ')'); tok.requiresBlock = true; return tok; } }, /** * 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] || '$index'; assertExpression(captures[3]) tok.code = captures[3]; return tok; } }, /** * Code. */ code: function() { var captures; if (captures = /^(!?=|-)[ \t]*([^\n]+)/.exec(this.input)) { this.consume(captures[0].length); var flags = captures[1]; captures[1] = captures[2]; var tok = this.tok('code', captures[1]); tok.escape = flags.charAt(0) === '='; tok.buffer = flags.charAt(0) === '=' || flags.charAt(1) === '='; if (tok.buffer) assertExpression(captures[1]) return tok; } }, /** * Attributes. */ attrs: function() { if ('(' == this.input.charAt(0)) { var index = this.bracketExpression().end , str = this.input.substr(1, index-1) , tok = this.tok('attrs'); assertNestingCorrect(str); var quote = ''; var interpolate = function (attr) { return attr.replace(/(\\)?#\{(.+)/g, function(_, escape, expr){ if (escape) return _; try { var range = characterParser.parseMax(expr); if (expr[range.end] !== '}') return _.substr(0, 2) + interpolate(_.substr(2)); assertExpression(range.src) return quote + " + (" + range.src + ") + " + quote + interpolate(expr.substr(range.end + 1)); } catch (ex) { return _.substr(0, 2) + interpolate(_.substr(2)); } }); } this.consume(index + 1); tok.attrs = []; var escapedAttr = true var key = ''; var val = ''; var interpolatable = ''; var state = characterParser.defaultState(); var loc = 'key'; var isEndOfAttribute = function (i) { if (key.trim() === '') return false; if (i === str.length) return true; if (loc === 'key') { if (str[i] === ' ' || str[i] === '\n') { for (var x = i; x < str.length; x++) { if (str[x] != ' ' && str[x] != '\n') { if (str[x] === '=' || str[x] === '!' || str[x] === ',') return false; else return true; } } } return str[i] === ',' } else if (loc === 'value' && !state.isNesting()) { try { assertExpression(val); if (str[i] === ' ' || str[i] === '\n') { for (var x = i; x < str.length; x++) { if (str[x] != ' ' && str[x] != '\n') { if (characterParser.isPunctuator(str[x]) && str[x] != '"' && str[x] != "'") return false; else return true; } } } return str[i] === ','; } catch (ex) { return false; } } } this.lineno += str.split("\n").length - 1; for (var i = 0; i <= str.length; i++) { if (isEndOfAttribute(i)) { val = val.trim(); if (val) assertExpression(val) key = key.trim(); key = key.replace(/^['"]|['"]$/g, ''); tok.attrs.push({ name: key, val: '' == val ? true : val, escaped: escapedAttr }); key = val = ''; loc = 'key'; escapedAttr = false; } else { switch (loc) { case 'key-char': if (str[i] === quote) { loc = 'key'; if (i + 1 < str.length && [' ', ',', '!', '=', '\n'].indexOf(str[i + 1]) === -1) throw new Error('Unexpected character ' + str[i + 1] + ' expected ` `, `\\n`, `,`, `!` or `=`'); } else { key += str[i]; } break; case 'key': if (key === '' && (str[i] === '"' || str[i] === "'")) { loc = 'key-char'; quote = str[i]; } else if (str[i] === '!' || str[i] === '=') { escapedAttr = str[i] !== '!'; if (str[i] === '!') i++; if (str[i] !== '=') throw new Error('Unexpected character ' + str[i] + ' expected `=`'); loc = 'value'; state = characterParser.defaultState(); } else { key += str[i] } break; case 'value': state = characterParser.parseChar(str[i], state); if (state.isString()) { loc = 'string'; quote = str[i]; interpolatable = str[i]; } else { val += str[i]; } break; case 'string': state = characterParser.parseChar(str[i], state); interpolatable += str[i]; if (!state.isString()) { loc = 'value'; val += interpolate(interpolatable); } break; } } } if ('/' == this.input.charAt(0)) { this.consume(1); tok.selfClosing = true; } return tok; } }, /** * &attributes block */ attributesBlock: function () { var captures; if (/^&attributes\b/.test(this.input)) { this.consume(11); var args = this.bracketExpression(); this.consume(args.end + 1); return this.tok('&attributes', args.src); } }, /** * Indent | Outdent | Newline. */ indent: 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; } if (captures) { var tok , indents = captures[1].length; ++this.lineno; this.consume(indents + 1); if (' ' == this.input[0] || '\t' == this.input[0]) { throw new Error('Invalid indentation, you can use tabs or spaces but not both'); } // blank line if ('\n' == this.input[0]) { this.pipeless = false; return this.tok('newline'); } // outdent if (this.indentStack.length && indents < this.indentStack[0]) { while (this.indentStack.length && this.indentStack[0] > indents) { this.stash.push(this.tok('outdent')); this.indentStack.shift(); } tok = this.stash.pop(); // indent } else if (indents && indents != this.indentStack[0]) { this.indentStack.unshift(indents); tok = this.tok('indent', indents); // newline } else { tok = this.tok('newline'); } this.pipeless = false; return tok; } }, /** * Pipe-less text consumed only when * pipeless is true; */ pipelessText: function() { if (!this.pipeless) return; 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; } var indents = captures && captures[1].length; if (indents && (this.indentStack.length === 0 || indents > this.indentStack[0])) { var indent = captures[1]; var line; var tokens = []; var isMatch; do { // text has `\n` as a prefix var i = this.input.substr(1).indexOf('\n'); if (-1 == i) i = this.input.length - 1; var str = this.input.substr(1, i); isMatch = str.substr(0, indent.length) === indent || !str.trim(); if (isMatch) { // consume test along with `\n` prefix if match this.consume(str.length + 1); tokens.push(str.substr(indent.length)); } } while(this.input.length && isMatch); while (this.input.length === 0 && tokens[tokens.length - 1] === '') tokens.pop(); return this.tok('pipeless-text', tokens); } }, /** * ':' */ colon: function() { var good = /^: +/.test(this.input); var res = this.scan(/^: */, ':'); if (res && !good) { console.warn('Warning: space required after `:` on line ' + this.lineno + ' of jade file "' + this.filename + '"'); } return res; }, fail: function () { throw new Error('unexpected text ' + this.input.substr(0, 5)); }, /** * Return the next token object, or those * previously stashed by lookahead. * * @return {Object} * @api private */ advance: function(){ return this.stashed() || this.next(); }, /** * Return the next token object. * * @return {Object} * @api private */ next: function() { return this.deferred() || this.blank() || this.eos() || this.pipelessText() || this.yield() || this.doctype() || this.interpolation() || this["case"]() || this.when() || this["default"]() || this["extends"]() || this.append() || this.prepend() || this.block() || this.mixinBlock() || this.include() || this.includeFiltered() || this.mixin() || this.call() || this.conditional() || this.each() || this["while"]() || this.tag() || this.filter() || this.code() || this.id() || this.className() || this.attrs() || this.attributesBlock() || this.indent() || this.text() || this.comment() || this.colon() || this.dot() || this.textFail() || this.fail(); } }; },{"./utils":25,"character-parser":29}],7:[function(require,module,exports){ 'use strict'; var Node = require('./node'); /** * Initialize a `Attrs` node. * * @api public */ var Attrs = module.exports = function Attrs() { this.attributeNames = []; this.attrs = []; this.attributeBlocks = []; }; // Inherit from `Node`. Attrs.prototype = Object.create(Node.prototype); Attrs.prototype.constructor = Attrs; Attrs.prototype.type = 'Attrs'; /** * Set attribute `name` to `val`, keep in mind these become * part of a raw js object literal, so to quote a value you must * '"quote me"', otherwise or example 'user.name' is literal JavaScript. * * @param {String} name * @param {String} val * @param {Boolean} escaped * @return {Tag} for chaining * @api public */ Attrs.prototype.setAttribute = function(name, val, escaped){ if (name !== 'class' && this.attributeNames.indexOf(name) !== -1) { throw new Error('Duplicate attribute "' + name + '" is not allowed.'); } this.attributeNames.push(name); this.attrs.push({ name: name, val: val, escaped: escaped }); return this; }; /** * Remove attribute `name` when present. * * @param {String} name * @api public */ Attrs.prototype.removeAttribute = function(name){ var err = new Error('attrs.removeAttribute is deprecated and will be removed in v2.0.0'); console.warn(err.stack); for (var i = 0, len = this.attrs.length; i < len; ++i) { if (this.attrs[i] && this.attrs[i].name == name) { delete this.attrs[i]; } } }; /** * Get attribute value by `name`. * * @param {String} name * @return {String} * @api public */ Attrs.prototype.getAttribute = function(name){ var err = new Error('attrs.getAttribute is deprecated and will be removed in v2.0.0'); console.warn(err.stack); for (var i = 0, len = this.attrs.length; i < len; ++i) { if (this.attrs[i] && this.attrs[i].name == name) { return this.attrs[i].val; } } }; Attrs.prototype.addAttributes = function (src) { this.attributeBlocks.push(src); }; },{"./node":20}],8:[function(require,module,exports){ 'use strict'; var Node = require('./node'); /** * Initialize a `BlockComment` with the given `block`. * * @param {String} val * @param {Block} block * @param {Boolean} buffer * @api public */ var BlockComment = module.exports = function BlockComment(val, block, buffer) { this.block = block; this.val = val; this.buffer = buffer; }; // Inherit from `Node`. BlockComment.prototype = Object.create(Node.prototype); BlockComment.prototype.constructor = BlockComment; BlockComment.prototype.type = 'BlockComment'; },{"./node":20}],9:[function(require,module,exports){ 'use strict'; var Node = require('./node'); /** * Initialize a new `Block` with an optional `node`. * * @param {Node} node * @api public */ var Block = module.exports = function Block(node){ this.nodes = []; if (node) this.push(node); }; // Inherit from `Node`. Block.prototype = Object.create(Node.prototype); Block.prototype.constructor = Block; Block.prototype.type = 'Block'; /** * Block flag. */ Block.prototype.isBlock = true; /** * Replace the nodes in `other` with the nodes * in `this` block. * * @param {Block} other * @api private */ Block.prototype.replace = function(other){ var err = new Error('block.replace is deprecated and will be removed in v2.0.0'); console.warn(err.stack); other.nodes = this.nodes; }; /** * Push the given `node`. * * @param {Node} node * @return {Number} * @api public */ Block.prototype.push = function(node){ return this.nodes.push(node); }; /** * Check if this block is empty. * * @return {Boolean} * @api public */ Block.prototype.isEmpty = function(){ return 0 == this.nodes.length; }; /** * Unshift the given `node`. * * @param {Node} node * @return {Number} * @api public */ Block.prototype.unshift = function(node){ return this.nodes.unshift(node); }; /** * Return the "last" block, or the first `yield` node. * * @return {Block} * @api private */ Block.prototype.includeBlock = function(){ var ret = this , node; for (var i = 0, len = this.nodes.length; i < len; ++i) { node = this.nodes[i]; if (node.yield) return node; else if (node.textOnly) continue; else if (node.includeBlock) ret = node.includeBlock(); else if (node.block && !node.block.isEmpty()) ret = node.block.includeBlock(); if (ret.yield) return ret; } return ret; }; /** * Return a clone of this block. * * @return {Block} * @api private */ Block.prototype.clone = function(){ var err = new Error('block.clone is deprecated and will be removed in v2.0.0'); console.warn(err.stack); var clone = new Block; for (var i = 0, len = this.nodes.length; i < len; ++i) { clone.push(this.nodes[i].clone()); } return clone; }; },{"./node":20}],10:[function(require,module,exports){ 'use strict'; var Node = require('./node'); /** * Initialize a new `Case` with `expr`. * * @param {String} expr * @api public */ var Case = exports = module.exports = function Case(expr, block){ this.expr = expr; this.block = block; }; // Inherit from `Node`. Case.prototype = Object.create(Node.prototype); Case.prototype.constructor = Case; Case.prototype.type = 'Case'; var When = exports.When = function When(expr, block){ this.expr = expr; this.block = block; this.debug = false; }; // Inherit from `Node`. When.prototype = Object.create(Node.prototype); When.prototype.constructor = When; When.prototype.type = 'When'; },{"./node":20}],11:[function(require,module,exports){ 'use strict'; var Node = require('./node'); /** * Initialize a `Code` node with the given code `val`. * Code may also be optionally buffered and escaped. * * @param {String} val * @param {Boolean} buffer * @param {Boolean} escape * @api public */ var Code = module.exports = function Code(val, buffer, escape) { this.val = val; this.buffer = buffer; this.escape = escape; if (val.match(/^ *else/)) this.debug = false; }; // Inherit from `Node`. Code.prototype = Object.create(Node.prototype); Code.prototype.constructor = Code; Code.prototype.type = 'Code'; // prevent the minifiers removing this },{"./node":20}],12:[function(require,module,exports){ 'use strict'; var Node = require('./node'); /** * Initialize a `Comment` with the given `val`, optionally `buffer`, * otherwise the comment may render in the output. * * @param {String} val * @param {Boolean} buffer * @api public */ var Comment = module.exports = function Comment(val, buffer) { this.val = val; this.buffer = buffer; }; // Inherit from `Node`. Comment.prototype = Object.create(Node.prototype); Comment.prototype.constructor = Comment; Comment.prototype.type = 'Comment'; },{"./node":20}],13:[function(require,module,exports){ 'use strict'; var Node = require('./node'); /** * Initialize a `Doctype` with the given `val`. * * @param {String} val * @api public */ var Doctype = module.exports = function Doctype(val) { this.val = val; }; // Inherit from `Node`. Doctype.prototype = Object.create(Node.prototype); Doctype.prototype.constructor = Doctype; Doctype.prototype.type = 'Doctype'; },{"./node":20}],14:[function(require,module,exports){ 'use strict'; var Node = require('./node'); /** * Initialize an `Each` node, representing iteration * * @param {String} obj * @param {String} val * @param {String} key * @param {Block} block * @api public */ var Each = module.exports = function Each(obj, val, key, block) { this.obj = obj; this.val = val; this.key = key; this.block = block; }; // Inherit from `Node`. Each.prototype = Object.create(Node.prototype); Each.prototype.constructor = Each; Each.prototype.type = 'Each'; },{"./node":20}],15:[function(require,module,exports){ 'use strict'; var Node = require('./node'); /** * Initialize a `Filter` node with the given * filter `name` and `block`. * * @param {String} name * @param {Block|Node} block * @api public */ var Filter = module.exports = function Filter(name, block, attrs) { this.name = name; this.block = block; this.attrs = attrs; }; // Inherit from `Node`. Filter.prototype = Object.create(Node.prototype); Filter.prototype.constructor = Filter; Filter.prototype.type = 'Filter'; },{"./node":20}],16:[function(require,module,exports){ 'use strict'; exports.Node = require('./node'); exports.Tag = require('./tag'); exports.Code = require('./code'); exports.Each = require('./each'); exports.Case = require('./case'); exports.Text = require('./text'); exports.Block = require('./block'); exports.MixinBlock = require('./mixin-block'); exports.Mixin = require('./mixin'); exports.Filter = require('./filter'); exports.Comment = require('./comment'); exports.Literal = require('./literal'); exports.BlockComment = require('./block-comment'); exports.Doctype = require('./doctype'); },{"./block":9,"./block-comment":8,"./case":10,"./code":11,"./comment":12,"./doctype":13,"./each":14,"./filter":15,"./literal":17,"./mixin":19,"./mixin-block":18,"./node":20,"./tag":21,"./text":22}],17:[function(require,module,exports){ 'use strict'; var Node = require('./node'); /** * Initialize a `Literal` node with the given `str. * * @param {String} str * @api public */ var Literal = module.exports = function Literal(str) { this.str = str; }; // Inherit from `Node`. Literal.prototype = Object.create(Node.prototype); Literal.prototype.constructor = Literal; Literal.prototype.type = 'Literal'; },{"./node":20}],18:[function(require,module,exports){ 'use strict'; var Node = require('./node'); /** * Initialize a new `Block` with an optional `node`. * * @param {Node} node * @api public */ var MixinBlock = module.exports = function MixinBlock(){}; // Inherit from `Node`. MixinBlock.prototype = Object.create(Node.prototype); MixinBlock.prototype.constructor = MixinBlock; MixinBlock.prototype.type = 'MixinBlock'; },{"./node":20}],19:[function(require,module,exports){ 'use strict'; var Attrs = require('./attrs'); /** * Initialize a new `Mixin` with `name` and `block`. * * @param {String} name * @param {String} args * @param {Block} block * @api public */ var Mixin = module.exports = function Mixin(name, args, block, call){ Attrs.call(this); this.name = name; this.args = args; this.block = block; this.call = call; }; // Inherit from `Attrs`. Mixin.prototype = Object.create(Attrs.prototype); Mixin.prototype.constructor = Mixin; Mixin.prototype.type = 'Mixin'; },{"./attrs":7}],20:[function(require,module,exports){ 'use strict'; var Node = module.exports = function Node(){}; /** * Clone this node (return itself) * * @return {Node} * @api private */ Node.prototype.clone = function(){ var err = new Error('node.clone is deprecated and will be removed in v2.0.0'); console.warn(err.stack); return this; }; Node.prototype.type = ''; },{}],21:[function(require,module,exports){ 'use strict'; var Attrs = require('./attrs'); var Block = require('./block'); var inlineTags = require('../inline-tags'); /** * Initialize a `Tag` node with the given tag `name` and optional `block`. * * @param {String} name * @param {Block} block * @api public */ var Tag = module.exports = function Tag(name, block) { Attrs.call(this); this.name = name; this.block = block || new Block; }; // Inherit from `Attrs`. Tag.prototype = Object.create(Attrs.prototype); Tag.prototype.constructor = Tag; Tag.prototype.type = 'Tag'; /** * Clone this tag. * * @return {Tag} * @api private */ Tag.prototype.clone = function(){ var err = new Error('tag.clone is deprecated and will be removed in v2.0.0'); console.warn(err.stack); var clone = new Tag(this.name, this.block.clone()); clone.line = this.line; clone.attrs = this.attrs; clone.textOnly = this.textOnly; return clone; }; /** * Check if this tag is an inline tag. * * @return {Boolean} * @api private */ Tag.prototype.isInline = function(){ return ~inlineTags.indexOf(this.name); }; /** * Check if this tag's contents can be inlined. Used for pretty printing. * * @return {Boolean} * @api private */ Tag.prototype.canInline = function(){ var nodes = this.block.nodes; function isInline(node){ // Recurse if the node is a block if (node.isBlock) return node.nodes.every(isInline); return node.isText || (node.isInline && node.isInline()); } // Empty tag if (!nodes.length) return true; // Text-only or inline-only tag if (1 == nodes.length) return isInline(nodes[0]); // Multi-line inline-only tag if (this.block.nodes.every(isInline)) { for (var i = 1, len = nodes.length; i < len; ++i) { if (nodes[i-1].isText && nodes[i].isText) return false; } return true; } // Mixed tag return false; }; },{"../inline-tags":5,"./attrs":7,"./block":9}],22:[function(require,module,exports){ 'use strict'; var Node = require('./node'); /** * Initialize a `Text` node with optional `line`. * * @param {String} line * @api public */ var Text = module.exports = function Text(line) { this.val = line; }; // Inherit from `Node`. Text.prototype = Object.create(Node.prototype); Text.prototype.constructor = Text; Text.prototype.type = 'Text'; /** * Flag as text. */ Text.prototype.isText = true; },{"./node":20}],23:[function(require,module,exports){ 'use strict'; var Lexer = require('./lexer'); var nodes = require('./nodes'); var utils = require('./utils'); var filters = require('./filters'); var path = require('path'); var constantinople = require('constantinople'); var parseJSExpression = require('character-parser').parseMax; var extname = path.extname; /** * Initialize `Parser` with the given input `str` and `filename`. * * @param {String} str * @param {String} filename * @param {Object} options * @api public */ var Parser = exports = module.exports = function Parser(str, filename, options){ //Strip any UTF-8 BOM off of the start of `str`, if it exists. this.input = str.replace(/^\uFEFF/, ''); this.lexer = new Lexer(this.input, filename); this.filename = filename; this.blocks = {}; this.mixins = {}; this.options = options; this.contexts = [this]; this.inMixin = 0; this.dependencies = []; this.inBlock = 0; }; /** * Parser prototype. */ Parser.prototype = { /** * Save original constructor */ constructor: Parser, /** * Push `parser` onto the context stack, * or pop and return a `Parser`. */ context: function(parser){ if (parser) { this.contexts.push(parser); } else { return this.contexts.pop(); } }, /** * Return the next token object. * * @return {Object} * @api private */ advance: function(){ return this.lexer.advance(); }, /** * Single token lookahead. * * @return {Object} * @api private */ peek: function() { return this.lookahead(1); }, /** * Return lexer lineno. * * @return {Number} * @api private */ line: function() { return this.lexer.lineno; }, /** * `n` token lookahead. * * @param {Number} n * @return {Object} * @api private */ lookahead: function(n){ return this.lexer.lookahead(n); }, /** * Parse input returning a string of js for evaluation. * * @return {String} * @api public */ parse: function(){ var block = new nodes.Block, parser; block.line = 0; block.filename = this.filename; while ('eos' != this.peek().type) { if ('newline' == this.peek().type) { this.advance(); } else { var next = this.peek(); var expr = this.parseExpr(); expr.filename = expr.filename || this.filename; expr.line = next.line; block.push(expr); } } if (parser = this.extending) { this.context(parser); var ast = parser.parse(); this.context(); // hoist mixins for (var name in this.mixins) ast.unshift(this.mixins[name]); return ast; } if (!this.extending && !this.included && Object.keys(this.blocks).length){ var blocks = []; utils.walkAST(block, function (node) { if (node.type === 'Block' && node.name) { blocks.push(node.name); } }); Object.keys(this.blocks).forEach(function (name) { if (blocks.indexOf(name) === -1 && !this.blocks[name].isSubBlock) { console.warn('Warning: Unexpected block "' + name + '" ' + ' on line ' + this.blocks[name].line + ' of ' + (this.blocks[name].filename) + '. This block is never used. This warning will be an error in v2.0.0'); } }.bind(this)); } return block; }, /** * Expect the given type, or throw an exception. * * @param {String} type * @api private */ expect: function(type){ if (this.peek().type === type) { return this.advance(); } else { throw new Error('expected "' + type + '", but got "' + this.peek().type + '"'); } }, /** * Accept the given `type`. * * @param {String} type * @api private */ accept: function(type){ if (this.peek().type === type) { return this.advance(); } }, /** * tag * | doctype * | mixin * | include * | filter * | comment * | text * | each * | code * | yield * | id * | class * | interpolation */ parseExpr: function(){ switch (this.peek().type) { case 'tag': return this.parseTag(); case 'mixin': return this.parseMixin(); case 'block': return this.parseBlock(); case 'mixin-block': return this.parseMixinBlock(); case 'case': return this.parseCase(); case 'extends': return this.parseExtends(); case 'include': return this.parseInclude(); case 'doctype': return this.parseDoctype(); case 'filter': return this.parseFilter(); case 'comment': return this.parseComment(); case 'text': return this.parseText(); case 'each': return this.parseEach(); case 'code': return this.parseCode(); case 'call': return this.parseCall(); case 'interpolation': return this.parseInterpolation(); case 'yield': this.advance(); var block = new nodes.Block; block.yield = true; return block; case 'id': case 'class': var tok = this.advance(); this.lexer.defer(this.lexer.tok('tag', 'div')); this.lexer.defer(tok); return this.parseExpr(); default: throw new Error('unexpected token "' + this.peek().type + '"'); } }, /** * Text */ parseText: function(){ var tok = this.expect('text'); var tokens = this.parseInlineTagsInText(tok.val); if (tokens.length === 1) return tokens[0]; var node = new nodes.Block; for (var i = 0; i < tokens.length; i++) { node.push(tokens[i]); }; return node; }, /** * ':' expr * | block */ parseBlockExpansion: function(){ if (':' == this.peek().type) { this.advance(); return new nodes.Block(this.parseExpr()); } else { return this.block(); } }, /** * case */ parseCase: function(){ var val = this.expect('case').val; var node = new nodes.Case(val); node.line = this.line(); var block = new nodes.Block; block.line = this.line(); block.filename = this.filename; this.expect('indent'); while ('outdent' != this.peek().type) { switch (this.peek().type) { case 'comment': case 'newline': this.advance(); break; case 'when': block.push(this.parseWhen()); break; case 'default': block.push(this.parseDefault()); break; default: throw new Error('Unexpected token "' + this.peek().type + '", expected "when", "default" or "newline"'); } } this.expect('outdent'); node.block = block; return node; }, /** * when */ parseWhen: function(){ var val = this.expect('when').val; if (this.peek().type !== 'newline') return new nodes.Case.When(val, this.parseBlockExpansion()); else return new nodes.Case.When(val); }, /** * default */ parseDefault: function(){ this.expect('default'); return new nodes.Case.When('default', this.parseBlockExpansion()); }, /** * code */ parseCode: function(afterIf){ var tok = this.expect('code'); var node = new nodes.Code(tok.val, tok.buffer, tok.escape); var block; node.line = this.line(); // throw an error if an else does not have an if if (tok.isElse && !tok.hasIf) { throw new Error('Unexpected else without if'); } // handle block block = 'indent' == this.peek().type; if (block) { node.block = this.block(); } // handle missing block if (tok.requiresBlock && !block) { node.block = new nodes.Block(); } // mark presense of if for future elses if (tok.isIf && this.peek().isElse) { this.peek().hasIf = true; } else if (tok.isIf && this.peek().type === 'newline' && this.lookahead(2).isElse) { this.lookahead(2).hasIf = true; } return node; }, /** * comment */ parseComment: function(){ var tok = this.expect('comment'); var node; var block; if (block = this.parseTextBlock()) { node = new nodes.BlockComment(tok.val, block, tok.buffer); } else { node = new nodes.Comment(tok.val, tok.buffer); } node.line = this.line(); return node; }, /** * doctype */ parseDoctype: function(){ var tok = this.expect('doctype'); var node = new nodes.Doctype(tok.val); node.line = this.line(); return node; }, /** * filter attrs? text-block */ parseFilter: function(){ var tok = this.expect('filter'); var attrs = this.accept('attrs'); var block; block = this.parseTextBlock() || new nodes.Block(); var options = {}; if (attrs) { attrs.attrs.forEach(function (attribute) { options[attribute.name] = constantinople.toConstant(attribute.val); }); } var node = new nodes.Filter(tok.val, block, options); node.line = this.line(); return node; }, /** * each block */ parseEach: function(){ var tok = this.expect('each'); var node = new nodes.Each(tok.code, tok.val, tok.key); node.line = this.line(); node.block = this.block(); if (this.peek().type == 'code' && this.peek().val == 'else') { this.advance(); node.alternative = this.block(); } return node; }, /** * Resolves a path relative to the template for use in * includes and extends * * @param {String} path * @param {String} purpose Used in error messages. * @return {String} * @api private */ resolvePath: function (path, purpose) { var p = require('path'); var dirname = p.dirname; var basename = p.basename; var join = p.join; if (path[0] !== '/' && !this.filename) throw new Error('the "filename" option is required to use "' + purpose + '" with "relative" paths'); if (path[0] === '/' && !this.options.basedir) throw new Error('the "basedir" option is required to use "' + purpose + '" with "absolute" paths'); path = join(path[0] === '/' ? this.options.basedir : dirname(this.filename), path); if (basename(path).indexOf('.') === -1) path += '.jade'; return path; }, /** * 'extends' name */ parseExtends: function(){ var fs = require('fs'); var path = this.resolvePath(this.expect('extends').val.trim(), 'extends'); if ('.jade' != path.substr(-5)) path += '.jade'; this.dependencies.push(path); var str = fs.readFileSync(path, 'utf8'); var parser = new this.constructor(str, path, this.options); parser.dependencies = this.dependencies; parser.blocks = this.blocks; parser.included = this.included; parser.contexts = this.contexts; this.extending = parser; // TODO: null node return new nodes.Literal(''); }, /** * 'block' name block */ parseBlock: function(){ var block = this.expect('block'); var mode = block.mode; var name = block.val.trim(); var line = block.line; this.inBlock++; block = 'indent' == this.peek().type ? this.block() : new nodes.Block(new nodes.Literal('')); this.inBlock--; block.name = name; block.line = line; var prev = this.blocks[name] || {prepended: [], appended: []} if (prev.mode === 'replace') return this.blocks[name] = prev; var allNodes = prev.prepended.concat(block.nodes).concat(prev.appended); switch (mode) { case 'append': prev.appended = prev.parser === this ? prev.appended.concat(block.nodes) : block.nodes.concat(prev.appended); break; case 'prepend': prev.prepended = prev.parser === this ? block.nodes.concat(prev.prepended) : prev.prepended.concat(block.nodes); break; } block.nodes = allNodes; block.appended = prev.appended; block.prepended = prev.prepended; block.mode = mode; block.parser = this; block.isSubBlock = this.inBlock > 0; return this.blocks[name] = block; }, parseMixinBlock: function () { var block = this.expect('mixin-block'); if (!this.inMixin) { throw new Error('Anonymous blocks are not allowed unless they are part of a mixin.'); } return new nodes.MixinBlock(); }, /** * include block? */ parseInclude: function(){ var fs = require('fs'); var tok = this.expect('include'); var path = this.resolvePath(tok.val.trim(), 'include'); this.dependencies.push(path); // has-filter if (tok.filter) { var str = fs.readFileSync(path, 'utf8').replace(/\r/g, ''); var options = {filename: path}; if (tok.attrs) { tok.attrs.attrs.forEach(function (attribute) { options[attribute.name] = constantinople.toConstant(attribute.val); }); } str = filters(tok.filter, str, options); return new nodes.Literal(str); } // non-jade if ('.jade' != path.substr(-5)) { var str = fs.readFileSync(path, 'utf8').replace(/\r/g, ''); return new nodes.Literal(str); } var str = fs.readFileSync(path, 'utf8'); var parser = new this.constructor(str, path, this.options); parser.dependencies = this.dependencies; parser.blocks = utils.merge({}, this.blocks); parser.included = true; parser.mixins = this.mixins; this.context(parser); var ast = parser.parse(); this.context(); ast.filename = path; if ('indent' == this.peek().type) { ast.includeBlock().push(this.block()); } return ast; }, /** * call ident block */ parseCall: function(){ var tok = this.expect('call'); var name = tok.val; var args = tok.args; var mixin = new nodes.Mixin(name, args, new nodes.Block, true); this.tag(mixin); if (mixin.code) { mixin.block.push(mixin.code); mixin.code = null; } if (mixin.block.isEmpty()) mixin.block = null; return mixin; }, /** * mixin block */ parseMixin: function(){ var tok = this.expect('mixin'); var name = tok.val; var args = tok.args; var mixin; // definition if ('indent' == this.peek().type) { this.inMixin++; mixin = new nodes.Mixin(name, args, this.block(), false); this.mixins[name] = mixin; this.inMixin--; return mixin; // call } else { return new nodes.Mixin(name, args, null, true); } }, parseInlineTagsInText: function (str) { var line = this.line(); var match = /(\\)?#\[((?:.|\n)*)$/.exec(str); if (match) { if (match[1]) { // escape var text = new nodes.Text(str.substr(0, match.index) + '#['); text.line = line; var rest = this.parseInlineTagsInText(match[2]); if (rest[0].type === 'Text') { text.val += rest[0].val; rest.shift(); } return [text].concat(rest); } else { var text = new nodes.Text(str.substr(0, match.index)); text.line = line; var buffer = [text]; var rest = match[2]; var range = parseJSExpression(rest); var inner = new Parser(range.src, this.filename, this.options); buffer.push(inner.parse()); return buffer.concat(this.parseInlineTagsInText(rest.substr(range.end + 1))); } } else { var text = new nodes.Text(str); text.line = line; return [text]; } }, /** * indent (text | newline)* outdent */ parseTextBlock: function(){ var block = new nodes.Block; block.line = this.line(); var body = this.peek(); if (body.type !== 'pipeless-text') return; this.advance(); block.nodes = body.val.reduce(function (accumulator, text) { return accumulator.concat(this.parseInlineTagsInText(text)); }.bind(this), []); return block; }, /** * indent expr* outdent */ block: function(){ var block = new nodes.Block; block.line = this.line(); block.filename = this.filename; this.expect('indent'); while ('outdent' != this.peek().type) { if ('newline' == this.peek().type) { this.advance(); } else { var expr = this.parseExpr(); expr.filename = this.filename; block.push(expr); } } this.expect('outdent'); return block; }, /** * interpolation (attrs | class | id)* (text | code | ':')? newline* block? */ parseInterpolation: function(){ var tok = this.advance(); var tag = new nodes.Tag(tok.val); tag.buffer = true; return this.tag(tag); }, /** * tag (attrs | class | id)* (text | code | ':')? newline* block? */ parseTag: function(){ var tok = this.advance(); var tag = new nodes.Tag(tok.val); tag.selfClosing = tok.selfClosing; return this.tag(tag); }, /** * Parse tag. */ tag: function(tag){ tag.line = this.line(); var seenAttrs = false; // (attrs | class | id)* out: while (true) { switch (this.peek().type) { case 'id': case 'class': var tok = this.advance(); tag.setAttribute(tok.type, "'" + tok.val + "'"); continue; case 'attrs': if (seenAttrs) { console.warn(this.filename + ', line ' + this.peek().line + ':\nYou should not have jade tags with multiple attributes.'); } seenAttrs = true; var tok = this.advance(); var attrs = tok.attrs; if (tok.selfClosing) tag.selfClosing = true; for (var i = 0; i < attrs.length; i++) { tag.setAttribute(attrs[i].name, attrs[i].val, attrs[i].escaped); } continue; case '&attributes': var tok = this.advance(); tag.addAttributes(tok.val); break; default: break out; } } // check immediate '.' if ('dot' == this.peek().type) { tag.textOnly = true; this.advance(); } // (text | code | ':')? switch (this.peek().type) { case 'text': tag.block.push(this.parseText()); break; case 'code': tag.code = this.parseCode(); break; case ':': this.advance(); tag.block = new nodes.Block; tag.block.push(this.parseExpr()); break; case 'newline': case 'indent': case 'outdent': case 'eos': case 'pipeless-text': break; default: throw new Error('Unexpected token `' + this.peek().type + '` expected `text`, `code`, `:`, `newline` or `eos`') } // newline* while ('newline' == this.peek().type) this.advance(); // block? if (tag.textOnly) { tag.block = this.parseTextBlock() || new nodes.Block(); } else if ('indent' == this.peek().type) { var block = this.block(); for (var i = 0, len = block.nodes.length; i < len; ++i) { tag.block.push(block.nodes[i]); } } return tag; } }; },{"./filters":3,"./lexer":6,"./nodes":16,"./utils":25,"character-parser":29,"constantinople":30,"fs":26,"path":27}],24:[function(require,module,exports){ 'use strict'; /** * Merge two attribute objects giving precedence * to values in object `b`. Classes are special-cased * allowing for arrays and merging/joining appropriately * resulting in a string. * * @param {Object} a * @param {Object} b * @return {Object} a * @api private */ exports.merge = function merge(a, b) { if (arguments.length === 1) { var attrs = a[0]; for (var i = 1; i < a.length; i++) { attrs = merge(attrs, a[i]); } return attrs; } var ac = a['class']; var bc = b['class']; if (ac || bc) { ac = ac || []; bc = bc || []; if (!Array.isArray(ac)) ac = [ac]; if (!Array.isArray(bc)) bc = [bc]; a['class'] = ac.concat(bc).filter(nulls); } for (var key in b) { if (key != 'class') { a[key] = b[key]; } } return a; }; /** * Filter null `val`s. * * @param {*} val * @return {Boolean} * @api private */ function nulls(val) { return val != null && val !== ''; } /** * join array as classes. * * @param {*} val * @return {String} */ exports.joinClasses = joinClasses; function joinClasses(val) { return (Array.isArray(val) ? val.map(joinClasses) : (val && typeof val === 'object') ? Object.keys(val).filter(function (key) { return val[key]; }) : [val]).filter(nulls).join(' '); } /** * Render the given classes. * * @param {Array} classes * @param {Array.} escaped * @return {String} */ exports.cls = function cls(classes, escaped) { var buf = []; for (var i = 0; i < classes.length; i++) { if (escaped && escaped[i]) { buf.push(exports.escape(joinClasses([classes[i]]))); } else { buf.push(joinClasses(classes[i])); } } var text = joinClasses(buf); if (text.length) { return ' class="' + text + '"'; } else { return ''; } }; exports.style = function (val) { if (val && typeof val === 'object') { return Object.keys(val).map(function (style) { return style + ':' + val[style]; }).join(';'); } else { return val; } }; /** * Render the given attribute. * * @param {String} key * @param {String} val * @param {Boolean} escaped * @param {Boolean} terse * @return {String} */ exports.attr = function attr(key, val, escaped, terse) { if (key === 'style') { val = exports.style(val); } if ('boolean' == typeof val || null == val) { if (val) { return ' ' + (terse ? key : key + '="' + key + '"'); } else { return ''; } } else if (0 == key.indexOf('data') && 'string' != typeof val) { if (JSON.stringify(val).indexOf('&') !== -1) { console.warn('Since Jade 2.0.0, ampersands (`&`) in data attributes ' + 'will be escaped to `&`'); }; if (val && typeof val.toISOString === 'function') { console.warn('Jade will eliminate the double quotes around dates in ' + 'ISO form after 2.0.0'); } return ' ' + key + "='" + JSON.stringify(val).replace(/'/g, ''') + "'"; } else if (escaped) { if (val && typeof val.toISOString === 'function') { console.warn('Jade will stringify dates in ISO form after 2.0.0'); } return ' ' + key + '="' + exports.escape(val) + '"'; } else { if (val && typeof val.toISOString === 'function') { console.warn('Jade will stringify dates in ISO form after 2.0.0'); } return ' ' + key + '="' + val + '"'; } }; /** * Render the given attributes object. * * @param {Object} obj * @param {Object} escaped * @return {String} */ exports.attrs = function attrs(obj, terse){ var buf = []; var keys = Object.keys(obj); if (keys.length) { for (var i = 0; i < keys.length; ++i) { var key = keys[i] , val = obj[key]; if ('class' == key) { if (val = joinClasses(val)) { buf.push(' ' + key + '="' + val + '"'); } } else { buf.push(exports.attr(key, val, false, terse)); } } } return buf.join(''); }; /** * Escape the given string of `html`. * * @param {String} html * @return {String} * @api private */ exports.escape = function escape(html){ var result = String(html) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"'); if (result === '' + html) return html; else return result; }; /** * Re-throw the given `err` in context to the * the jade in `filename` at the given `lineno`. * * @param {Error} err * @param {String} filename * @param {String} lineno * @api private */ exports.rethrow = function rethrow(err, filename, lineno, str){ if (!(err instanceof Error)) throw err; if ((typeof window != 'undefined' || !filename) && !str) { err.message += ' on line ' + lineno; throw err; } try { str = str || require('fs').readFileSync(filename, 'utf8') } catch (ex) { rethrow(err, null, lineno) } var context = 3 , lines = str.split('\n') , start = Math.max(lineno - context, 0) , end = Math.min(lines.length, lineno + context); // Error context var context = lines.slice(start, end).map(function(line, i){ var curr = i + start + 1; return (curr == lineno ? ' > ' : ' ') + curr + '| ' + line; }).join('\n'); // Alter exception message err.path = filename; err.message = (filename || 'Jade') + ':' + lineno + '\n' + context + '\n\n' + err.message; throw err; }; },{"fs":26}],25:[function(require,module,exports){ 'use strict'; /** * Merge `b` into `a`. * * @param {Object} a * @param {Object} b * @return {Object} * @api public */ exports.merge = function(a, b) { for (var key in b) a[key] = b[key]; return a; }; exports.stringify = function(str) { return JSON.stringify(str) .replace(/\u2028/g, '\\u2028') .replace(/\u2029/g, '\\u2029'); }; exports.walkAST = function walkAST(ast, before, after) { before && before(ast); switch (ast.type) { case 'Block': ast.nodes.forEach(function (node) { walkAST(node, before, after); }); break; case 'Case': case 'Each': case 'Mixin': case 'Tag': case 'When': case 'Code': ast.block && walkAST(ast.block, before, after); break; case 'Attrs': case 'BlockComment': case 'Comment': case 'Doctype': case 'Filter': case 'Literal': case 'MixinBlock': case 'Text': break; default: throw new Error('Unexpected node type ' + ast.type); break; } after && after(ast); }; },{}],26:[function(require,module,exports){ },{}],27:[function(require,module,exports){ (function (process){ // Copyright Joyent, Inc. and other Node contributors. // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to permit // persons to whom the Software is furnished to do so, subject to the // following conditions: // // The above copyright notice and this permission notice shall be included // in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE // USE OR OTHER DEALINGS IN THE SOFTWARE. // resolves . and .. elements in a path array with directory names there // must be no slashes, empty elements, or device names (c:\) in the array // (so also no leading and trailing slashes - it does not distinguish // relative and absolute paths) function normalizeArray(parts, allowAboveRoot) { // if the path tries to go above the root, `up` ends up > 0 var up = 0; for (var i = parts.length - 1; i >= 0; i--) { var last = parts[i]; if (last === '.') { parts.splice(i, 1); } else if (last === '..') { parts.splice(i, 1); up++; } else if (up) { parts.splice(i, 1); up--; } } // if the path is allowed to go above the root, restore leading ..s if (allowAboveRoot) { for (; up--; up) { parts.unshift('..'); } } return parts; } // Split a filename into [root, dir, basename, ext], unix version // 'root' is just a slash, or nothing. var splitPathRe = /^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/; var splitPath = function(filename) { return splitPathRe.exec(filename).slice(1); }; // path.resolve([from ...], to) // posix version exports.resolve = function() { var resolvedPath = '', resolvedAbsolute = false; for (var i = arguments.length - 1; i >= -1 && !resolvedAbsolute; i--) { var path = (i >= 0) ? arguments[i] : process.cwd(); // Skip empty and invalid entries if (typeof path !== 'string') { throw new TypeError('Arguments to path.resolve must be strings'); } else if (!path) { continue; } resolvedPath = path + '/' + resolvedPath; resolvedAbsolute = path.charAt(0) === '/'; } // At this point the path should be resolved to a full absolute path, but // handle relative paths to be safe (might happen when process.cwd() fails) // Normalize the path resolvedPath = normalizeArray(filter(resolvedPath.split('/'), function(p) { return !!p; }), !resolvedAbsolute).join('/'); return ((resolvedAbsolute ? '/' : '') + resolvedPath) || '.'; }; // path.normalize(path) // posix version exports.normalize = function(path) { var isAbsolute = exports.isAbsolute(path), trailingSlash = substr(path, -1) === '/'; // Normalize the path path = normalizeArray(filter(path.split('/'), function(p) { return !!p; }), !isAbsolute).join('/'); if (!path && !isAbsolute) { path = '.'; } if (path && trailingSlash) { path += '/'; } return (isAbsolute ? '/' : '') + path; }; // posix version exports.isAbsolute = function(path) { return path.charAt(0) === '/'; }; // posix version exports.join = function() { var paths = Array.prototype.slice.call(arguments, 0); return exports.normalize(filter(paths, function(p, index) { if (typeof p !== 'string') { throw new TypeError('Arguments to path.join must be strings'); } return p; }).join('/')); }; // path.relative(from, to) // posix version exports.relative = function(from, to) { from = exports.resolve(from).substr(1); to = exports.resolve(to).substr(1); function trim(arr) { var start = 0; for (; start < arr.length; start++) { if (arr[start] !== '') break; } var end = arr.length - 1; for (; end >= 0; end--) { if (arr[end] !== '') break; } if (start > end) return []; return arr.slice(start, end - start + 1); } var fromParts = trim(from.split('/')); var toParts = trim(to.split('/')); var length = Math.min(fromParts.length, toParts.length); var samePartsLength = length; for (var i = 0; i < length; i++) { if (fromParts[i] !== toParts[i]) { samePartsLength = i; break; } } var outputParts = []; for (var i = samePartsLength; i < fromParts.length; i++) { outputParts.push('..'); } outputParts = outputParts.concat(toParts.slice(samePartsLength)); return outputParts.join('/'); }; exports.sep = '/'; exports.delimiter = ':'; exports.dirname = function(path) { var result = splitPath(path), root = result[0], dir = result[1]; if (!root && !dir) { // No dirname whatsoever return '.'; } if (dir) { // It has a dirname, strip trailing slash dir = dir.substr(0, dir.length - 1); } return root + dir; }; exports.basename = function(path, ext) { var f = splitPath(path)[2]; // TODO: make this comparison case-insensitive on windows? if (ext && f.substr(-1 * ext.length) === ext) { f = f.substr(0, f.length - ext.length); } return f; }; exports.extname = function(path) { return splitPath(path)[3]; }; function filter (xs, f) { if (xs.filter) return xs.filter(f); var res = []; for (var i = 0; i < xs.length; i++) { if (f(xs[i], i, xs)) res.push(xs[i]); } return res; } // String.prototype.substr - negative index don't work in IE8 var substr = 'ab'.substr(-1) === 'b' ? function (str, start, len) { return str.substr(start, len) } : function (str, start, len) { if (start < 0) start = str.length + start; return str.substr(start, len); } ; }).call(this,require('_process')) },{"_process":28}],28:[function(require,module,exports){ // shim for using process in browser var process = module.exports = {}; process.nextTick = (function () { var canSetImmediate = typeof window !== 'undefined' && window.setImmediate; var canMutationObserver = typeof window !== 'undefined' && window.MutationObserver; var canPost = typeof window !== 'undefined' && window.postMessage && window.addEventListener ; if (canSetImmediate) { return function (f) { return window.setImmediate(f) }; } var queue = []; if (canMutationObserver) { var hiddenDiv = document.createElement("div"); var observer = new MutationObserver(function () { var queueList = queue.slice(); queue.length = 0; queueList.forEach(function (fn) { fn(); }); }); observer.observe(hiddenDiv, { attributes: true }); return function nextTick(fn) { if (!queue.length) { hiddenDiv.setAttribute('yes', 'no'); } queue.push(fn); }; } if (canPost) { window.addEventListener('message', function (ev) { var source = ev.source; if ((source === window || source === null) && ev.data === 'process-tick') { ev.stopPropagation(); if (queue.length > 0) { var fn = queue.shift(); fn(); } } }, true); return function nextTick(fn) { queue.push(fn); window.postMessage('process-tick', '*'); }; } return function nextTick(fn) { setTimeout(fn, 0); }; })(); process.title = 'browser'; process.browser = true; process.env = {}; process.argv = []; function noop() {} process.on = noop; process.addListener = noop; process.once = noop; process.off = noop; process.removeListener = noop; process.removeAllListeners = noop; process.emit = noop; process.binding = function (name) { throw new Error('process.binding is not supported'); }; // TODO(shtylman) process.cwd = function () { return '/' }; process.chdir = function (dir) { throw new Error('process.chdir is not supported'); }; },{}],29:[function(require,module,exports){ exports = (module.exports = parse); exports.parse = parse; function parse(src, state, options) { options = options || {}; state = state || exports.defaultState(); var start = options.start || 0; var end = options.end || src.length; var index = start; while (index < end) { if (state.roundDepth < 0 || state.curlyDepth < 0 || state.squareDepth < 0) { throw new SyntaxError('Mismatched Bracket: ' + src[index - 1]); } exports.parseChar(src[index++], state); } return state; } exports.parseMax = parseMax; function parseMax(src, options) { options = options || {}; var start = options.start || 0; var index = start; var state = exports.defaultState(); while (state.roundDepth >= 0 && state.curlyDepth >= 0 && state.squareDepth >= 0) { if (index >= src.length) { throw new Error('The end of the string was reached with no closing bracket found.'); } exports.parseChar(src[index++], state); } var end = index - 1; return { start: start, end: end, src: src.substring(start, end) }; } exports.parseUntil = parseUntil; function parseUntil(src, delimiter, options) { options = options || {}; var includeLineComment = options.includeLineComment || false; var start = options.start || 0; var index = start; var state = exports.defaultState(); while (state.isString() || state.regexp || state.blockComment || (!includeLineComment && state.lineComment) || !startsWith(src, delimiter, index)) { exports.parseChar(src[index++], state); } var end = index; return { start: start, end: end, src: src.substring(start, end) }; } exports.parseChar = parseChar; function parseChar(character, state) { if (character.length !== 1) throw new Error('Character must be a string of length 1'); state = state || exports.defaultState(); state.src = state.src || ''; state.src += character; var wasComment = state.blockComment || state.lineComment; var lastChar = state.history ? state.history[0] : ''; if (state.regexpStart) { if (character === '/' || character == '*') { state.regexp = false; } state.regexpStart = false; } if (state.lineComment) { if (character === '\n') { state.lineComment = false; } } else if (state.blockComment) { if (state.lastChar === '*' && character === '/') { state.blockComment = false; } } else if (state.singleQuote) { if (character === '\'' && !state.escaped) { state.singleQuote = false; } else if (character === '\\' && !state.escaped) { state.escaped = true; } else { state.escaped = false; } } else if (state.doubleQuote) { if (character === '"' && !state.escaped) { state.doubleQuote = false; } else if (character === '\\' && !state.escaped) { state.escaped = true; } else { state.escaped = false; } } else if (state.regexp) { if (character === '/' && !state.escaped) { state.regexp = false; } else if (character === '\\' && !state.escaped) { state.escaped = true; } else { state.escaped = false; } } else if (lastChar === '/' && character === '/') { state.history = state.history.substr(1); state.lineComment = true; } else if (lastChar === '/' && character === '*') { state.history = state.history.substr(1); state.blockComment = true; } else if (character === '/' && isRegexp(state.history)) { state.regexp = true; state.regexpStart = true; } else if (character === '\'') { state.singleQuote = true; } else if (character === '"') { state.doubleQuote = true; } else if (character === '(') { state.roundDepth++; } else if (character === ')') { state.roundDepth--; } else if (character === '{') { state.curlyDepth++; } else if (character === '}') { state.curlyDepth--; } else if (character === '[') { state.squareDepth++; } else if (character === ']') { state.squareDepth--; } if (!state.blockComment && !state.lineComment && !wasComment) state.history = character + state.history; state.lastChar = character; // store last character for ending block comments return state; } exports.defaultState = function () { return new State() }; function State() { this.lineComment = false; this.blockComment = false; this.singleQuote = false; this.doubleQuote = false; this.regexp = false; this.escaped = false; this.roundDepth = 0; this.curlyDepth = 0; this.squareDepth = 0; this.history = '' this.lastChar = '' } State.prototype.isString = function () { return this.singleQuote || this.doubleQuote; } State.prototype.isComment = function () { return this.lineComment || this.blockComment; } State.prototype.isNesting = function () { return this.isString() || this.isComment() || this.regexp || this.roundDepth > 0 || this.curlyDepth > 0 || this.squareDepth > 0 } function startsWith(str, start, i) { return str.substr(i || 0, start.length) === start; } exports.isPunctuator = isPunctuator function isPunctuator(c) { if (!c) return true; // the start of a string is a punctuator var code = c.charCodeAt(0) switch (code) { case 46: // . dot case 40: // ( open bracket case 41: // ) close bracket case 59: // ; semicolon case 44: // , comma case 123: // { open curly brace case 125: // } close curly brace case 91: // [ case 93: // ] case 58: // : case 63: // ? case 126: // ~ case 37: // % case 38: // & case 42: // *: case 43: // + case 45: // - case 47: // / case 60: // < case 62: // > case 94: // ^ case 124: // | case 33: // ! case 61: // = return true; default: return false; } } exports.isKeyword = isKeyword function isKeyword(id) { return (id === 'if') || (id === 'in') || (id === 'do') || (id === 'var') || (id === 'for') || (id === 'new') || (id === 'try') || (id === 'let') || (id === 'this') || (id === 'else') || (id === 'case') || (id === 'void') || (id === 'with') || (id === 'enum') || (id === 'while') || (id === 'break') || (id === 'catch') || (id === 'throw') || (id === 'const') || (id === 'yield') || (id === 'class') || (id === 'super') || (id === 'return') || (id === 'typeof') || (id === 'delete') || (id === 'switch') || (id === 'export') || (id === 'import') || (id === 'default') || (id === 'finally') || (id === 'extends') || (id === 'function') || (id === 'continue') || (id === 'debugger') || (id === 'package') || (id === 'private') || (id === 'interface') || (id === 'instanceof') || (id === 'implements') || (id === 'protected') || (id === 'public') || (id === 'static') || (id === 'yield') || (id === 'let'); } function isRegexp(history) { //could be start of regexp or divide sign history = history.replace(/^\s*/, ''); //unless its an `if`, `while`, `for` or `with` it's a divide, so we assume it's a divide if (history[0] === ')') return false; //unless it's a function expression, it's a regexp, so we assume it's a regexp if (history[0] === '}') return true; //any punctuation means it's a regexp if (isPunctuator(history[0])) return true; //if the last thing was a keyword then it must be a regexp (e.g. `typeof /foo/`) if (/^\w+\b/.test(history) && isKeyword(/^\w+\b/.exec(history)[0].split('').reverse().join(''))) return true; return false; } },{}],30:[function(require,module,exports){ 'use strict' var detect = require('acorn-globals'); var lastSRC = '(null)'; var lastRes = true; var lastConstants = undefined; module.exports = isConstant; function isConstant(src, constants) { src = '(' + src + ')'; if (lastSRC === src && lastConstants === constants) return lastRes; lastSRC = src; lastConstants = constants; try { isExpression(src); return lastRes = (detect(src).filter(function (key) { return !constants || !(key.name in constants); }).length === 0); } catch (ex) { return lastRes = false; } } isConstant.isConstant = isConstant; isConstant.toConstant = toConstant; function toConstant(src, constants) { if (!isConstant(src, constants)) throw new Error(JSON.stringify(src) + ' is not constant.'); return Function(Object.keys(constants || {}).join(','), 'return (' + src + ')').apply(null, Object.keys(constants || {}).map(function (key) { return constants[key]; })); } function isExpression(src) { try { eval('throw "STOP"; (function () { return (' + src + '); })()'); return false; } catch (err) { return err === 'STOP'; } } },{"acorn-globals":31}],31:[function(require,module,exports){ 'use strict'; var acorn = require('acorn'); var walk = require('acorn/util/walk'); function isScope(node) { return node.type === 'FunctionExpression' || node.type === 'FunctionDeclaration' || node.type === 'Program'; } function isBlockScope(node) { return node.type === 'BlockStatement' || isScope(node); } function declaresArguments(node) { return node.type === 'FunctionExpression' || node.type === 'FunctionDeclaration' || node.type === 'ArrowFunction'; } function declaresThis(node) { return node.type === 'FunctionExpression' || node.type === 'FunctionDeclaration'; } module.exports = findGlobals; function findGlobals(source) { var globals = []; var ast = typeof source === 'string' ? acorn.parse(source, { ecmaVersion: 6, allowReturnOutsideFunction: true }) : source; if (!(ast && typeof ast === 'object' && ast.type === 'Program')) { throw new TypeError('Source must be either a string of JavaScript or an acorn AST'); } var declareFunction = function (node) { var fn = node; fn.locals = fn.locals || {}; node.params.forEach(function (node) { fn.locals[node.name] = true; }); if (node.id) { fn.locals[node.id.name] = true; } } walk.ancestor(ast, { 'VariableDeclaration': function (node, parents) { var parent = null; for (var i = parents.length - 1; i >= 0 && parent === null; i--) { if (node.kind === 'var' ? isScope(parents[i]) : isBlockScope(parents[i])) { parent = parents[i]; } } parent.locals = parent.locals || {}; node.declarations.forEach(function (declaration) { parent.locals[declaration.id.name] = true; }); }, 'FunctionDeclaration': function (node, parents) { var parent = null; for (var i = parents.length - 2; i >= 0 && parent === null; i--) { if (isScope(parents[i])) { parent = parents[i]; } } parent.locals = parent.locals || {}; parent.locals[node.id.name] = true; declareFunction(node); }, 'Function': declareFunction, 'TryStatement': function (node) { node.handler.body.locals = node.handler.body.locals || {}; node.handler.body.locals[node.handler.param.name] = true; } }); walk.ancestor(ast, { 'Identifier': function (node, parents) { var name = node.name; if (name === 'undefined') return; for (var i = 0; i < parents.length; i++) { if (name === 'arguments' && declaresArguments(parents[i])) { return; } if (parents[i].locals && name in parents[i].locals) { return; } } node.parents = parents; globals.push(node); }, ThisExpression: function (node, parents) { for (var i = 0; i < parents.length; i++) { if (declaresThis(parents[i])) { return; } } node.parents = parents; globals.push(node); } }); var groupedGlobals = {}; globals.forEach(function (node) { groupedGlobals[node.name] = (groupedGlobals[node.name] || []); groupedGlobals[node.name].push(node); }); return Object.keys(groupedGlobals).sort().map(function (name) { return {name: name, nodes: groupedGlobals[name]}; }); } },{"acorn":32,"acorn/util/walk":33}],32:[function(require,module,exports){ // Acorn is a tiny, fast JavaScript parser written in JavaScript. // // Acorn was written by Marijn Haverbeke and various contributors and // released under an MIT license. The Unicode regexps (for identifiers // and whitespace) were taken from [Esprima](http://esprima.org) by // Ariya Hidayat. // // Git repositories for Acorn are available at // // http://marijnhaverbeke.nl/git/acorn // https://github.com/marijnh/acorn.git // // Please use the [github bug tracker][ghbt] to report issues. // // [ghbt]: https://github.com/marijnh/acorn/issues // // This file defines the main parser interface. The library also comes // with a [error-tolerant parser][dammit] and an // [abstract syntax tree walker][walk], defined in other files. // // [dammit]: acorn_loose.js // [walk]: util/walk.js (function(root, mod) { if (typeof exports == "object" && typeof module == "object") return mod(exports); // CommonJS if (typeof define == "function" && define.amd) return define(["exports"], mod); // AMD mod(root.acorn || (root.acorn = {})); // Plain browser env })(this, function(exports) { "use strict"; exports.version = "0.8.0"; // The main exported interface (under `self.acorn` when in the // browser) is a `parse` function that takes a code string and // returns an abstract syntax tree as specified by [Mozilla parser // API][api], with the caveat that inline XML is not recognized. // // [api]: https://developer.mozilla.org/en-US/docs/SpiderMonkey/Parser_API var options, input, inputLen, sourceFile; exports.parse = function(inpt, opts) { input = String(inpt); inputLen = input.length; setOptions(opts); initTokenState(); return parseTopLevel(options.program); }; // A second optional argument can be given to further configure // the parser process. These options are recognized: var defaultOptions = exports.defaultOptions = { // `ecmaVersion` indicates the ECMAScript version to parse. Must // be either 3, or 5, or 6. This influences support for strict // mode, the set of reserved words, support for getters and // setters and other features. ecmaVersion: 5, // Turn on `strictSemicolons` to prevent the parser from doing // automatic semicolon insertion. strictSemicolons: false, // When `allowTrailingCommas` is false, the parser will not allow // trailing commas in array and object literals. allowTrailingCommas: true, // By default, reserved words are not enforced. Enable // `forbidReserved` to enforce them. When this option has the // value "everywhere", reserved words and keywords can also not be // used as property names. forbidReserved: false, // When enabled, a return at the top level is not considered an // error. allowReturnOutsideFunction: false, // When `locations` is on, `loc` properties holding objects with // `start` and `end` properties in `{line, column}` form (with // line being 1-based and column 0-based) will be attached to the // nodes. locations: false, // A function can be passed as `onToken` option, which will // cause Acorn to call that function with object in the same // format as tokenize() returns. Note that you are not // allowed to call the parser from the callback—that will // corrupt its internal state. onToken: null, // A function can be passed as `onComment` option, which will // cause Acorn to call that function with `(block, text, start, // end)` parameters whenever a comment is skipped. `block` is a // boolean indicating whether this is a block (`/* */`) comment, // `text` is the content of the comment, and `start` and `end` are // character offsets that denote the start and end of the comment. // When the `locations` option is on, two more parameters are // passed, the full `{line, column}` locations of the start and // end of the comments. Note that you are not allowed to call the // parser from the callback—that will corrupt its internal state. onComment: null, // Nodes have their start and end characters offsets recorded in // `start` and `end` properties (directly on the node, rather than // the `loc` object, which holds line/column data. To also add a // [semi-standardized][range] `range` property holding a `[start, // end]` array with the same numbers, set the `ranges` option to // `true`. // // [range]: https://bugzilla.mozilla.org/show_bug.cgi?id=745678 ranges: false, // It is possible to parse multiple files into a single AST by // passing the tree produced by parsing the first file as // `program` option in subsequent parses. This will add the // toplevel forms of the parsed file to the `Program` (top) node // of an existing parse tree. program: null, // When `locations` is on, you can pass this to record the source // file in every node's `loc` object. sourceFile: null, // This value, if given, is stored in every node, whether // `locations` is on or off. directSourceFile: null }; var isArray = function (obj) { return Object.prototype.toString.call(obj) === "[object Array]"; }; function setOptions(opts) { options = opts || {}; for (var opt in defaultOptions) if (!has(options, opt)) options[opt] = defaultOptions[opt]; sourceFile = options.sourceFile || null; if (isArray(options.onToken)) { var tokens = options.onToken; options.onToken = function (token) { tokens.push(token); }; } if (isArray(options.onComment)) { var comments = options.onComment; options.onComment = function (block, text, start, end, startLoc, endLoc) { var comment = { type: block ? 'Block' : 'Line', value: text, start: start, end: end }; if (options.locations) { comment.loc = new SourceLocation(); comment.loc.start = startLoc; comment.loc.end = endLoc; } if (options.ranges) comment.range = [start, end]; comments.push(comment); }; } isKeyword = options.ecmaVersion >= 6 ? isEcma6Keyword : isEcma5AndLessKeyword; } // The `getLineInfo` function is mostly useful when the // `locations` option is off (for performance reasons) and you // want to find the line/column position for a given character // offset. `input` should be the code string that the offset refers // into. var getLineInfo = exports.getLineInfo = function(input, offset) { for (var line = 1, cur = 0;;) { lineBreak.lastIndex = cur; var match = lineBreak.exec(input); if (match && match.index < offset) { ++line; cur = match.index + match[0].length; } else break; } return {line: line, column: offset - cur}; }; function Token() { this.type = tokType; this.value = tokVal; this.start = tokStart; this.end = tokEnd; if (options.locations) { this.loc = new SourceLocation(); this.loc.end = tokEndLoc; // TODO: remove in next major release this.startLoc = tokStartLoc; this.endLoc = tokEndLoc; } if (options.ranges) this.range = [tokStart, tokEnd]; } exports.Token = Token; // Acorn is organized as a tokenizer and a recursive-descent parser. // The `tokenize` export provides an interface to the tokenizer. // Because the tokenizer is optimized for being efficiently used by // the Acorn parser itself, this interface is somewhat crude and not // very modular. Performing another parse or call to `tokenize` will // reset the internal state, and invalidate existing tokenizers. exports.tokenize = function(inpt, opts) { input = String(inpt); inputLen = input.length; setOptions(opts); initTokenState(); function getToken(forceRegexp) { lastEnd = tokEnd; readToken(forceRegexp); return new Token(); } getToken.jumpTo = function(pos, reAllowed) { tokPos = pos; if (options.locations) { tokCurLine = 1; tokLineStart = lineBreak.lastIndex = 0; var match; while ((match = lineBreak.exec(input)) && match.index < pos) { ++tokCurLine; tokLineStart = match.index + match[0].length; } } tokRegexpAllowed = reAllowed; skipSpace(); }; return getToken; }; // State is kept in (closure-)global variables. We already saw the // `options`, `input`, and `inputLen` variables above. // The current position of the tokenizer in the input. var tokPos; // The start and end offsets of the current token. var tokStart, tokEnd; // When `options.locations` is true, these hold objects // containing the tokens start and end line/column pairs. var tokStartLoc, tokEndLoc; // The type and value of the current token. Token types are objects, // named by variables against which they can be compared, and // holding properties that describe them (indicating, for example, // the precedence of an infix operator, and the original name of a // keyword token). The kind of value that's held in `tokVal` depends // on the type of the token. For literals, it is the literal value, // for operators, the operator name, and so on. var tokType, tokVal; // Internal state for the tokenizer. To distinguish between division // operators and regular expressions, it remembers whether the last // token was one that is allowed to be followed by an expression. // (If it is, a slash is probably a regexp, if it isn't it's a // division operator. See the `parseStatement` function for a // caveat.) var tokRegexpAllowed; // When `options.locations` is true, these are used to keep // track of the current line, and know when a new line has been // entered. var tokCurLine, tokLineStart; // These store the position of the previous token, which is useful // when finishing a node and assigning its `end` position. var lastStart, lastEnd, lastEndLoc; // This is the parser's state. `inFunction` is used to reject // `return` statements outside of functions, `inGenerator` to // reject `yield`s outside of generators, `labels` to verify // that `break` and `continue` have somewhere to jump to, and // `strict` indicates whether strict mode is on. var inFunction, inGenerator, labels, strict; // This counter is used for checking that arrow expressions did // not contain nested parentheses in argument list. var metParenL; // This is used by parser for detecting if it's inside ES6 // Template String. If it is, it should treat '$' as prefix before // '{expression}' and everything else as string literals. var inTemplate; // This function is used to raise exceptions on parse errors. It // takes an offset integer (into the current `input`) to indicate // the location of the error, attaches the position to the end // of the error message, and then raises a `SyntaxError` with that // message. function raise(pos, message) { var loc = getLineInfo(input, pos); message += " (" + loc.line + ":" + loc.column + ")"; var err = new SyntaxError(message); err.pos = pos; err.loc = loc; err.raisedAt = tokPos; throw err; } // Reused empty array added for node fields that are always empty. var empty = []; // ## Token types // The assignment of fine-grained, information-carrying type objects // allows the tokenizer to store the information it has about a // token in a way that is very cheap for the parser to look up. // All token type variables start with an underscore, to make them // easy to recognize. // These are the general types. The `type` property is only used to // make them recognizeable when debugging. var _num = {type: "num"}, _regexp = {type: "regexp"}, _string = {type: "string"}; var _name = {type: "name"}, _eof = {type: "eof"}; // Keyword tokens. The `keyword` property (also used in keyword-like // operators) indicates that the token originated from an // identifier-like word, which is used when parsing property names. // // The `beforeExpr` property is used to disambiguate between regular // expressions and divisions. It is set on all token types that can // be followed by an expression (thus, a slash after them would be a // regular expression). // // `isLoop` marks a keyword as starting a loop, which is important // to know when parsing a label, in order to allow or disallow // continue jumps to that label. var _break = {keyword: "break"}, _case = {keyword: "case", beforeExpr: true}, _catch = {keyword: "catch"}; var _continue = {keyword: "continue"}, _debugger = {keyword: "debugger"}, _default = {keyword: "default"}; var _do = {keyword: "do", isLoop: true}, _else = {keyword: "else", beforeExpr: true}; var _finally = {keyword: "finally"}, _for = {keyword: "for", isLoop: true}, _function = {keyword: "function"}; var _if = {keyword: "if"}, _return = {keyword: "return", beforeExpr: true}, _switch = {keyword: "switch"}; var _throw = {keyword: "throw", beforeExpr: true}, _try = {keyword: "try"}, _var = {keyword: "var"}; var _let = {keyword: "let"}, _const = {keyword: "const"}; var _while = {keyword: "while", isLoop: true}, _with = {keyword: "with"}, _new = {keyword: "new", beforeExpr: true}; var _this = {keyword: "this"}; var _class = {keyword: "class"}, _extends = {keyword: "extends", beforeExpr: true}; var _export = {keyword: "export"}, _import = {keyword: "import"}; var _yield = {keyword: "yield", beforeExpr: true}; // The keywords that denote values. var _null = {keyword: "null", atomValue: null}, _true = {keyword: "true", atomValue: true}; var _false = {keyword: "false", atomValue: false}; // Some keywords are treated as regular operators. `in` sometimes // (when parsing `for`) needs to be tested against specifically, so // we assign a variable name to it for quick comparing. var _in = {keyword: "in", binop: 7, beforeExpr: true}; // Map keyword names to token types. var keywordTypes = {"break": _break, "case": _case, "catch": _catch, "continue": _continue, "debugger": _debugger, "default": _default, "do": _do, "else": _else, "finally": _finally, "for": _for, "function": _function, "if": _if, "return": _return, "switch": _switch, "throw": _throw, "try": _try, "var": _var, "let": _let, "const": _const, "while": _while, "with": _with, "null": _null, "true": _true, "false": _false, "new": _new, "in": _in, "instanceof": {keyword: "instanceof", binop: 7, beforeExpr: true}, "this": _this, "typeof": {keyword: "typeof", prefix: true, beforeExpr: true}, "void": {keyword: "void", prefix: true, beforeExpr: true}, "delete": {keyword: "delete", prefix: true, beforeExpr: true}, "class": _class, "extends": _extends, "export": _export, "import": _import, "yield": _yield}; // Punctuation token types. Again, the `type` property is purely for debugging. var _bracketL = {type: "[", beforeExpr: true}, _bracketR = {type: "]"}, _braceL = {type: "{", beforeExpr: true}; var _braceR = {type: "}"}, _parenL = {type: "(", beforeExpr: true}, _parenR = {type: ")"}; var _comma = {type: ",", beforeExpr: true}, _semi = {type: ";", beforeExpr: true}; var _colon = {type: ":", beforeExpr: true}, _dot = {type: "."}, _ellipsis = {type: "..."}, _question = {type: "?", beforeExpr: true}; var _arrow = {type: "=>", beforeExpr: true}, _bquote = {type: "`"}, _dollarBraceL = {type: "${", beforeExpr: true}; // Operators. These carry several kinds of properties to help the // parser use them properly (the presence of these properties is // what categorizes them as operators). // // `binop`, when present, specifies that this operator is a binary // operator, and will refer to its precedence. // // `prefix` and `postfix` mark the operator as a prefix or postfix // unary operator. `isUpdate` specifies that the node produced by // the operator should be of type UpdateExpression rather than // simply UnaryExpression (`++` and `--`). // // `isAssign` marks all of `=`, `+=`, `-=` etcetera, which act as // binary operators with a very low precedence, that should result // in AssignmentExpression nodes. var _slash = {binop: 10, beforeExpr: true}, _eq = {isAssign: true, beforeExpr: true}; var _assign = {isAssign: true, beforeExpr: true}; var _incDec = {postfix: true, prefix: true, isUpdate: true}, _prefix = {prefix: true, beforeExpr: true}; var _logicalOR = {binop: 1, beforeExpr: true}; var _logicalAND = {binop: 2, beforeExpr: true}; var _bitwiseOR = {binop: 3, beforeExpr: true}; var _bitwiseXOR = {binop: 4, beforeExpr: true}; var _bitwiseAND = {binop: 5, beforeExpr: true}; var _equality = {binop: 6, beforeExpr: true}; var _relational = {binop: 7, beforeExpr: true}; var _bitShift = {binop: 8, beforeExpr: true}; var _plusMin = {binop: 9, prefix: true, beforeExpr: true}; var _modulo = {binop: 10, beforeExpr: true}; // '*' may be multiply or have special meaning in ES6 var _star = {binop: 10, beforeExpr: true}; // Provide access to the token types for external users of the // tokenizer. exports.tokTypes = {bracketL: _bracketL, bracketR: _bracketR, braceL: _braceL, braceR: _braceR, parenL: _parenL, parenR: _parenR, comma: _comma, semi: _semi, colon: _colon, dot: _dot, ellipsis: _ellipsis, question: _question, slash: _slash, eq: _eq, name: _name, eof: _eof, num: _num, regexp: _regexp, string: _string, arrow: _arrow, bquote: _bquote, dollarBraceL: _dollarBraceL}; for (var kw in keywordTypes) exports.tokTypes["_" + kw] = keywordTypes[kw]; // This is a trick taken from Esprima. It turns out that, on // non-Chrome browsers, to check whether a string is in a set, a // predicate containing a big ugly `switch` statement is faster than // a regular expression, and on Chrome the two are about on par. // This function uses `eval` (non-lexical) to produce such a // predicate from a space-separated string of words. // // It starts by sorting the words by length. function makePredicate(words) { words = words.split(" "); var f = "", cats = []; out: for (var i = 0; i < words.length; ++i) { for (var j = 0; j < cats.length; ++j) if (cats[j][0].length == words[i].length) { cats[j].push(words[i]); continue out; } cats.push([words[i]]); } function compareTo(arr) { if (arr.length == 1) return f += "return str === " + JSON.stringify(arr[0]) + ";"; f += "switch(str){"; for (var i = 0; i < arr.length; ++i) f += "case " + JSON.stringify(arr[i]) + ":"; f += "return true}return false;"; } // When there are more than three length categories, an outer // switch first dispatches on the lengths, to save on comparisons. if (cats.length > 3) { cats.sort(function(a, b) {return b.length - a.length;}); f += "switch(str.length){"; for (var i = 0; i < cats.length; ++i) { var cat = cats[i]; f += "case " + cat[0].length + ":"; compareTo(cat); } f += "}"; // Otherwise, simply generate a flat `switch` statement. } else { compareTo(words); } return new Function("str", f); } // The ECMAScript 3 reserved word list. var isReservedWord3 = makePredicate("abstract boolean byte char class double enum export extends final float goto implements import int interface long native package private protected public short static super synchronized throws transient volatile"); // ECMAScript 5 reserved words. var isReservedWord5 = makePredicate("class enum extends super const export import"); // The additional reserved words in strict mode. var isStrictReservedWord = makePredicate("implements interface let package private protected public static yield"); // The forbidden variable names in strict mode. var isStrictBadIdWord = makePredicate("eval arguments"); // And the keywords. var ecma5AndLessKeywords = "break case catch continue debugger default do else finally for function if return switch throw try var while with null true false instanceof typeof void delete new in this"; var isEcma5AndLessKeyword = makePredicate(ecma5AndLessKeywords); var isEcma6Keyword = makePredicate(ecma5AndLessKeywords + " let const class extends export import yield"); var isKeyword = isEcma5AndLessKeyword; // ## Character categories // Big ugly regular expressions that match characters in the // whitespace, identifier, and identifier-start categories. These // are only applied when a character is found to actually have a // code point above 128. // Generated by `tools/generate-identifier-regex.js`. var nonASCIIwhitespace = /[\u1680\u180e\u2000-\u200a\u202f\u205f\u3000\ufeff]/; var nonASCIIidentifierStartChars = "\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u052F\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0-\u08B2\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0980\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58\u0C59\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D60\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F4\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F8\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19C1-\u19C7\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2160-\u2188\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCC\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA66E\uA67F-\uA69D\uA6A0-\uA6EF\uA717-\uA71F\uA722-\uA788\uA78B-\uA78E\uA790-\uA7AD\uA7B0\uA7B1\uA7F7-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uA9E0-\uA9E4\uA9E6-\uA9EF\uA9FA-\uA9FE\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA7E-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB5F\uAB64\uAB65\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC"; var nonASCIIidentifierChars = "\u0300-\u036F\u0483-\u0487\u0591-\u05BD\u05BF\u05C1\u05C2\u05C4\u05C5\u05C7\u0610-\u061A\u064B-\u0669\u0670\u06D6-\u06DC\u06DF-\u06E4\u06E7\u06E8\u06EA-\u06ED\u06F0-\u06F9\u0711\u0730-\u074A\u07A6-\u07B0\u07C0-\u07C9\u07EB-\u07F3\u0816-\u0819\u081B-\u0823\u0825-\u0827\u0829-\u082D\u0859-\u085B\u08E4-\u0903\u093A-\u093C\u093E-\u094F\u0951-\u0957\u0962\u0963\u0966-\u096F\u0981-\u0983\u09BC\u09BE-\u09C4\u09C7\u09C8\u09CB-\u09CD\u09D7\u09E2\u09E3\u09E6-\u09EF\u0A01-\u0A03\u0A3C\u0A3E-\u0A42\u0A47\u0A48\u0A4B-\u0A4D\u0A51\u0A66-\u0A71\u0A75\u0A81-\u0A83\u0ABC\u0ABE-\u0AC5\u0AC7-\u0AC9\u0ACB-\u0ACD\u0AE2\u0AE3\u0AE6-\u0AEF\u0B01-\u0B03\u0B3C\u0B3E-\u0B44\u0B47\u0B48\u0B4B-\u0B4D\u0B56\u0B57\u0B62\u0B63\u0B66-\u0B6F\u0B82\u0BBE-\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCD\u0BD7\u0BE6-\u0BEF\u0C00-\u0C03\u0C3E-\u0C44\u0C46-\u0C48\u0C4A-\u0C4D\u0C55\u0C56\u0C62\u0C63\u0C66-\u0C6F\u0C81-\u0C83\u0CBC\u0CBE-\u0CC4\u0CC6-\u0CC8\u0CCA-\u0CCD\u0CD5\u0CD6\u0CE2\u0CE3\u0CE6-\u0CEF\u0D01-\u0D03\u0D3E-\u0D44\u0D46-\u0D48\u0D4A-\u0D4D\u0D57\u0D62\u0D63\u0D66-\u0D6F\u0D82\u0D83\u0DCA\u0DCF-\u0DD4\u0DD6\u0DD8-\u0DDF\u0DE6-\u0DEF\u0DF2\u0DF3\u0E31\u0E34-\u0E3A\u0E47-\u0E4E\u0E50-\u0E59\u0EB1\u0EB4-\u0EB9\u0EBB\u0EBC\u0EC8-\u0ECD\u0ED0-\u0ED9\u0F18\u0F19\u0F20-\u0F29\u0F35\u0F37\u0F39\u0F3E\u0F3F\u0F71-\u0F84\u0F86\u0F87\u0F8D-\u0F97\u0F99-\u0FBC\u0FC6\u102B-\u103E\u1040-\u1049\u1056-\u1059\u105E-\u1060\u1062-\u1064\u1067-\u106D\u1071-\u1074\u1082-\u108D\u108F-\u109D\u135D-\u135F\u1712-\u1714\u1732-\u1734\u1752\u1753\u1772\u1773\u17B4-\u17D3\u17DD\u17E0-\u17E9\u180B-\u180D\u1810-\u1819\u18A9\u1920-\u192B\u1930-\u193B\u1946-\u194F\u19B0-\u19C0\u19C8\u19C9\u19D0-\u19D9\u1A17-\u1A1B\u1A55-\u1A5E\u1A60-\u1A7C\u1A7F-\u1A89\u1A90-\u1A99\u1AB0-\u1ABD\u1B00-\u1B04\u1B34-\u1B44\u1B50-\u1B59\u1B6B-\u1B73\u1B80-\u1B82\u1BA1-\u1BAD\u1BB0-\u1BB9\u1BE6-\u1BF3\u1C24-\u1C37\u1C40-\u1C49\u1C50-\u1C59\u1CD0-\u1CD2\u1CD4-\u1CE8\u1CED\u1CF2-\u1CF4\u1CF8\u1CF9\u1DC0-\u1DF5\u1DFC-\u1DFF\u200C\u200D\u203F\u2040\u2054\u20D0-\u20DC\u20E1\u20E5-\u20F0\u2CEF-\u2CF1\u2D7F\u2DE0-\u2DFF\u302A-\u302F\u3099\u309A\uA620-\uA629\uA66F\uA674-\uA67D\uA69F\uA6F0\uA6F1\uA802\uA806\uA80B\uA823-\uA827\uA880\uA881\uA8B4-\uA8C4\uA8D0-\uA8D9\uA8E0-\uA8F1\uA900-\uA909\uA926-\uA92D\uA947-\uA953\uA980-\uA983\uA9B3-\uA9C0\uA9D0-\uA9D9\uA9E5\uA9F0-\uA9F9\uAA29-\uAA36\uAA43\uAA4C\uAA4D\uAA50-\uAA59\uAA7B-\uAA7D\uAAB0\uAAB2-\uAAB4\uAAB7\uAAB8\uAABE\uAABF\uAAC1\uAAEB-\uAAEF\uAAF5\uAAF6\uABE3-\uABEA\uABEC\uABED\uABF0-\uABF9\uFB1E\uFE00-\uFE0F\uFE20-\uFE2D\uFE33\uFE34\uFE4D-\uFE4F\uFF10-\uFF19\uFF3F"; var nonASCIIidentifierStart = new RegExp("[" + nonASCIIidentifierStartChars + "]"); var nonASCIIidentifier = new RegExp("[" + nonASCIIidentifierStartChars + nonASCIIidentifierChars + "]"); // Whether a single character denotes a newline. var newline = /[\n\r\u2028\u2029]/; // Matches a whole line break (where CRLF is considered a single // line break). Used to count lines. var lineBreak = /\r\n|[\n\r\u2028\u2029]/g; // Test whether a given character code starts an identifier. var isIdentifierStart = exports.isIdentifierStart = function(code) { if (code < 65) return code === 36; if (code < 91) return true; if (code < 97) return code === 95; if (code < 123)return true; return code >= 0xaa && nonASCIIidentifierStart.test(String.fromCharCode(code)); }; // Test whether a given character is part of an identifier. var isIdentifierChar = exports.isIdentifierChar = function(code) { if (code < 48) return code === 36; if (code < 58) return true; if (code < 65) return false; if (code < 91) return true; if (code < 97) return code === 95; if (code < 123)return true; return code >= 0xaa && nonASCIIidentifier.test(String.fromCharCode(code)); }; // ## Tokenizer // These are used when `options.locations` is on, for the // `tokStartLoc` and `tokEndLoc` properties. function Position() { this.line = tokCurLine; this.column = tokPos - tokLineStart; } // Reset the token state. Used at the start of a parse. function initTokenState() { tokCurLine = 1; tokPos = tokLineStart = 0; tokRegexpAllowed = true; metParenL = 0; inTemplate = false; skipSpace(); } // Called at the end of every token. Sets `tokEnd`, `tokVal`, and // `tokRegexpAllowed`, and skips the space after the token, so that // the next one's `tokStart` will point at the right position. function finishToken(type, val, shouldSkipSpace) { tokEnd = tokPos; if (options.locations) tokEndLoc = new Position; tokType = type; if (shouldSkipSpace !== false) skipSpace(); tokVal = val; tokRegexpAllowed = type.beforeExpr; if (options.onToken) { options.onToken(new Token()); } } function skipBlockComment() { var startLoc = options.onComment && options.locations && new Position; var start = tokPos, end = input.indexOf("*/", tokPos += 2); if (end === -1) raise(tokPos - 2, "Unterminated comment"); tokPos = end + 2; if (options.locations) { lineBreak.lastIndex = start; var match; while ((match = lineBreak.exec(input)) && match.index < tokPos) { ++tokCurLine; tokLineStart = match.index + match[0].length; } } if (options.onComment) options.onComment(true, input.slice(start + 2, end), start, tokPos, startLoc, options.locations && new Position); } function skipLineComment(startSkip) { var start = tokPos; var startLoc = options.onComment && options.locations && new Position; var ch = input.charCodeAt(tokPos+=startSkip); while (tokPos < inputLen && ch !== 10 && ch !== 13 && ch !== 8232 && ch !== 8233) { ++tokPos; ch = input.charCodeAt(tokPos); } if (options.onComment) options.onComment(false, input.slice(start + startSkip, tokPos), start, tokPos, startLoc, options.locations && new Position); } // Called at the start of the parse and after every token. Skips // whitespace and comments, and. function skipSpace() { while (tokPos < inputLen) { var ch = input.charCodeAt(tokPos); if (ch === 32) { // ' ' ++tokPos; } else if (ch === 13) { ++tokPos; var next = input.charCodeAt(tokPos); if (next === 10) { ++tokPos; } if (options.locations) { ++tokCurLine; tokLineStart = tokPos; } } else if (ch === 10 || ch === 8232 || ch === 8233) { ++tokPos; if (options.locations) { ++tokCurLine; tokLineStart = tokPos; } } else if (ch > 8 && ch < 14) { ++tokPos; } else if (ch === 47) { // '/' var next = input.charCodeAt(tokPos + 1); if (next === 42) { // '*' skipBlockComment(); } else if (next === 47) { // '/' skipLineComment(2); } else break; } else if (ch === 160) { // '\xa0' ++tokPos; } else if (ch >= 5760 && nonASCIIwhitespace.test(String.fromCharCode(ch))) { ++tokPos; } else { break; } } } // ### Token reading // This is the function that is called to fetch the next token. It // is somewhat obscure, because it works in character codes rather // than characters, and because operator parsing has been inlined // into it. // // All in the name of speed. // // The `forceRegexp` parameter is used in the one case where the // `tokRegexpAllowed` trick does not work. See `parseStatement`. function readToken_dot() { var next = input.charCodeAt(tokPos + 1); if (next >= 48 && next <= 57) return readNumber(true); var next2 = input.charCodeAt(tokPos + 2); if (options.ecmaVersion >= 6 && next === 46 && next2 === 46) { // 46 = dot '.' tokPos += 3; return finishToken(_ellipsis); } else { ++tokPos; return finishToken(_dot); } } function readToken_slash() { // '/' var next = input.charCodeAt(tokPos + 1); if (tokRegexpAllowed) {++tokPos; return readRegexp();} if (next === 61) return finishOp(_assign, 2); return finishOp(_slash, 1); } function readToken_mult_modulo(code) { // '%*' var next = input.charCodeAt(tokPos + 1); if (next === 61) return finishOp(_assign, 2); return finishOp(code === 42 ? _star : _modulo, 1); } function readToken_pipe_amp(code) { // '|&' var next = input.charCodeAt(tokPos + 1); if (next === code) return finishOp(code === 124 ? _logicalOR : _logicalAND, 2); if (next === 61) return finishOp(_assign, 2); return finishOp(code === 124 ? _bitwiseOR : _bitwiseAND, 1); } function readToken_caret() { // '^' var next = input.charCodeAt(tokPos + 1); if (next === 61) return finishOp(_assign, 2); return finishOp(_bitwiseXOR, 1); } function readToken_plus_min(code) { // '+-' var next = input.charCodeAt(tokPos + 1); if (next === code) { if (next == 45 && input.charCodeAt(tokPos + 2) == 62 && newline.test(input.slice(lastEnd, tokPos))) { // A `-->` line comment skipLineComment(3); skipSpace(); return readToken(); } return finishOp(_incDec, 2); } if (next === 61) return finishOp(_assign, 2); return finishOp(_plusMin, 1); } function readToken_lt_gt(code) { // '<>' var next = input.charCodeAt(tokPos + 1); var size = 1; if (next === code) { size = code === 62 && input.charCodeAt(tokPos + 2) === 62 ? 3 : 2; if (input.charCodeAt(tokPos + size) === 61) return finishOp(_assign, size + 1); return finishOp(_bitShift, size); } if (next == 33 && code == 60 && input.charCodeAt(tokPos + 2) == 45 && input.charCodeAt(tokPos + 3) == 45) { // `