'use strict'; /** * Stringify/parse functions that don't operate * recursively, so they avoid call stack exceeded * errors. */ exports.stringify = function stringify(input) { var queue = []; queue.push({obj: input}); var res = ''; var next, obj, prefix, val, i, arrayPrefix, keys, k, key, value, objPrefix; while ((next = queue.pop())) { obj = next.obj; prefix = next.prefix || ''; val = next.val || ''; res += prefix; if (val) { res += val; } else if (typeof obj !== 'object') { res += typeof obj === 'undefined' ? null : JSON.stringify(obj); } else if (obj === null) { res += 'null'; } else if (Array.isArray(obj)) { queue.push({val: ']'}); for (i = obj.length - 1; i >= 0; i--) { arrayPrefix = i === 0 ? '' : ','; queue.push({obj: obj[i], prefix: arrayPrefix}); } queue.push({val: '['}); } else { // object keys = []; for (k in obj) { if (obj.hasOwnProperty(k)) { keys.push(k); } } queue.push({val: '}'}); for (i = keys.length - 1; i >= 0; i--) { key = keys[i]; value = obj[key]; objPrefix = (i > 0 ? ',' : ''); objPrefix += JSON.stringify(key) + ':'; queue.push({obj: value, prefix: objPrefix}); } queue.push({val: '{'}); } } return res; }; // Convenience function for the parse function. // This pop function is basically copied from // pouchCollate.parseIndexableString function pop(obj, stack, metaStack) { var lastMetaElement = metaStack[metaStack.length - 1]; if (obj === lastMetaElement.element) { // popping a meta-element, e.g. an object whose value is another object metaStack.pop(); lastMetaElement = metaStack[metaStack.length - 1]; } var element = lastMetaElement.element; var lastElementIndex = lastMetaElement.index; if (Array.isArray(element)) { element.push(obj); } else if (lastElementIndex === stack.length - 2) { // obj with key+value var key = stack.pop(); element[key] = obj; } else { stack.push(obj); // obj with key only } } exports.parse = function (str) { var stack = []; var metaStack = []; // stack for arrays and objects var i = 0; var collationIndex,parsedNum,numChar; var parsedString,lastCh,numConsecutiveSlashes,ch; var arrayElement, objElement; while (true) { collationIndex = str[i++]; if (collationIndex === '}' || collationIndex === ']' || typeof collationIndex === 'undefined') { if (stack.length === 1) { return stack.pop(); } else { pop(stack.pop(), stack, metaStack); continue; } } switch (collationIndex) { case ' ': case '\t': case '\n': case ':': case ',': break; case 'n': i += 3; // 'ull' pop(null, stack, metaStack); break; case 't': i += 3; // 'rue' pop(true, stack, metaStack); break; case 'f': i += 4; // 'alse' pop(false, stack, metaStack); break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case '-': parsedNum = ''; i--; while (true) { numChar = str[i++]; if (/[\d\.\-e\+]/.test(numChar)) { parsedNum += numChar; } else { i--; break; } } pop(parseFloat(parsedNum), stack, metaStack); break; case '"': parsedString = ''; lastCh = void 0; numConsecutiveSlashes = 0; while (true) { ch = str[i++]; if (ch !== '"' || (lastCh === '\\' && numConsecutiveSlashes % 2 === 1)) { parsedString += ch; lastCh = ch; if (lastCh === '\\') { numConsecutiveSlashes++; } else { numConsecutiveSlashes = 0; } } else { break; } } pop(JSON.parse('"' + parsedString + '"'), stack, metaStack); break; case '[': arrayElement = { element: [], index: stack.length }; stack.push(arrayElement.element); metaStack.push(arrayElement); break; case '{': objElement = { element: {}, index: stack.length }; stack.push(objElement.element); metaStack.push(objElement); break; default: throw new Error( 'unexpectedly reached end of input: ' + collationIndex); } } };