241 lines
		
	
	
		
			8.1 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			241 lines
		
	
	
		
			8.1 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| const STR = 0b000000001;
 | |
| const NUM = 0b000000010;
 | |
| const ARR = 0b000000100;
 | |
| const OBJ = 0b000001000;
 | |
| const NULL = 0b000010000;
 | |
| const BOOL = 0b000100000;
 | |
| const NAN = 0b001000000;
 | |
| const INFINITY = 0b010000000;
 | |
| const MINUS_INFINITY = 0b100000000;
 | |
| const INF = INFINITY | MINUS_INFINITY;
 | |
| const SPECIAL = NULL | BOOL | INF | NAN;
 | |
| const ATOM = STR | NUM | SPECIAL;
 | |
| const COLLECTION = ARR | OBJ;
 | |
| const ALL = ATOM | COLLECTION;
 | |
| const Allow = {
 | |
|     STR,
 | |
|     NUM,
 | |
|     ARR,
 | |
|     OBJ,
 | |
|     NULL,
 | |
|     BOOL,
 | |
|     NAN,
 | |
|     INFINITY,
 | |
|     MINUS_INFINITY,
 | |
|     INF,
 | |
|     SPECIAL,
 | |
|     ATOM,
 | |
|     COLLECTION,
 | |
|     ALL,
 | |
| };
 | |
| // The JSON string segment was unable to be parsed completely
 | |
| class PartialJSON extends Error {
 | |
| }
 | |
| class MalformedJSON extends Error {
 | |
| }
 | |
| /**
 | |
|  * Parse incomplete JSON
 | |
|  * @param {string} jsonString Partial JSON to be parsed
 | |
|  * @param {number} allowPartial Specify what types are allowed to be partial, see {@link Allow} for details
 | |
|  * @returns The parsed JSON
 | |
|  * @throws {PartialJSON} If the JSON is incomplete (related to the `allow` parameter)
 | |
|  * @throws {MalformedJSON} If the JSON is malformed
 | |
|  */
 | |
| function parseJSON(jsonString, allowPartial = Allow.ALL) {
 | |
|     if (typeof jsonString !== 'string') {
 | |
|         throw new TypeError(`expecting str, got ${typeof jsonString}`);
 | |
|     }
 | |
|     if (!jsonString.trim()) {
 | |
|         throw new Error(`${jsonString} is empty`);
 | |
|     }
 | |
|     return _parseJSON(jsonString.trim(), allowPartial);
 | |
| }
 | |
