parser.mjs 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. const STR = 0b000000001;
  2. const NUM = 0b000000010;
  3. const ARR = 0b000000100;
  4. const OBJ = 0b000001000;
  5. const NULL = 0b000010000;
  6. const BOOL = 0b000100000;
  7. const NAN = 0b001000000;
  8. const INFINITY = 0b010000000;
  9. const MINUS_INFINITY = 0b100000000;
  10. const INF = INFINITY | MINUS_INFINITY;
  11. const SPECIAL = NULL | BOOL | INF | NAN;
  12. const ATOM = STR | NUM | SPECIAL;
  13. const COLLECTION = ARR | OBJ;
  14. const ALL = ATOM | COLLECTION;
  15. const Allow = {
  16. STR,
  17. NUM,
  18. ARR,
  19. OBJ,
  20. NULL,
  21. BOOL,
  22. NAN,
  23. INFINITY,
  24. MINUS_INFINITY,
  25. INF,
  26. SPECIAL,
  27. ATOM,
  28. COLLECTION,
  29. ALL,
  30. };
  31. // The JSON string segment was unable to be parsed completely
  32. class PartialJSON extends Error {
  33. }
  34. class MalformedJSON extends Error {
  35. }
  36. /**
  37. * Parse incomplete JSON
  38. * @param {string} jsonString Partial JSON to be parsed
  39. * @param {number} allowPartial Specify what types are allowed to be partial, see {@link Allow} for details
  40. * @returns The parsed JSON
  41. * @throws {PartialJSON} If the JSON is incomplete (related to the `allow` parameter)
  42. * @throws {MalformedJSON} If the JSON is malformed
  43. */
  44. function parseJSON(jsonString, allowPartial = Allow.ALL) {
  45. if (typeof jsonString !== 'string') {
  46. throw new TypeError(`expecting str, got ${typeof jsonString}`);
  47. }
  48. if (!jsonString.trim()) {
  49. throw new Error(`${jsonString} is empty`);
  50. }
  51. return _parseJSON(jsonString.trim(), allowPartial);
  52. }
  53. const _parseJSON = (jsonString, allow) => {
  54. const length = jsonString.length;
  55. let index = 0;
  56. const markPartialJSON = (msg) => {
  57. throw new PartialJSON(`${msg} at position ${index}`);
  58. };
  59. const throwMalformedError = (msg) => {
  60. throw new MalformedJSON(`${msg} at position ${index}`);
  61. };
  62. const parseAny = () => {
  63. skipBlank();
  64. if (index >= length)
  65. markPartialJSON('Unexpected end of input');
  66. if (jsonString[index] === '"')
  67. return parseStr();
  68. if (jsonString[index] === '{')
  69. return parseObj();
  70. if (jsonString[index] === '[')
  71. return parseArr();
  72. if (jsonString.substring(index, index + 4) === 'null' ||
  73. (Allow.NULL & allow && length - index < 4 && 'null'.startsWith(jsonString.substring(index)))) {
  74. index += 4;
  75. return null;
  76. }
  77. if (jsonString.substring(index, index + 4) === 'true' ||
  78. (Allow.BOOL & allow && length - index < 4 && 'true'.startsWith(jsonString.substring(index)))) {
  79. index += 4;
  80. return true;
  81. }
  82. if (jsonString.substring(index, index + 5) === 'false' ||
  83. (Allow.BOOL & allow && length - index < 5 && 'false'.startsWith(jsonString.substring(index)))) {
  84. index += 5;
  85. return false;
  86. }
  87. if (jsonString.substring(index, index + 8) === 'Infinity' ||
  88. (Allow.INFINITY & allow && length - index < 8 && 'Infinity'.startsWith(jsonString.substring(index)))) {
  89. index += 8;
  90. return Infinity;
  91. }
  92. if (jsonString.substring(index, index + 9) === '-Infinity' ||
  93. (Allow.MINUS_INFINITY & allow &&
  94. 1 < length - index &&
  95. length - index < 9 &&
  96. '-Infinity'.startsWith(jsonString.substring(index)))) {
  97. index += 9;
  98. return -Infinity;
  99. }
  100. if (jsonString.substring(index, index + 3) === 'NaN' ||
  101. (Allow.NAN & allow && length - index < 3 && 'NaN'.startsWith(jsonString.substring(index)))) {
  102. index += 3;
  103. return NaN;
  104. }
  105. return parseNum();
  106. };
  107. const parseStr = () => {
  108. const start = index;
  109. let escape = false;
  110. index++; // skip initial quote
  111. while (index < length && (jsonString[index] !== '"' || (escape && jsonString[index - 1] === '\\'))) {
  112. escape = jsonString[index] === '\\' ? !escape : false;
  113. index++;
  114. }
  115. if (jsonString.charAt(index) == '"') {
  116. try {
  117. return JSON.parse(jsonString.substring(start, ++index - Number(escape)));
  118. }
  119. catch (e) {
  120. throwMalformedError(String(e));
  121. }
  122. }
  123. else if (Allow.STR & allow) {
  124. try {
  125. return JSON.parse(jsonString.substring(start, index - Number(escape)) + '"');
  126. }
  127. catch (e) {
  128. // SyntaxError: Invalid escape sequence
  129. return JSON.parse(jsonString.substring(start, jsonString.lastIndexOf('\\')) + '"');
  130. }
  131. }
  132. markPartialJSON('Unterminated string literal');
  133. };
  134. const parseObj = () => {
  135. index++; // skip initial brace
  136. skipBlank();
  137. const obj = {};
  138. try {
  139. while (jsonString[index] !== '}') {
  140. skipBlank();
  141. if (index >= length && Allow.OBJ & allow)
  142. return obj;
  143. const key = parseStr();
  144. skipBlank();
  145. index++; // skip colon
  146. try {
  147. const value = parseAny();
  148. Object.defineProperty(obj, key, { value, writable: true, enumerable: true, configurable: true });
  149. }
  150. catch (e) {
  151. if (Allow.OBJ & allow)
  152. return obj;
  153. else
  154. throw e;
  155. }
  156. skipBlank();
  157. if (jsonString[index] === ',')
  158. index++; // skip comma
  159. }
  160. }
  161. catch (e) {
  162. if (Allow.OBJ & allow)
  163. return obj;
  164. else
  165. markPartialJSON("Expected '}' at end of object");
  166. }
  167. index++; // skip final brace
  168. return obj;
  169. };
  170. const parseArr = () => {
  171. index++; // skip initial bracket
  172. const arr = [];
  173. try {
  174. while (jsonString[index] !== ']') {
  175. arr.push(parseAny());
  176. skipBlank();
  177. if (jsonString[index] === ',') {
  178. index++; // skip comma
  179. }
  180. }
  181. }
  182. catch (e) {
  183. if (Allow.ARR & allow) {
  184. return arr;
  185. }
  186. markPartialJSON("Expected ']' at end of array");
  187. }
  188. index++; // skip final bracket
  189. return arr;
  190. };
  191. const parseNum = () => {
  192. if (index === 0) {
  193. if (jsonString === '-' && Allow.NUM & allow)
  194. markPartialJSON("Not sure what '-' is");
  195. try {
  196. return JSON.parse(jsonString);
  197. }
  198. catch (e) {
  199. if (Allow.NUM & allow) {
  200. try {
  201. if ('.' === jsonString[jsonString.length - 1])
  202. return JSON.parse(jsonString.substring(0, jsonString.lastIndexOf('.')));
  203. return JSON.parse(jsonString.substring(0, jsonString.lastIndexOf('e')));
  204. }
  205. catch (e) { }
  206. }
  207. throwMalformedError(String(e));
  208. }
  209. }
  210. const start = index;
  211. if (jsonString[index] === '-')
  212. index++;
  213. while (jsonString[index] && !',]}'.includes(jsonString[index]))
  214. index++;
  215. if (index == length && !(Allow.NUM & allow))
  216. markPartialJSON('Unterminated number literal');
  217. try {
  218. return JSON.parse(jsonString.substring(start, index));
  219. }
  220. catch (e) {
  221. if (jsonString.substring(start, index) === '-' && Allow.NUM & allow)
  222. markPartialJSON("Not sure what '-' is");
  223. try {
  224. return JSON.parse(jsonString.substring(start, jsonString.lastIndexOf('e')));
  225. }
  226. catch (e) {
  227. throwMalformedError(String(e));
  228. }
  229. }
  230. };
  231. const skipBlank = () => {
  232. while (index < length && ' \n\r\t'.includes(jsonString[index])) {
  233. index++;
  234. }
  235. };
  236. return parseAny();
  237. };
  238. // using this function with malformed JSON is undefined behavior
  239. const partialParse = (input) => parseJSON(input, Allow.ALL ^ Allow.NUM);
  240. export { partialParse, PartialJSON, MalformedJSON };
  241. //# sourceMappingURL=parser.mjs.map