parser-input.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  1. "use strict";
  2. var __importDefault = (this && this.__importDefault) || function (mod) {
  3. return (mod && mod.__esModule) ? mod : { "default": mod };
  4. };
  5. Object.defineProperty(exports, "__esModule", { value: true });
  6. var chunker_1 = __importDefault(require("./chunker"));
  7. exports.default = (function () {
  8. var // Less input string
  9. input;
  10. var // current chunk
  11. j;
  12. var // holds state for backtracking
  13. saveStack = [];
  14. var // furthest index the parser has gone to
  15. furthest;
  16. var // if this is furthest we got to, this is the probably cause
  17. furthestPossibleErrorMessage;
  18. var // chunkified input
  19. chunks;
  20. var // current chunk
  21. current;
  22. var // index of current chunk, in `input`
  23. currentPos;
  24. var parserInput = {};
  25. var CHARCODE_SPACE = 32;
  26. var CHARCODE_TAB = 9;
  27. var CHARCODE_LF = 10;
  28. var CHARCODE_CR = 13;
  29. var CHARCODE_PLUS = 43;
  30. var CHARCODE_COMMA = 44;
  31. var CHARCODE_FORWARD_SLASH = 47;
  32. var CHARCODE_9 = 57;
  33. function skipWhitespace(length) {
  34. var oldi = parserInput.i;
  35. var oldj = j;
  36. var curr = parserInput.i - currentPos;
  37. var endIndex = parserInput.i + current.length - curr;
  38. var mem = (parserInput.i += length);
  39. var inp = input;
  40. var c;
  41. var nextChar;
  42. var comment;
  43. for (; parserInput.i < endIndex; parserInput.i++) {
  44. c = inp.charCodeAt(parserInput.i);
  45. if (parserInput.autoCommentAbsorb && c === CHARCODE_FORWARD_SLASH) {
  46. nextChar = inp.charAt(parserInput.i + 1);
  47. if (nextChar === '/') {
  48. comment = { index: parserInput.i, isLineComment: true };
  49. var nextNewLine = inp.indexOf('\n', parserInput.i + 2);
  50. if (nextNewLine < 0) {
  51. nextNewLine = endIndex;
  52. }
  53. parserInput.i = nextNewLine;
  54. comment.text = inp.substr(comment.index, parserInput.i - comment.index);
  55. parserInput.commentStore.push(comment);
  56. continue;
  57. }
  58. else if (nextChar === '*') {
  59. var nextStarSlash = inp.indexOf('*/', parserInput.i + 2);
  60. if (nextStarSlash >= 0) {
  61. comment = {
  62. index: parserInput.i,
  63. text: inp.substr(parserInput.i, nextStarSlash + 2 - parserInput.i),
  64. isLineComment: false
  65. };
  66. parserInput.i += comment.text.length - 1;
  67. parserInput.commentStore.push(comment);
  68. continue;
  69. }
  70. }
  71. break;
  72. }
  73. if ((c !== CHARCODE_SPACE) && (c !== CHARCODE_LF) && (c !== CHARCODE_TAB) && (c !== CHARCODE_CR)) {
  74. break;
  75. }
  76. }
  77. current = current.slice(length + parserInput.i - mem + curr);
  78. currentPos = parserInput.i;
  79. if (!current.length) {
  80. if (j < chunks.length - 1) {
  81. current = chunks[++j];
  82. skipWhitespace(0); // skip space at the beginning of a chunk
  83. return true; // things changed
  84. }
  85. parserInput.finished = true;
  86. }
  87. return oldi !== parserInput.i || oldj !== j;
  88. }
  89. parserInput.save = function () {
  90. currentPos = parserInput.i;
  91. saveStack.push({ current: current, i: parserInput.i, j: j });
  92. };
  93. parserInput.restore = function (possibleErrorMessage) {
  94. if (parserInput.i > furthest || (parserInput.i === furthest && possibleErrorMessage && !furthestPossibleErrorMessage)) {
  95. furthest = parserInput.i;
  96. furthestPossibleErrorMessage = possibleErrorMessage;
  97. }
  98. var state = saveStack.pop();
  99. current = state.current;
  100. currentPos = parserInput.i = state.i;
  101. j = state.j;
  102. };
  103. parserInput.forget = function () {
  104. saveStack.pop();
  105. };
  106. parserInput.isWhitespace = function (offset) {
  107. var pos = parserInput.i + (offset || 0);
  108. var code = input.charCodeAt(pos);
  109. return (code === CHARCODE_SPACE || code === CHARCODE_CR || code === CHARCODE_TAB || code === CHARCODE_LF);
  110. };
  111. // Specialization of $(tok)
  112. parserInput.$re = function (tok) {
  113. if (parserInput.i > currentPos) {
  114. current = current.slice(parserInput.i - currentPos);
  115. currentPos = parserInput.i;
  116. }
  117. var m = tok.exec(current);
  118. if (!m) {
  119. return null;
  120. }
  121. skipWhitespace(m[0].length);
  122. if (typeof m === 'string') {
  123. return m;
  124. }
  125. return m.length === 1 ? m[0] : m;
  126. };
  127. parserInput.$char = function (tok) {
  128. if (input.charAt(parserInput.i) !== tok) {
  129. return null;
  130. }
  131. skipWhitespace(1);
  132. return tok;
  133. };
  134. parserInput.$str = function (tok) {
  135. var tokLength = tok.length;
  136. // https://jsperf.com/string-startswith/21
  137. for (var i = 0; i < tokLength; i++) {
  138. if (input.charAt(parserInput.i + i) !== tok.charAt(i)) {
  139. return null;
  140. }
  141. }
  142. skipWhitespace(tokLength);
  143. return tok;
  144. };
  145. parserInput.$quoted = function (loc) {
  146. var pos = loc || parserInput.i;
  147. var startChar = input.charAt(pos);
  148. if (startChar !== '\'' && startChar !== '"') {
  149. return;
  150. }
  151. var length = input.length;
  152. var currentPosition = pos;
  153. for (var i = 1; i + currentPosition < length; i++) {
  154. var nextChar = input.charAt(i + currentPosition);
  155. switch (nextChar) {
  156. case '\\':
  157. i++;
  158. continue;
  159. case '\r':
  160. case '\n':
  161. break;
  162. case startChar:
  163. var str = input.substr(currentPosition, i + 1);
  164. if (!loc && loc !== 0) {
  165. skipWhitespace(i + 1);
  166. return str;
  167. }
  168. return [startChar, str];
  169. default:
  170. }
  171. }
  172. return null;
  173. };
  174. /**
  175. * Permissive parsing. Ignores everything except matching {} [] () and quotes
  176. * until matching token (outside of blocks)
  177. */
  178. parserInput.$parseUntil = function (tok) {
  179. var quote = '';
  180. var returnVal = null;
  181. var inComment = false;
  182. var blockDepth = 0;
  183. var blockStack = [];
  184. var parseGroups = [];
  185. var length = input.length;
  186. var startPos = parserInput.i;
  187. var lastPos = parserInput.i;
  188. var i = parserInput.i;
  189. var loop = true;
  190. var testChar;
  191. if (typeof tok === 'string') {
  192. testChar = function (char) { return char === tok; };
  193. }
  194. else {
  195. testChar = function (char) { return tok.test(char); };
  196. }
  197. do {
  198. var prevChar = void 0;
  199. var nextChar = input.charAt(i);
  200. if (blockDepth === 0 && testChar(nextChar)) {
  201. returnVal = input.substr(lastPos, i - lastPos);
  202. if (returnVal) {
  203. parseGroups.push(returnVal);
  204. }
  205. else {
  206. parseGroups.push(' ');
  207. }
  208. returnVal = parseGroups;
  209. skipWhitespace(i - startPos);
  210. loop = false;
  211. }
  212. else {
  213. if (inComment) {
  214. if (nextChar === '*' &&
  215. input.charAt(i + 1) === '/') {
  216. i++;
  217. blockDepth--;
  218. inComment = false;
  219. }
  220. i++;
  221. continue;
  222. }
  223. switch (nextChar) {
  224. case '\\':
  225. i++;
  226. nextChar = input.charAt(i);
  227. parseGroups.push(input.substr(lastPos, i - lastPos + 1));
  228. lastPos = i + 1;
  229. break;
  230. case '/':
  231. if (input.charAt(i + 1) === '*') {
  232. i++;
  233. inComment = true;
  234. blockDepth++;
  235. }
  236. break;
  237. case '\'':
  238. case '"':
  239. quote = parserInput.$quoted(i);
  240. if (quote) {
  241. parseGroups.push(input.substr(lastPos, i - lastPos), quote);
  242. i += quote[1].length - 1;
  243. lastPos = i + 1;
  244. }
  245. else {
  246. skipWhitespace(i - startPos);
  247. returnVal = nextChar;
  248. loop = false;
  249. }
  250. break;
  251. case '{':
  252. blockStack.push('}');
  253. blockDepth++;
  254. break;
  255. case '(':
  256. blockStack.push(')');
  257. blockDepth++;
  258. break;
  259. case '[':
  260. blockStack.push(']');
  261. blockDepth++;
  262. break;
  263. case '}':
  264. case ')':
  265. case ']':
  266. var expected = blockStack.pop();
  267. if (nextChar === expected) {
  268. blockDepth--;
  269. }
  270. else {
  271. // move the parser to the error and return expected
  272. skipWhitespace(i - startPos);
  273. returnVal = expected;
  274. loop = false;
  275. }
  276. }
  277. i++;
  278. if (i > length) {
  279. loop = false;
  280. }
  281. }
  282. prevChar = nextChar;
  283. } while (loop);
  284. return returnVal ? returnVal : null;
  285. };
  286. parserInput.autoCommentAbsorb = true;
  287. parserInput.commentStore = [];
  288. parserInput.finished = false;
  289. // Same as $(), but don't change the state of the parser,
  290. // just return the match.
  291. parserInput.peek = function (tok) {
  292. if (typeof tok === 'string') {
  293. // https://jsperf.com/string-startswith/21
  294. for (var i = 0; i < tok.length; i++) {
  295. if (input.charAt(parserInput.i + i) !== tok.charAt(i)) {
  296. return false;
  297. }
  298. }
  299. return true;
  300. }
  301. else {
  302. return tok.test(current);
  303. }
  304. };
  305. // Specialization of peek()
  306. // TODO remove or change some currentChar calls to peekChar
  307. parserInput.peekChar = function (tok) { return input.charAt(parserInput.i) === tok; };
  308. parserInput.currentChar = function () { return input.charAt(parserInput.i); };
  309. parserInput.prevChar = function () { return input.charAt(parserInput.i - 1); };
  310. parserInput.getInput = function () { return input; };
  311. parserInput.peekNotNumeric = function () {
  312. var c = input.charCodeAt(parserInput.i);
  313. // Is the first char of the dimension 0-9, '.', '+' or '-'
  314. return (c > CHARCODE_9 || c < CHARCODE_PLUS) || c === CHARCODE_FORWARD_SLASH || c === CHARCODE_COMMA;
  315. };
  316. parserInput.start = function (str, chunkInput, failFunction) {
  317. input = str;
  318. parserInput.i = j = currentPos = furthest = 0;
  319. // chunking apparently makes things quicker (but my tests indicate
  320. // it might actually make things slower in node at least)
  321. // and it is a non-perfect parse - it can't recognise
  322. // unquoted urls, meaning it can't distinguish comments
  323. // meaning comments with quotes or {}() in them get 'counted'
  324. // and then lead to parse errors.
  325. // In addition if the chunking chunks in the wrong place we might
  326. // not be able to parse a parser statement in one go
  327. // this is officially deprecated but can be switched on via an option
  328. // in the case it causes too much performance issues.
  329. if (chunkInput) {
  330. chunks = chunker_1.default(str, failFunction);
  331. }
  332. else {
  333. chunks = [str];
  334. }
  335. current = chunks[0];
  336. skipWhitespace(0);
  337. };
  338. parserInput.end = function () {
  339. var message;
  340. var isFinished = parserInput.i >= input.length;
  341. if (parserInput.i < furthest) {
  342. message = furthestPossibleErrorMessage;
  343. parserInput.i = furthest;
  344. }
  345. return {
  346. isFinished: isFinished,
  347. furthest: parserInput.i,
  348. furthestPossibleErrorMessage: message,
  349. furthestReachedEnd: parserInput.i >= input.length - 1,
  350. furthestChar: input[parserInput.i]
  351. };
  352. };
  353. return parserInput;
  354. });
  355. //# sourceMappingURL=parser-input.js.map