index.js 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. exports = (module.exports = parse);
  2. exports.parse = parse;
  3. function parse(src, state, options) {
  4. options = options || {};
  5. state = state || exports.defaultState();
  6. var start = options.start || 0;
  7. var end = options.end || src.length;
  8. var index = start;
  9. while (index < end) {
  10. if (state.roundDepth < 0 || state.curlyDepth < 0 || state.squareDepth < 0) {
  11. throw new SyntaxError('Mismatched Bracket: ' + src[index - 1]);
  12. }
  13. exports.parseChar(src[index++], state);
  14. }
  15. return state;
  16. }
  17. exports.parseMax = parseMax;
  18. function parseMax(src, options) {
  19. options = options || {};
  20. var start = options.start || 0;
  21. var index = start;
  22. var state = exports.defaultState();
  23. while (state.roundDepth >= 0 && state.curlyDepth >= 0 && state.squareDepth >= 0) {
  24. if (index >= src.length) {
  25. throw new Error('The end of the string was reached with no closing bracket found.');
  26. }
  27. exports.parseChar(src[index++], state);
  28. }
  29. var end = index - 1;
  30. return {
  31. start: start,
  32. end: end,
  33. src: src.substring(start, end)
  34. };
  35. }
  36. exports.parseUntil = parseUntil;
  37. function parseUntil(src, delimiter, options) {
  38. options = options || {};
  39. var includeLineComment = options.includeLineComment || false;
  40. var start = options.start || 0;
  41. var index = start;
  42. var state = exports.defaultState();
  43. while (state.isString() || state.regexp || state.blockComment ||
  44. (!includeLineComment && state.lineComment) || !startsWith(src, delimiter, index)) {
  45. exports.parseChar(src[index++], state);
  46. }
  47. var end = index;
  48. return {
  49. start: start,
  50. end: end,
  51. src: src.substring(start, end)
  52. };
  53. }
  54. exports.parseChar = parseChar;
  55. function parseChar(character, state) {
  56. if (character.length !== 1) throw new Error('Character must be a string of length 1');
  57. state = state || exports.defaultState();
  58. state.src = state.src || '';
  59. state.src += character;
  60. var wasComment = state.blockComment || state.lineComment;
  61. var lastChar = state.history ? state.history[0] : '';
  62. if (state.regexpStart) {
  63. if (character === '/' || character == '*') {
  64. state.regexp = false;
  65. }
  66. state.regexpStart = false;
  67. }
  68. if (state.lineComment) {
  69. if (character === '\n') {
  70. state.lineComment = false;
  71. }
  72. } else if (state.blockComment) {
  73. if (state.lastChar === '*' && character === '/') {
  74. state.blockComment = false;
  75. }
  76. } else if (state.singleQuote) {
  77. if (character === '\'' && !state.escaped) {
  78. state.singleQuote = false;
  79. } else if (character === '\\' && !state.escaped) {
  80. state.escaped = true;
  81. } else {
  82. state.escaped = false;
  83. }
  84. } else if (state.doubleQuote) {
  85. if (character === '"' && !state.escaped) {
  86. state.doubleQuote = false;
  87. } else if (character === '\\' && !state.escaped) {
  88. state.escaped = true;
  89. } else {
  90. state.escaped = false;
  91. }
  92. } else if (state.regexp) {
  93. if (character === '/' && !state.escaped) {
  94. state.regexp = false;
  95. } else if (character === '\\' && !state.escaped) {
  96. state.escaped = true;
  97. } else {
  98. state.escaped = false;
  99. }
  100. } else if (lastChar === '/' && character === '/') {
  101. state.history = state.history.substr(1);
  102. state.lineComment = true;
  103. } else if (lastChar === '/' && character === '*') {
  104. state.history = state.history.substr(1);
  105. state.blockComment = true;
  106. } else if (character === '/' && isRegexp(state.history)) {
  107. state.regexp = true;
  108. state.regexpStart = true;
  109. } else if (character === '\'') {
  110. state.singleQuote = true;
  111. } else if (character === '"') {
  112. state.doubleQuote = true;
  113. } else if (character === '(') {
  114. state.roundDepth++;
  115. } else if (character === ')') {
  116. state.roundDepth--;
  117. } else if (character === '{') {
  118. state.curlyDepth++;
  119. } else if (character === '}') {
  120. state.curlyDepth--;
  121. } else if (character === '[') {
  122. state.squareDepth++;
  123. } else if (character === ']') {
  124. state.squareDepth--;
  125. }
  126. if (!state.blockComment && !state.lineComment && !wasComment) state.history = character + state.history;
  127. state.lastChar = character; // store last character for ending block comments
  128. return state;
  129. }
  130. exports.defaultState = function () { return new State() };
  131. function State() {
  132. this.lineComment = false;
  133. this.blockComment = false;
  134. this.singleQuote = false;
  135. this.doubleQuote = false;
  136. this.regexp = false;
  137. this.escaped = false;
  138. this.roundDepth = 0;
  139. this.curlyDepth = 0;
  140. this.squareDepth = 0;
  141. this.history = ''
  142. this.lastChar = ''
  143. }
  144. State.prototype.isString = function () {
  145. return this.singleQuote || this.doubleQuote;
  146. }
  147. State.prototype.isComment = function () {
  148. return this.lineComment || this.blockComment;
  149. }
  150. State.prototype.isNesting = function () {
  151. return this.isString() || this.isComment() || this.regexp || this.roundDepth > 0 || this.curlyDepth > 0 || this.squareDepth > 0
  152. }
  153. function startsWith(str, start, i) {
  154. return str.substr(i || 0, start.length) === start;
  155. }
  156. exports.isPunctuator = isPunctuator
  157. function isPunctuator(c) {
  158. if (!c) return true; // the start of a string is a punctuator
  159. var code = c.charCodeAt(0)
  160. switch (code) {
  161. case 46: // . dot
  162. case 40: // ( open bracket
  163. case 41: // ) close bracket
  164. case 59: // ; semicolon
  165. case 44: // , comma
  166. case 123: // { open curly brace
  167. case 125: // } close curly brace
  168. case 91: // [
  169. case 93: // ]
  170. case 58: // :
  171. case 63: // ?
  172. case 126: // ~
  173. case 37: // %
  174. case 38: // &
  175. case 42: // *:
  176. case 43: // +
  177. case 45: // -
  178. case 47: // /
  179. case 60: // <
  180. case 62: // >
  181. case 94: // ^
  182. case 124: // |
  183. case 33: // !
  184. case 61: // =
  185. return true;
  186. default:
  187. return false;
  188. }
  189. }
  190. exports.isKeyword = isKeyword
  191. function isKeyword(id) {
  192. return (id === 'if') || (id === 'in') || (id === 'do') || (id === 'var') || (id === 'for') || (id === 'new') ||
  193. (id === 'try') || (id === 'let') || (id === 'this') || (id === 'else') || (id === 'case') ||
  194. (id === 'void') || (id === 'with') || (id === 'enum') || (id === 'while') || (id === 'break') || (id === 'catch') ||
  195. (id === 'throw') || (id === 'const') || (id === 'yield') || (id === 'class') || (id === 'super') ||
  196. (id === 'return') || (id === 'typeof') || (id === 'delete') || (id === 'switch') || (id === 'export') ||
  197. (id === 'import') || (id === 'default') || (id === 'finally') || (id === 'extends') || (id === 'function') ||
  198. (id === 'continue') || (id === 'debugger') || (id === 'package') || (id === 'private') || (id === 'interface') ||
  199. (id === 'instanceof') || (id === 'implements') || (id === 'protected') || (id === 'public') || (id === 'static') ||
  200. (id === 'yield') || (id === 'let');
  201. }
  202. function isRegexp(history) {
  203. //could be start of regexp or divide sign
  204. history = history.replace(/^\s*/, '');
  205. //unless its an `if`, `while`, `for` or `with` it's a divide, so we assume it's a divide
  206. if (history[0] === ')') return false;
  207. //unless it's a function expression, it's a regexp, so we assume it's a regexp
  208. if (history[0] === '}') return true;
  209. //any punctuation means it's a regexp
  210. if (isPunctuator(history[0])) return true;
  211. //if the last thing was a keyword then it must be a regexp (e.g. `typeof /foo/`)
  212. if (/^\w+\b/.test(history) && isKeyword(/^\w+\b/.exec(history)[0].split('').reverse().join(''))) return true;
  213. return false;
  214. }