| const _parseJSON = (jsonString, allow) => {
 | |
|     const length = jsonString.length;
 | |
|     let index = 0;
 | |
|     const markPartialJSON = (msg) => {
 | |
|         throw new PartialJSON(`${msg} at position ${index}`);
 | |
|     };
 | |
|     const throwMalformedError = (msg) => {
 | |
|         throw new MalformedJSON(`${msg} at position ${index}`);
 | |
|     };
 | |
|     const parseAny = () => {
 | |
|         skipBlank();
 | |
|         if (index >= length)
 | |
|             markPartialJSON('Unexpected end of input');
 | |
|         if (jsonString[index] === '"')
 | |
|             return parseStr();
 | |
|         if (jsonString[index] === '{')
 | |
|             return parseObj();
 | |
|         if (jsonString[index] === '[')
 | |
|             return parseArr();
 | |
|         if (jsonString.substring(index, index + 4) === 'null' ||
 | |
|             (Allow.NULL & allow && length - index < 4 && 'null'.startsWith(jsonString.substring(index)))) {
 | |
|             index += 4;
 | |
|             return null;
 | |
|         }
 | |
|         if (jsonString.substring(index, index + 4) === 'true' ||
 | |
|             (Allow.BOOL & allow && length - index < 4 && 'true'.startsWith(jsonString.substring(index)))) {
 | |
|             index += 4;
 | |
|             return true;
 | |
|         }
 | |
|         if (jsonString.substring(index, index + 5) === 'false' ||
 | |
|             (Allow.BOOL & allow && length - index < 5 && 'false'.startsWith(jsonString.substring(index)))) {
 | |
|             index += 5;
 | |
|             return false;
 | |
|         }
 | |
|         if (jsonString.substring(index, index + 8) === 'Infinity' ||
 | |
|             (Allow.INFINITY & allow && length - index < 8 && 'Infinity'.startsWith(jsonString.substring(index)))) {
 | |
|             index += 8;
 | |
|             return Infinity;
 | |
|         }
 | |
|         if (jsonString.substring(index, index + 9) === '-Infinity' ||
 | |
|             (Allow.MINUS_INFINITY & allow &&
 | |
|                 1 < length - index &&
 | |
|                 length - index < 9 &&
 | |
|                 '-Infinity'.startsWith(jsonString.substring(index)))) {
 | |
|             index += 9;
 | |
|             return -Infinity;
 | |
|         }
 | |
|         if (jsonString.substring(index, index + 3) === 'NaN' ||
 | |
|             (Allow.NAN & allow && length - index < 3 && 'NaN'.startsWith(jsonString.substring(index)))) {
 | |
|             index += 3;
 | |
|             return NaN;
 | |
|         }
 | |
|         return parseNum();
 | |
|     };
 | |
|     const parseStr = () => {
 | |
|         const start = index;
 | |
|         let escape = false;
 | |
|         index++; // skip initial quote
 | |
|         while (index < length && (jsonString[index] !== '"' || (escape && jsonString[index - 1] === '\\'))) {
 | |
|             escape = jsonString[index] === '\\' ? !escape : false;
 | |
|             index++;
 | |
|         }
 | |
|         if (jsonString.charAt(index) == '"') {
 | |
|             try {
 | |
|                 return JSON.parse(jsonString.substring(start, ++index - Number(escape)));
 | |
|             }
 | |
|             catch (e) {
 | |
|                 throwMalformedError(String(e));
 | |
|             }
 | |
|         }
 | |
|         else if (Allow.STR & allow) {
 | |
|             try {
 | |
|                 return JSON.parse(jsonString.substring(start, index - Number(escape)) + '"');
 | |
|             }
 | |
|             catch (e) {
 | |
|                 // SyntaxError: Invalid escape sequence
 | |
|                 return JSON.parse(jsonString.substring(start, jsonString.lastIndexOf('\\')) + '"');
 | |
|             }
 | |
|         }
 | |
|         markPartialJSON('Unterminated string literal');
 | |
|     };
 | |
|     const parseObj = () => {
 | |
|         index++; // skip initial brace
 | |
|         skipBlank();
 | |
|         const obj = {};
 | |
|         try {
 | |
|             while (jsonString[index] !== '}') {
 | |
|                 skipBlank();
 | |
|                 if (index >= length && Allow.OBJ & allow)
 | |
|                     return obj;
 | |
|                 const key = parseStr();
 | |
|                 skipBlank();
 | |
|                 index++; // skip colon
 | |
|                 try {
 | |
|                     const value = parseAny();
 | |
|                     Object.defineProperty(obj, key, { value, writable: true, enumerable: true, configurable: true });
 | |
|                 }
 | |
|                 catch (e) {
 | |
|                     if (Allow.OBJ & allow)
 | |
|                         return obj;
 | |
|                     else
 | |
|                         throw e;
 | |
|                 }
 | |
|                 skipBlank();
 | |
|                 if (jsonString[index] === ',')
 | |
|                     index++; // skip comma
 | |
|             }
 | |
|         }
 | |
|         catch (e) {
 | |
|             if (Allow.OBJ & allow)
 | |
|                 return obj;
 | |
|             else
 | |
|                 markPartialJSON("Expected '}' at end of object");
 | |
|         }
 | |
|         index++; // skip final brace
 | |
|         return obj;
 | |
|     };
 | |
|     const parseArr = () => {
 | |
|         index++; // skip initial bracket
 | |
|         const arr = [];
 | |
|         try {
 | |
|             while (jsonString[index] !== ']') {
 | |
|                 arr.push(parseAny());
 | |
|                 skipBlank();
 | |
|                 if (jsonString[index] === ',') {
 | |
|                     index++; // skip comma
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|         catch (e) {
 | |
|             if (Allow.ARR & allow) {
 | |
|                 return arr;
 | |
|             }
 | |
|             markPartialJSON("Expected ']' at end of array");
 | |
|         }
 | |
|         index++; // skip final bracket
 | |
|         return arr;
 | |
|     };
 | |
|     const parseNum = () => {
 | |
|         if (index === 0) {
 | |
|             if (jsonString === '-' && Allow.NUM & allow)
 | |
|                 markPartialJSON("Not sure what '-' is");
 | |
|             try {
 | |
|                 return JSON.parse(jsonString);
 | |
|             }
 | |
|             catch (e) {
 | |
|                 if (Allow.NUM & allow) {
 | |
|                     try {
 | |
|                         if ('.' === jsonString[jsonString.length - 1])
 | |
|                             return JSON.parse(jsonString.substring(0, jsonString.lastIndexOf('.')));
 | |
|                         return JSON.parse(jsonString.substring(0, jsonString.lastIndexOf('e')));
 | |
|                     }
 | |
|                     catch (e) { }
 | |
|                 }
 | |
|                 throwMalformedError(String(e));
 | |
|             }
 | |
|         }
 | |
|         const start = index;
 | |
|         if (jsonString[index] === '-')
 | |
|             index++;
 | |
|         while (jsonString[index] && !',]}'.includes(jsonString[index]))
 | |
|             index++;
 | |
|         if (index == length && !(Allow.NUM & allow))
 | |
|             markPartialJSON('Unterminated number literal');
 | |
|         try {
 | |
|             return JSON.parse(jsonString.substring(start, index));
 | |
|         }
 | |
|         catch (e) {
 | |
|             if (jsonString.substring(start, index) === '-' && Allow.NUM & allow)
 | |
|                 markPartialJSON("Not sure what '-' is");
 | |
|             try {
 | |
|                 return JSON.parse(jsonString.substring(start, jsonString.lastIndexOf('e')));
 | |
|             }
 | |
|             catch (e) {
 | |
|                 throwMalformedError(String(e));
 | |
|             }
 | |
|         }
 | |
|     };
 | |
|     const skipBlank = () => {
 | |
|         while (index < length && ' \n\r\t'.includes(jsonString[index])) {
 | |
|             index++;
 | |
|         }
 | |
|     };
 | |
|     return parseAny();
 | |
| };
 | |
| // using this function with malformed JSON is undefined behavior
 | |
| const partialParse = (input) => parseJSON(input, Allow.ALL ^ Allow.NUM);
 | |
| export { partialParse, PartialJSON, MalformedJSON };
 | |
| //# sourceMappingURL=parser.mjs.map
 |