You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
232 lines
7.5 KiB
JavaScript
232 lines
7.5 KiB
JavaScript
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;
|
|
}
|