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.singleQuote || state.doubleQuote || 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 || 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 === '/') { history = history.substr(1); state.lineComment = true; } else if (lastChar === '/' && character === '*') { history = history.substr(1); state.blockComment = true; } else if (character === '/') { //could be start of regexp or divide sign var history = state.history.replace(/^\s*/, ''); if (history[0] === ')') { //unless its an `if`, `while`, `for` or `with` it's a divide //this is probably best left though } else if (history[0] === '}') { //unless it's a function expression, it's a regexp //this is probably best left though } else if (isPunctuator(history[0])) { state.regexp = true; } else if (/^\w+\b/.test(history) && isKeyword(/^\w+\b/.exec(history)[0])) { state.regexp = true; } else { // assume it's divide } } 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 = defaultState; function defaultState() { return { lineComment: false, blockComment: false, singleQuote: false, doubleQuote: false, regexp: false, escaped: false, roundDepth: 0, curlyDepth: 0, squareDepth: 0, history: '' }; } function startsWith(str, start, i) { return str.substr(i || 0, start.length) === start; } 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; } } 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'); }