| 'use strict' | 
|   | 
| const check = require('check-types') | 
| const error = require('./error') | 
| const EventEmitter = require('events').EventEmitter | 
| const events = require('./events') | 
| const promise = require('./promise') | 
|   | 
| const terminators = { | 
|   obj: '}', | 
|   arr: ']' | 
| } | 
|   | 
| const escapes = { | 
|   /* eslint-disable quote-props */ | 
|   '"': '"', | 
|   '\\': '\\', | 
|   '/': '/', | 
|   'b': '\b', | 
|   'f': '\f', | 
|   'n': '\n', | 
|   'r': '\r', | 
|   't': '\t' | 
|   /* eslint-enable quote-props */ | 
| } | 
|   | 
| module.exports = initialise | 
|   | 
| /** | 
|  * Public function `walk`. | 
|  * | 
|  * Returns an event emitter and asynchronously walks a stream of JSON data, | 
|  * emitting events as it encounters tokens. The event emitter is decorated | 
|  * with a `pause` method that can be called to pause processing. | 
|  * | 
|  * @param stream:     Readable instance representing the incoming JSON. | 
|  * | 
|  * @option yieldRate: The number of data items to process per timeslice, | 
|  *                    default is 16384. | 
|  * | 
|  * @option Promise:   The promise constructor to use, defaults to bluebird. | 
|  * | 
|  * @option ndjson:    Set this to true to parse newline-delimited JSON. | 
|  **/ | 
| function initialise (stream, options = {}) { | 
|   check.assert.instanceStrict(stream, require('stream').Readable, 'Invalid stream argument') | 
|   | 
|   const currentPosition = { | 
|     line: 1, | 
|     column: 1 | 
|   } | 
|   const emitter = new EventEmitter() | 
|   const handlers = { | 
|     arr: value, | 
|     obj: property | 
|   } | 
|   const json = [] | 
|   const lengths = [] | 
|   const previousPosition = {} | 
|   const Promise = promise(options) | 
|   const scopes = [] | 
|   const yieldRate = options.yieldRate || 16384 | 
|   const shouldHandleNdjson = !! options.ndjson | 
|   | 
|   let index = 0 | 
|   let isStreamEnded = false | 
|   let isWalkBegun = false | 
|   let isWalkEnded = false | 
|   let isWalkingString = false | 
|   let hasEndedLine = true | 
|   let count = 0 | 
|   let resumeFn | 
|   let pause | 
|   let cachedCharacter | 
|   | 
|   stream.setEncoding('utf8') | 
|   stream.on('data', readStream) | 
|   stream.on('end', endStream) | 
|   stream.on('error', err => { | 
|     emitter.emit(events.error, err) | 
|     endStream() | 
|   }) | 
|   | 
|   emitter.pause = () => { | 
|     let resolve | 
|     pause = new Promise(res => resolve = res) | 
|     return () => { | 
|       pause = null | 
|       count = 0 | 
|   | 
|       if (shouldHandleNdjson && isStreamEnded && isWalkEnded) { | 
|         emit(events.end) | 
|       } else { | 
|         resolve() | 
|       } | 
|     } | 
|   } | 
|   | 
|   return emitter | 
|   | 
|   function readStream (chunk) { | 
|     addChunk(chunk) | 
|   | 
|     if (isWalkBegun) { | 
|       return resume() | 
|     } | 
|   | 
|     isWalkBegun = true | 
|     value() | 
|   } | 
|   | 
|   function addChunk (chunk) { | 
|     json.push(chunk) | 
|   | 
|     const chunkLength = chunk.length | 
|     lengths.push({ | 
|       item: chunkLength, | 
|       aggregate: length() + chunkLength | 
|     }) | 
|   } | 
|   | 
|   function length () { | 
|     const chunkCount = lengths.length | 
|   | 
|     if (chunkCount === 0) { | 
|       return 0 | 
|     } | 
|   | 
|     return lengths[chunkCount - 1].aggregate | 
|   } | 
|   | 
|   function value () { | 
|     /* eslint-disable no-underscore-dangle */ | 
|     if (++count % yieldRate !== 0) { | 
|       return _do() | 
|     } | 
|   | 
|     return new Promise(resolve => { | 
|       setImmediate(() => _do().then(resolve)) | 
|     }) | 
|   | 
|     function _do () { | 
|       return awaitNonWhitespace() | 
|         .then(next) | 
|         .then(handleValue) | 
|         .catch(() => {}) | 
|     } | 
|     /* eslint-enable no-underscore-dangle */ | 
|   } | 
|   | 
|   function awaitNonWhitespace () { | 
|     return wait() | 
|   | 
|     function wait () { | 
|       return awaitCharacter() | 
|         .then(step) | 
|     } | 
|   | 
|     function step () { | 
|       if (isWhitespace(character())) { | 
|         return next().then(wait) | 
|       } | 
|     } | 
|   } | 
|   | 
|   function awaitCharacter () { | 
|     let resolve, reject | 
|   | 
|     if (index < length()) { | 
|       return Promise.resolve() | 
|     } | 
|   | 
|     if (isStreamEnded) { | 
|       setImmediate(endWalk) | 
|       return Promise.reject() | 
|     } | 
|   | 
|     resumeFn = after | 
|   | 
|     return new Promise((res, rej) => { | 
|       resolve = res | 
|       reject = rej | 
|     }) | 
|   | 
|     function after () { | 
|       if (index < length()) { | 
|         return resolve() | 
|       } | 
|   | 
|       reject() | 
|   | 
|       if (isStreamEnded) { | 
|         setImmediate(endWalk) | 
|       } | 
|     } | 
|   } | 
|   | 
|   function character () { | 
|     if (cachedCharacter) { | 
|       return cachedCharacter | 
|     } | 
|   | 
|     if (lengths[0].item > index) { | 
|       return cachedCharacter = json[0][index] | 
|     } | 
|   | 
|     const len = lengths.length | 
|     for (let i = 1; i < len; ++i) { | 
|       const { aggregate, item } = lengths[i] | 
|       if (aggregate > index) { | 
|         return cachedCharacter = json[i][index + item - aggregate] | 
|       } | 
|     } | 
|   } | 
|   | 
|   function isWhitespace (char) { | 
|     switch (char) { | 
|       case '\n': | 
|         if (shouldHandleNdjson && scopes.length === 0) { | 
|           return false | 
|         } | 
|       case ' ': | 
|       case '\t': | 
|       case '\r': | 
|         return true | 
|     } | 
|   | 
|     return false | 
|   } | 
|   | 
|   function next () { | 
|     return awaitCharacter().then(after) | 
|   | 
|     function after () { | 
|       const result = character() | 
|   | 
|       cachedCharacter = null | 
|       index += 1 | 
|       previousPosition.line = currentPosition.line | 
|       previousPosition.column = currentPosition.column | 
|   | 
|       if (result === '\n') { | 
|         currentPosition.line += 1 | 
|         currentPosition.column = 1 | 
|       } else { | 
|         currentPosition.column += 1 | 
|       } | 
|   | 
|       if (index > lengths[0].aggregate) { | 
|         json.shift() | 
|   | 
|         const difference = lengths.shift().item | 
|         index -= difference | 
|   | 
|         lengths.forEach(len => len.aggregate -= difference) | 
|       } | 
|   | 
|       return result | 
|     } | 
|   } | 
|   | 
|   function handleValue (char) { | 
|     if (shouldHandleNdjson && scopes.length === 0) { | 
|       if (char === '\n') { | 
|         hasEndedLine = true | 
|         return emit(events.endLine) | 
|           .then(value) | 
|       } | 
|   | 
|       if (! hasEndedLine) { | 
|         return fail(char, '\n', previousPosition) | 
|           .then(value) | 
|       } | 
|   | 
|       hasEndedLine = false | 
|     } | 
|   | 
|     switch (char) { | 
|       case '[': | 
|         return array() | 
|       case '{': | 
|         return object() | 
|       case '"': | 
|         return string() | 
|       case '0': | 
|       case '1': | 
|       case '2': | 
|       case '3': | 
|       case '4': | 
|       case '5': | 
|       case '6': | 
|       case '7': | 
|       case '8': | 
|       case '9': | 
|       case '-': | 
|       case '.': | 
|         return number(char) | 
|       case 'f': | 
|         return literalFalse() | 
|       case 'n': | 
|         return literalNull() | 
|       case 't': | 
|         return literalTrue() | 
|       default: | 
|         return fail(char, 'value', previousPosition) | 
|           .then(value) | 
|     } | 
|   } | 
|   | 
|   function array () { | 
|     return scope(events.array, value) | 
|   } | 
|   | 
|   function scope (event, contentHandler) { | 
|     return emit(event) | 
|       .then(() => { | 
|         scopes.push(event) | 
|         return endScope(event) | 
|       }) | 
|       .then(contentHandler) | 
|   } | 
|   | 
|   function emit (...args) { | 
|     return (pause || Promise.resolve()) | 
|       .then(() => { | 
|         try { | 
|           emitter.emit(...args) | 
|         } catch (err) { | 
|           try { | 
|             emitter.emit(events.error, err) | 
|           } catch (_) { | 
|             // When calling user code, anything is possible | 
|           } | 
|         } | 
|       }) | 
|   } | 
|   | 
|   function endScope (scp) { | 
|     return awaitNonWhitespace() | 
|       .then(() => { | 
|         if (character() === terminators[scp]) { | 
|           return emit(events.endPrefix + scp) | 
|             .then(() => { | 
|               scopes.pop() | 
|               return next() | 
|             }) | 
|             .then(endValue) | 
|         } | 
|       }) | 
|       .catch(endWalk) | 
|   } | 
|   | 
|   function endValue () { | 
|     return awaitNonWhitespace() | 
|       .then(after) | 
|       .catch(endWalk) | 
|   | 
|     function after () { | 
|       if (scopes.length === 0) { | 
|         if (shouldHandleNdjson) { | 
|           return value() | 
|         } | 
|   | 
|         return fail(character(), 'EOF', currentPosition) | 
|           .then(value) | 
|       } | 
|   | 
|       return checkScope() | 
|     } | 
|   | 
|     function checkScope () { | 
|       const scp = scopes[scopes.length - 1] | 
|       const handler = handlers[scp] | 
|   | 
|       return endScope(scp) | 
|         .then(() => { | 
|           if (scopes.length > 0) { | 
|             return checkCharacter(character(), ',', currentPosition) | 
|           } | 
|         }) | 
|         .then(result => { | 
|           if (result) { | 
|             return next() | 
|           } | 
|         }) | 
|         .then(handler) | 
|     } | 
|   } | 
|   | 
|   function fail (actual, expected, position) { | 
|     return emit( | 
|       events.dataError, | 
|       error.create( | 
|         actual, | 
|         expected, | 
|         position.line, | 
|         position.column | 
|       ) | 
|     ) | 
|   } | 
|   | 
|   function checkCharacter (char, expected, position) { | 
|     if (char === expected) { | 
|       return Promise.resolve(true) | 
|     } | 
|   | 
|     return fail(char, expected, position) | 
|       .then(false) | 
|   } | 
|   | 
|   function object () { | 
|     return scope(events.object, property) | 
|   } | 
|   | 
|   function property () { | 
|     return awaitNonWhitespace() | 
|       .then(next) | 
|       .then(propertyName) | 
|   } | 
|   | 
|   function propertyName (char) { | 
|     return checkCharacter(char, '"', previousPosition) | 
|       .then(() => walkString(events.property)) | 
|       .then(awaitNonWhitespace) | 
|       .then(next) | 
|       .then(propertyValue) | 
|   } | 
|   | 
|   function propertyValue (char) { | 
|     return checkCharacter(char, ':', previousPosition) | 
|       .then(value) | 
|   } | 
|   | 
|   function walkString (event) { | 
|     let isEscaping = false | 
|     const str = [] | 
|   | 
|     isWalkingString = true | 
|   | 
|     return next().then(step) | 
|   | 
|     function step (char) { | 
|       if (isEscaping) { | 
|         isEscaping = false | 
|   | 
|         return escape(char).then(escaped => { | 
|           str.push(escaped) | 
|           return next().then(step) | 
|         }) | 
|       } | 
|   | 
|       if (char === '\\') { | 
|         isEscaping = true | 
|         return next().then(step) | 
|       } | 
|   | 
|       if (char !== '"') { | 
|         str.push(char) | 
|         return next().then(step) | 
|       } | 
|   | 
|       isWalkingString = false | 
|       return emit(event, str.join('')) | 
|     } | 
|   } | 
|   | 
|   function escape (char) { | 
|     if (escapes[char]) { | 
|       return Promise.resolve(escapes[char]) | 
|     } | 
|   | 
|     if (char === 'u') { | 
|       return escapeHex() | 
|     } | 
|   | 
|     return fail(char, 'escape character', previousPosition) | 
|       .then(() => `\\${char}`) | 
|   } | 
|   | 
|   function escapeHex () { | 
|     let hexits = [] | 
|   | 
|     return next().then(step.bind(null, 0)) | 
|   | 
|     function step (idx, char) { | 
|       if (isHexit(char)) { | 
|         hexits.push(char) | 
|       } | 
|   | 
|       if (idx < 3) { | 
|         return next().then(step.bind(null, idx + 1)) | 
|       } | 
|   | 
|       hexits = hexits.join('') | 
|   | 
|       if (hexits.length === 4) { | 
|         return String.fromCharCode(parseInt(hexits, 16)) | 
|       } | 
|   | 
|       return fail(char, 'hex digit', previousPosition) | 
|         .then(() => `\\u${hexits}${char}`) | 
|     } | 
|   } | 
|   | 
|   function string () { | 
|     return walkString(events.string).then(endValue) | 
|   } | 
|   | 
|   function number (firstCharacter) { | 
|     let digits = [ firstCharacter ] | 
|   | 
|     return walkDigits().then(addDigits.bind(null, checkDecimalPlace)) | 
|   | 
|     function addDigits (step, result) { | 
|       digits = digits.concat(result.digits) | 
|   | 
|       if (result.atEnd) { | 
|         return endNumber() | 
|       } | 
|   | 
|       return step() | 
|     } | 
|   | 
|     function checkDecimalPlace () { | 
|       if (character() === '.') { | 
|         return next() | 
|           .then(char => { | 
|             digits.push(char) | 
|             return walkDigits() | 
|           }) | 
|           .then(addDigits.bind(null, checkExponent)) | 
|       } | 
|   | 
|       return checkExponent() | 
|     } | 
|   | 
|     function checkExponent () { | 
|       if (character() === 'e' || character() === 'E') { | 
|         return next() | 
|           .then(char => { | 
|             digits.push(char) | 
|             return awaitCharacter() | 
|           }) | 
|           .then(checkSign) | 
|           .catch(fail.bind(null, 'EOF', 'exponent', currentPosition)) | 
|       } | 
|   | 
|       return endNumber() | 
|     } | 
|   | 
|     function checkSign () { | 
|       if (character() === '+' || character() === '-') { | 
|         return next().then(char => { | 
|           digits.push(char) | 
|           return readExponent() | 
|         }) | 
|       } | 
|   | 
|       return readExponent() | 
|     } | 
|   | 
|     function readExponent () { | 
|       return walkDigits().then(addDigits.bind(null, endNumber)) | 
|     } | 
|   | 
|     function endNumber () { | 
|       return emit(events.number, parseFloat(digits.join(''))) | 
|         .then(endValue) | 
|     } | 
|   } | 
|   | 
|   function walkDigits () { | 
|     const digits = [] | 
|   | 
|     return wait() | 
|   | 
|     function wait () { | 
|       return awaitCharacter() | 
|         .then(step) | 
|         .catch(atEnd) | 
|     } | 
|   | 
|     function step () { | 
|       if (isDigit(character())) { | 
|         return next().then(char => { | 
|           digits.push(char) | 
|           return wait() | 
|         }) | 
|       } | 
|   | 
|       return { digits, atEnd: false } | 
|     } | 
|   | 
|     function atEnd () { | 
|       return { digits, atEnd: true } | 
|     } | 
|   } | 
|   | 
|   function literalFalse () { | 
|     return literal([ 'a', 'l', 's', 'e' ], false) | 
|   } | 
|   | 
|   function literal (expectedCharacters, val) { | 
|     let actual, expected, invalid | 
|   | 
|     return wait() | 
|   | 
|     function wait () { | 
|       return awaitCharacter() | 
|         .then(step) | 
|         .catch(atEnd) | 
|     } | 
|   | 
|     function step () { | 
|       if (invalid || expectedCharacters.length === 0) { | 
|         return atEnd() | 
|       } | 
|   | 
|       return next().then(afterNext) | 
|     } | 
|   | 
|     function atEnd () { | 
|       return Promise.resolve() | 
|         .then(() => { | 
|           if (invalid) { | 
|             return fail(actual, expected, previousPosition) | 
|           } | 
|   | 
|           if (expectedCharacters.length > 0) { | 
|             return fail('EOF', expectedCharacters.shift(), currentPosition) | 
|           } | 
|   | 
|           return done() | 
|         }) | 
|         .then(endValue) | 
|     } | 
|   | 
|     function afterNext (char) { | 
|       actual = char | 
|       expected = expectedCharacters.shift() | 
|   | 
|       if (actual !== expected) { | 
|         invalid = true | 
|       } | 
|   | 
|       return wait() | 
|     } | 
|   | 
|     function done () { | 
|       return emit(events.literal, val) | 
|     } | 
|   } | 
|   | 
|   function literalNull () { | 
|     return literal([ 'u', 'l', 'l' ], null) | 
|   } | 
|   | 
|   function literalTrue () { | 
|     return literal([ 'r', 'u', 'e' ], true) | 
|   } | 
|   | 
|   function endStream () { | 
|     isStreamEnded = true | 
|   | 
|     if (isWalkBegun) { | 
|       return resume() | 
|     } | 
|   | 
|     endWalk() | 
|   } | 
|   | 
|   function resume () { | 
|     if (resumeFn) { | 
|       resumeFn() | 
|       resumeFn = null | 
|     } | 
|   } | 
|   | 
|   function endWalk () { | 
|     if (isWalkEnded) { | 
|       return Promise.resolve() | 
|     } | 
|   | 
|     isWalkEnded = true | 
|   | 
|     return Promise.resolve() | 
|       .then(() => { | 
|         if (isWalkingString) { | 
|           return fail('EOF', '"', currentPosition) | 
|         } | 
|       }) | 
|       .then(popScopes) | 
|       .then(() => emit(events.end)) | 
|   } | 
|   | 
|   function popScopes () { | 
|     if (scopes.length === 0) { | 
|       return Promise.resolve() | 
|     } | 
|   | 
|     return fail('EOF', terminators[scopes.pop()], currentPosition) | 
|       .then(popScopes) | 
|   } | 
| } | 
|   | 
| function isHexit (character) { | 
|   return isDigit(character) || | 
|     isInRange(character, 'A', 'F') || | 
|     isInRange(character, 'a', 'f') | 
| } | 
|   | 
| function isDigit (character) { | 
|   return isInRange(character, '0', '9') | 
| } | 
|   | 
| function isInRange (character, lower, upper) { | 
|   const code = character.charCodeAt(0) | 
|   | 
|   return code >= lower.charCodeAt(0) && code <= upper.charCodeAt(0) | 
| } |