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(); var wasComment = state.blockComment || state.lineComment; var lastChar = state.history ? state.history[0] : ''; 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; } 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; 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 = '' } 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) { 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; }