A Twitch.tv viewer reward and games system.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

217 lines
6.9 KiB

  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. var wasComment = state.blockComment || state.lineComment;
  59. var lastChar = state.history ? state.history[0] : '';
  60. if (state.lineComment) {
  61. if (character === '\n') {
  62. state.lineComment = false;
  63. }
  64. } else if (state.blockComment) {
  65. if (state.lastChar === '*' && character === '/') {
  66. state.blockComment = false;
  67. }
  68. } else if (state.singleQuote) {
  69. if (character === '\'' && !state.escaped) {
  70. state.singleQuote = false;
  71. } else if (character === '\\' && !state.escaped) {
  72. state.escaped = true;
  73. } else {
  74. state.escaped = false;
  75. }
  76. } else if (state.doubleQuote) {
  77. if (character === '"' && !state.escaped) {
  78. state.doubleQuote = false;
  79. } else if (character === '\\' && !state.escaped) {
  80. state.escaped = true;
  81. } else {
  82. state.escaped = false;
  83. }
  84. } else if (state.regexp) {
  85. if (character === '/' && !state.escaped) {
  86. state.regexp = false;
  87. } else if (character === '\\' && !state.escaped) {
  88. state.escaped = true;
  89. } else {
  90. state.escaped = false;
  91. }
  92. } else if (lastChar === '/' && character === '/') {
  93. state.history = state.history.substr(1);
  94. state.lineComment = true;
  95. } else if (lastChar === '/' && character === '*') {
  96. state.history = state.history.substr(1);
  97. state.blockComment = true;
  98. } else if (character === '/' && isRegexp(state.history)) {
  99. state.regexp = true;
  100. } else if (character === '\'') {
  101. state.singleQuote = true;
  102. } else if (character === '"') {
  103. state.doubleQuote = true;
  104. } else if (character === '(') {
  105. state.roundDepth++;
  106. } else if (character === ')') {
  107. state.roundDepth--;
  108. } else if (character === '{') {
  109. state.curlyDepth++;
  110. } else if (character === '}') {
  111. state.curlyDepth--;
  112. } else if (character === '[') {
  113. state.squareDepth++;
  114. } else if (character === ']') {
  115. state.squareDepth--;
  116. }
  117. if (!state.blockComment && !state.lineComment && !wasComment) state.history = character + state.history;
  118. return state;
  119. }
  120. exports.defaultState = function () { return new State() };
  121. function State() {
  122. this.lineComment = false;
  123. this.blockComment = false;
  124. this.singleQuote = false;
  125. this.doubleQuote = false;
  126. this.regexp = false;
  127. this.escaped = false;
  128. this.roundDepth = 0;
  129. this.curlyDepth = 0;
  130. this.squareDepth = 0;
  131. this.history = ''
  132. }
  133. State.prototype.isString = function () {
  134. return this.singleQuote || this.doubleQuote;
  135. }
  136. State.prototype.isComment = function () {
  137. return this.lineComment || this.blockComment;
  138. }
  139. State.prototype.isNesting = function () {
  140. return this.isString() || this.isComment() || this.regexp || this.roundDepth > 0 || this.curlyDepth > 0 || this.squareDepth > 0
  141. }
  142. function startsWith(str, start, i) {
  143. return str.substr(i || 0, start.length) === start;
  144. }
  145. exports.isPunctuator = isPunctuator
  146. function isPunctuator(c) {
  147. var code = c.charCodeAt(0)
  148. switch (code) {
  149. case 46: // . dot
  150. case 40: // ( open bracket
  151. case 41: // ) close bracket
  152. case 59: // ; semicolon
  153. case 44: // , comma
  154. case 123: // { open curly brace
  155. case 125: // } close curly brace
  156. case 91: // [
  157. case 93: // ]
  158. case 58: // :
  159. case 63: // ?
  160. case 126: // ~
  161. case 37: // %
  162. case 38: // &
  163. case 42: // *:
  164. case 43: // +
  165. case 45: // -
  166. case 47: // /
  167. case 60: // <
  168. case 62: // >
  169. case 94: // ^
  170. case 124: // |
  171. case 33: // !
  172. case 61: // =
  173. return true;
  174. default:
  175. return false;
  176. }
  177. }
  178. exports.isKeyword = isKeyword
  179. function isKeyword(id) {
  180. return (id === 'if') || (id === 'in') || (id === 'do') || (id === 'var') || (id === 'for') || (id === 'new') ||
  181. (id === 'try') || (id === 'let') || (id === 'this') || (id === 'else') || (id === 'case') ||
  182. (id === 'void') || (id === 'with') || (id === 'enum') || (id === 'while') || (id === 'break') || (id === 'catch') ||
  183. (id === 'throw') || (id === 'const') || (id === 'yield') || (id === 'class') || (id === 'super') ||
  184. (id === 'return') || (id === 'typeof') || (id === 'delete') || (id === 'switch') || (id === 'export') ||
  185. (id === 'import') || (id === 'default') || (id === 'finally') || (id === 'extends') || (id === 'function') ||
  186. (id === 'continue') || (id === 'debugger') || (id === 'package') || (id === 'private') || (id === 'interface') ||
  187. (id === 'instanceof') || (id === 'implements') || (id === 'protected') || (id === 'public') || (id === 'static') ||
  188. (id === 'yield') || (id === 'let');
  189. }
  190. function isRegexp(history) {
  191. //could be start of regexp or divide sign
  192. history = history.replace(/^\s*/, '');
  193. //unless its an `if`, `while`, `for` or `with` it's a divide, so we assume it's a divide
  194. if (history[0] === ')') return false;
  195. //unless it's a function expression, it's a regexp, so we assume it's a regexp
  196. if (history[0] === '}') return true;
  197. //any punctuation means it's a regexp
  198. if (isPunctuator(history[0])) return true;
  199. //if the last thing was a keyword then it must be a regexp (e.g. `typeof /foo/`)
  200. if (/^\w+\b/.test(history) && isKeyword(/^\w+\b/.exec(history)[0].split('').reverse().join(''))) return true;
  201. return false;
  202. }