source: frontend/node_modules/bfj/src/walk.js

Last change on this file was 9af201e, checked in by MBK <marija.karapandzova@…>, 11 days ago

Fix frontend appearance

  • Property mode set to 100644
File size: 14.2 KB
Line 
1'use strict'
2
3const check = require('check-types')
4const error = require('./error')
5const EventEmitter = require('events').EventEmitter
6const events = require('./events')
7const promise = require('./promise')
8
9const terminators = {
10 obj: '}',
11 arr: ']'
12}
13
14const escapes = {
15 /* eslint-disable quote-props */
16 '"': '"',
17 '\\': '\\',
18 '/': '/',
19 'b': '\b',
20 'f': '\f',
21 'n': '\n',
22 'r': '\r',
23 't': '\t'
24 /* eslint-enable quote-props */
25}
26
27module.exports = initialise
28
29/**
30 * Public function `walk`.
31 *
32 * Returns an event emitter and asynchronously walks a stream of JSON data,
33 * emitting events as it encounters tokens. The event emitter is decorated
34 * with a `pause` method that can be called to pause processing.
35 *
36 * @param stream: Readable instance representing the incoming JSON.
37 *
38 * @option yieldRate: The number of data items to process per timeslice,
39 * default is 16384.
40 *
41 * @option Promise: The promise constructor to use, defaults to bluebird.
42 *
43 * @option ndjson: Set this to true to parse newline-delimited JSON.
44 **/
45function initialise (stream, options = {}) {
46 check.assert.instanceStrict(stream, require('stream').Readable, 'Invalid stream argument')
47
48 const currentPosition = {
49 line: 1,
50 column: 1
51 }
52 const emitter = new EventEmitter()
53 const handlers = {
54 arr: value,
55 obj: property
56 }
57 const json = []
58 const lengths = []
59 const previousPosition = {}
60 const Promise = promise(options)
61 const scopes = []
62 const yieldRate = options.yieldRate || 16384
63 const shouldHandleNdjson = !! options.ndjson
64
65 let index = 0
66 let isStreamEnded = false
67 let isWalkBegun = false
68 let isWalkEnded = false
69 let isWalkingString = false
70 let hasEndedLine = true
71 let count = 0
72 let resumeFn
73 let pause
74 let cachedCharacter
75
76 stream.setEncoding('utf8')
77 stream.on('data', readStream)
78 stream.on('end', endStream)
79 stream.on('error', err => {
80 emitter.emit(events.error, err)
81 endStream()
82 })
83
84 emitter.pause = () => {
85 let resolve
86 pause = new Promise(res => resolve = res)
87 return () => {
88 pause = null
89 count = 0
90
91 if (shouldHandleNdjson && isStreamEnded && isWalkEnded) {
92 emit(events.end)
93 } else {
94 resolve()
95 }
96 }
97 }
98
99 return emitter
100
101 function readStream (chunk) {
102 addChunk(chunk)
103
104 if (isWalkBegun) {
105 return resume()
106 }
107
108 isWalkBegun = true
109 value()
110 }
111
112 function addChunk (chunk) {
113 json.push(chunk)
114
115 const chunkLength = chunk.length
116 lengths.push({
117 item: chunkLength,
118 aggregate: length() + chunkLength
119 })
120 }
121
122 function length () {
123 const chunkCount = lengths.length
124
125 if (chunkCount === 0) {
126 return 0
127 }
128
129 return lengths[chunkCount - 1].aggregate
130 }
131
132 function value () {
133 /* eslint-disable no-underscore-dangle */
134 if (++count % yieldRate !== 0) {
135 return _do()
136 }
137
138 return new Promise(resolve => {
139 setImmediate(() => _do().then(resolve))
140 })
141
142 function _do () {
143 return awaitNonWhitespace()
144 .then(next)
145 .then(handleValue)
146 .catch(() => {})
147 }
148 /* eslint-enable no-underscore-dangle */
149 }
150
151 function awaitNonWhitespace () {
152 return wait()
153
154 function wait () {
155 return awaitCharacter()
156 .then(step)
157 }
158
159 function step () {
160 if (isWhitespace(character())) {
161 return next().then(wait)
162 }
163 }
164 }
165
166 function awaitCharacter () {
167 let resolve, reject
168
169 if (index < length()) {
170 return Promise.resolve()
171 }
172
173 if (isStreamEnded) {
174 setImmediate(endWalk)
175 return Promise.reject()
176 }
177
178 resumeFn = after
179
180 return new Promise((res, rej) => {
181 resolve = res
182 reject = rej
183 })
184
185 function after () {
186 if (index < length()) {
187 return resolve()
188 }
189
190 reject()
191
192 if (isStreamEnded) {
193 setImmediate(endWalk)
194 }
195 }
196 }
197
198 function character () {
199 if (cachedCharacter) {
200 return cachedCharacter
201 }
202
203 if (lengths[0].item > index) {
204 return cachedCharacter = json[0][index]
205 }
206
207 const len = lengths.length
208 for (let i = 1; i < len; ++i) {
209 const { aggregate, item } = lengths[i]
210 if (aggregate > index) {
211 return cachedCharacter = json[i][index + item - aggregate]
212 }
213 }
214 }
215
216 function isWhitespace (char) {
217 switch (char) {
218 case '\n':
219 if (shouldHandleNdjson && scopes.length === 0) {
220 return false
221 }
222 case ' ':
223 case '\t':
224 case '\r':
225 return true
226 }
227
228 return false
229 }
230
231 function next () {
232 return awaitCharacter().then(after)
233
234 function after () {
235 const result = character()
236
237 cachedCharacter = null
238 index += 1
239 previousPosition.line = currentPosition.line
240 previousPosition.column = currentPosition.column
241
242 if (result === '\n') {
243 currentPosition.line += 1
244 currentPosition.column = 1
245 } else {
246 currentPosition.column += 1
247 }
248
249 if (index > lengths[0].aggregate) {
250 json.shift()
251
252 const difference = lengths.shift().item
253 index -= difference
254
255 lengths.forEach(len => len.aggregate -= difference)
256 }
257
258 return result
259 }
260 }
261
262 function handleValue (char) {
263 if (shouldHandleNdjson && scopes.length === 0) {
264 if (char === '\n') {
265 hasEndedLine = true
266 return emit(events.endLine)
267 .then(value)
268 }
269
270 if (! hasEndedLine) {
271 return fail(char, '\n', previousPosition)
272 .then(value)
273 }
274
275 hasEndedLine = false
276 }
277
278 switch (char) {
279 case '[':
280 return array()
281 case '{':
282 return object()
283 case '"':
284 return string()
285 case '0':
286 case '1':
287 case '2':
288 case '3':
289 case '4':
290 case '5':
291 case '6':
292 case '7':
293 case '8':
294 case '9':
295 case '-':
296 case '.':
297 return number(char)
298 case 'f':
299 return literalFalse()
300 case 'n':
301 return literalNull()
302 case 't':
303 return literalTrue()
304 default:
305 return fail(char, 'value', previousPosition)
306 .then(value)
307 }
308 }
309
310 function array () {
311 return scope(events.array, value)
312 }
313
314 function scope (event, contentHandler) {
315 return emit(event)
316 .then(() => {
317 scopes.push(event)
318 return endScope(event)
319 })
320 .then(contentHandler)
321 }
322
323 function emit (...args) {
324 return (pause || Promise.resolve())
325 .then(() => {
326 try {
327 emitter.emit(...args)
328 } catch (err) {
329 try {
330 emitter.emit(events.error, err)
331 } catch (_) {
332 // When calling user code, anything is possible
333 }
334 }
335 })
336 }
337
338 function endScope (scp) {
339 return awaitNonWhitespace()
340 .then(() => {
341 if (character() === terminators[scp]) {
342 return emit(events.endPrefix + scp)
343 .then(() => {
344 scopes.pop()
345 return next()
346 })
347 .then(endValue)
348 }
349 })
350 .catch(endWalk)
351 }
352
353 function endValue () {
354 return awaitNonWhitespace()
355 .then(after)
356 .catch(endWalk)
357
358 function after () {
359 if (scopes.length === 0) {
360 if (shouldHandleNdjson) {
361 return value()
362 }
363
364 return fail(character(), 'EOF', currentPosition)
365 .then(value)
366 }
367
368 return checkScope()
369 }
370
371 function checkScope () {
372 const scp = scopes[scopes.length - 1]
373 const handler = handlers[scp]
374
375 return endScope(scp)
376 .then(() => {
377 if (scopes.length > 0) {
378 return checkCharacter(character(), ',', currentPosition)
379 }
380 })
381 .then(result => {
382 if (result) {
383 return next()
384 }
385 })
386 .then(handler)
387 }
388 }
389
390 function fail (actual, expected, position) {
391 return emit(
392 events.dataError,
393 error.create(
394 actual,
395 expected,
396 position.line,
397 position.column
398 )
399 )
400 }
401
402 function checkCharacter (char, expected, position) {
403 if (char === expected) {
404 return Promise.resolve(true)
405 }
406
407 return fail(char, expected, position)
408 .then(false)
409 }
410
411 function object () {
412 return scope(events.object, property)
413 }
414
415 function property () {
416 return awaitNonWhitespace()
417 .then(next)
418 .then(propertyName)
419 }
420
421 function propertyName (char) {
422 return checkCharacter(char, '"', previousPosition)
423 .then(() => walkString(events.property))
424 .then(awaitNonWhitespace)
425 .then(next)
426 .then(propertyValue)
427 }
428
429 function propertyValue (char) {
430 return checkCharacter(char, ':', previousPosition)
431 .then(value)
432 }
433
434 function walkString (event) {
435 let isEscaping = false
436 const str = []
437
438 isWalkingString = true
439
440 return next().then(step)
441
442 function step (char) {
443 if (isEscaping) {
444 isEscaping = false
445
446 return escape(char).then(escaped => {
447 str.push(escaped)
448 return next().then(step)
449 })
450 }
451
452 if (char === '\\') {
453 isEscaping = true
454 return next().then(step)
455 }
456
457 if (char !== '"') {
458 str.push(char)
459 return next().then(step)
460 }
461
462 isWalkingString = false
463 return emit(event, str.join(''))
464 }
465 }
466
467 function escape (char) {
468 if (escapes[char]) {
469 return Promise.resolve(escapes[char])
470 }
471
472 if (char === 'u') {
473 return escapeHex()
474 }
475
476 return fail(char, 'escape character', previousPosition)
477 .then(() => `\\${char}`)
478 }
479
480 function escapeHex () {
481 let hexits = []
482
483 return next().then(step.bind(null, 0))
484
485 function step (idx, char) {
486 if (isHexit(char)) {
487 hexits.push(char)
488 }
489
490 if (idx < 3) {
491 return next().then(step.bind(null, idx + 1))
492 }
493
494 hexits = hexits.join('')
495
496 if (hexits.length === 4) {
497 return String.fromCharCode(parseInt(hexits, 16))
498 }
499
500 return fail(char, 'hex digit', previousPosition)
501 .then(() => `\\u${hexits}${char}`)
502 }
503 }
504
505 function string () {
506 return walkString(events.string).then(endValue)
507 }
508
509 function number (firstCharacter) {
510 let digits = [ firstCharacter ]
511
512 return walkDigits().then(addDigits.bind(null, checkDecimalPlace))
513
514 function addDigits (step, result) {
515 digits = digits.concat(result.digits)
516
517 if (result.atEnd) {
518 return endNumber()
519 }
520
521 return step()
522 }
523
524 function checkDecimalPlace () {
525 if (character() === '.') {
526 return next()
527 .then(char => {
528 digits.push(char)
529 return walkDigits()
530 })
531 .then(addDigits.bind(null, checkExponent))
532 }
533
534 return checkExponent()
535 }
536
537 function checkExponent () {
538 if (character() === 'e' || character() === 'E') {
539 return next()
540 .then(char => {
541 digits.push(char)
542 return awaitCharacter()
543 })
544 .then(checkSign)
545 .catch(fail.bind(null, 'EOF', 'exponent', currentPosition))
546 }
547
548 return endNumber()
549 }
550
551 function checkSign () {
552 if (character() === '+' || character() === '-') {
553 return next().then(char => {
554 digits.push(char)
555 return readExponent()
556 })
557 }
558
559 return readExponent()
560 }
561
562 function readExponent () {
563 return walkDigits().then(addDigits.bind(null, endNumber))
564 }
565
566 function endNumber () {
567 return emit(events.number, parseFloat(digits.join('')))
568 .then(endValue)
569 }
570 }
571
572 function walkDigits () {
573 const digits = []
574
575 return wait()
576
577 function wait () {
578 return awaitCharacter()
579 .then(step)
580 .catch(atEnd)
581 }
582
583 function step () {
584 if (isDigit(character())) {
585 return next().then(char => {
586 digits.push(char)
587 return wait()
588 })
589 }
590
591 return { digits, atEnd: false }
592 }
593
594 function atEnd () {
595 return { digits, atEnd: true }
596 }
597 }
598
599 function literalFalse () {
600 return literal([ 'a', 'l', 's', 'e' ], false)
601 }
602
603 function literal (expectedCharacters, val) {
604 let actual, expected, invalid
605
606 return wait()
607
608 function wait () {
609 return awaitCharacter()
610 .then(step)
611 .catch(atEnd)
612 }
613
614 function step () {
615 if (invalid || expectedCharacters.length === 0) {
616 return atEnd()
617 }
618
619 return next().then(afterNext)
620 }
621
622 function atEnd () {
623 return Promise.resolve()
624 .then(() => {
625 if (invalid) {
626 return fail(actual, expected, previousPosition)
627 }
628
629 if (expectedCharacters.length > 0) {
630 return fail('EOF', expectedCharacters.shift(), currentPosition)
631 }
632
633 return done()
634 })
635 .then(endValue)
636 }
637
638 function afterNext (char) {
639 actual = char
640 expected = expectedCharacters.shift()
641
642 if (actual !== expected) {
643 invalid = true
644 }
645
646 return wait()
647 }
648
649 function done () {
650 return emit(events.literal, val)
651 }
652 }
653
654 function literalNull () {
655 return literal([ 'u', 'l', 'l' ], null)
656 }
657
658 function literalTrue () {
659 return literal([ 'r', 'u', 'e' ], true)
660 }
661
662 function endStream () {
663 isStreamEnded = true
664
665 if (isWalkBegun) {
666 return resume()
667 }
668
669 endWalk()
670 }
671
672 function resume () {
673 if (resumeFn) {
674 resumeFn()
675 resumeFn = null
676 }
677 }
678
679 function endWalk () {
680 if (isWalkEnded) {
681 return Promise.resolve()
682 }
683
684 isWalkEnded = true
685
686 return Promise.resolve()
687 .then(() => {
688 if (isWalkingString) {
689 return fail('EOF', '"', currentPosition)
690 }
691 })
692 .then(popScopes)
693 .then(() => emit(events.end))
694 }
695
696 function popScopes () {
697 if (scopes.length === 0) {
698 return Promise.resolve()
699 }
700
701 return fail('EOF', terminators[scopes.pop()], currentPosition)
702 .then(popScopes)
703 }
704}
705
706function isHexit (character) {
707 return isDigit(character) ||
708 isInRange(character, 'A', 'F') ||
709 isInRange(character, 'a', 'f')
710}
711
712function isDigit (character) {
713 return isInRange(character, '0', '9')
714}
715
716function isInRange (character, lower, upper) {
717 const code = character.charCodeAt(0)
718
719 return code >= lower.charCodeAt(0) && code <= upper.charCodeAt(0)
720}
Note: See TracBrowser for help on using the repository browser.