Initial commit
[yaffs-website] / node_modules / parse-json / vendor / parse.js
1 /*
2  * Author: Alex Kocharin <alex@kocharin.ru>
3  * GIT: https://github.com/rlidwka/jju
4  * License: WTFPL, grab your copy here: http://www.wtfpl.net/txt/copying/
5  */
6
7 // RTFM: http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf
8
9 var Uni = require('./unicode')
10
11 function isHexDigit(x) {
12   return (x >= '0' && x <= '9')
13       || (x >= 'A' && x <= 'F')
14       || (x >= 'a' && x <= 'f')
15 }
16
17 function isOctDigit(x) {
18   return x >= '0' && x <= '7'
19 }
20
21 function isDecDigit(x) {
22   return x >= '0' && x <= '9'
23 }
24
25 var unescapeMap = {
26   '\'': '\'',
27   '"' : '"',
28   '\\': '\\',
29   'b' : '\b',
30   'f' : '\f',
31   'n' : '\n',
32   'r' : '\r',
33   't' : '\t',
34   'v' : '\v',
35   '/' : '/',
36 }
37
38 function formatError(input, msg, position, lineno, column, json5) {
39   var result = msg + ' at ' + (lineno + 1) + ':' + (column + 1)
40     , tmppos = position - column - 1
41     , srcline = ''
42     , underline = ''
43
44   var isLineTerminator = json5 ? Uni.isLineTerminator : Uni.isLineTerminatorJSON
45
46   // output no more than 70 characters before the wrong ones
47   if (tmppos < position - 70) {
48     tmppos = position - 70
49   }
50
51   while (1) {
52     var chr = input[++tmppos]
53
54     if (isLineTerminator(chr) || tmppos === input.length) {
55       if (position >= tmppos) {
56         // ending line error, so show it after the last char
57         underline += '^'
58       }
59       break
60     }
61     srcline += chr
62
63     if (position === tmppos) {
64       underline += '^'
65     } else if (position > tmppos) {
66       underline += input[tmppos] === '\t' ? '\t' : ' '
67     }
68
69     // output no more than 78 characters on the string
70     if (srcline.length > 78) break
71   }
72
73   return result + '\n' + srcline + '\n' + underline
74 }
75
76 function parse(input, options) {
77   // parse as a standard JSON mode
78   var json5 = !(options.mode === 'json' || options.legacy)
79   var isLineTerminator = json5 ? Uni.isLineTerminator : Uni.isLineTerminatorJSON
80   var isWhiteSpace = json5 ? Uni.isWhiteSpace : Uni.isWhiteSpaceJSON
81
82   var length = input.length
83     , lineno = 0
84     , linestart = 0
85     , position = 0
86     , stack = []
87
88   var tokenStart = function() {}
89   var tokenEnd = function(v) {return v}
90
91   /* tokenize({
92        raw: '...',
93        type: 'whitespace'|'comment'|'key'|'literal'|'separator'|'newline',
94        value: 'number'|'string'|'whatever',
95        path: [...],
96      })
97   */
98   if (options._tokenize) {
99     ;(function() {
100       var start = null
101       tokenStart = function() {
102         if (start !== null) throw Error('internal error, token overlap')
103         start = position
104       }
105
106       tokenEnd = function(v, type) {
107         if (start != position) {
108           var hash = {
109             raw: input.substr(start, position-start),
110             type: type,
111             stack: stack.slice(0),
112           }
113           if (v !== undefined) hash.value = v
114           options._tokenize.call(null, hash)
115         }
116         start = null
117         return v
118       }
119     })()
120   }
121
122   function fail(msg) {
123     var column = position - linestart
124
125     if (!msg) {
126       if (position < length) {
127         var token = '\'' +
128           JSON
129             .stringify(input[position])
130             .replace(/^"|"$/g, '')
131             .replace(/'/g, "\\'")
132             .replace(/\\"/g, '"')
133           + '\''
134
135         if (!msg) msg = 'Unexpected token ' + token
136       } else {
137         if (!msg) msg = 'Unexpected end of input'
138       }
139     }
140
141     var error = SyntaxError(formatError(input, msg, position, lineno, column, json5))
142     error.row = lineno + 1
143     error.column = column + 1
144     throw error
145   }
146
147   function newline(chr) {
148     // account for <cr><lf>
149     if (chr === '\r' && input[position] === '\n') position++
150     linestart = position
151     lineno++
152   }
153
154   function parseGeneric() {
155     var result
156
157     while (position < length) {
158       tokenStart()
159       var chr = input[position++]
160
161       if (chr === '"' || (chr === '\'' && json5)) {
162         return tokenEnd(parseString(chr), 'literal')
163
164       } else if (chr === '{') {
165         tokenEnd(undefined, 'separator')
166         return parseObject()
167
168       } else if (chr === '[') {
169         tokenEnd(undefined, 'separator')
170         return parseArray()
171
172       } else if (chr === '-'
173              ||  chr === '.'
174              ||  isDecDigit(chr)
175                  //           + number       Infinity          NaN
176              ||  (json5 && (chr === '+' || chr === 'I' || chr === 'N'))
177       ) {
178         return tokenEnd(parseNumber(), 'literal')
179
180       } else if (chr === 'n') {
181         parseKeyword('null')
182         return tokenEnd(null, 'literal')
183
184       } else if (chr === 't') {
185         parseKeyword('true')
186         return tokenEnd(true, 'literal')
187
188       } else if (chr === 'f') {
189         parseKeyword('false')
190         return tokenEnd(false, 'literal')
191
192       } else {
193         position--
194         return tokenEnd(undefined)
195       }
196     }
197   }
198
199   function parseKey() {
200     var result
201
202     while (position < length) {
203       tokenStart()
204       var chr = input[position++]
205
206       if (chr === '"' || (chr === '\'' && json5)) {
207         return tokenEnd(parseString(chr), 'key')
208
209       } else if (chr === '{') {
210         tokenEnd(undefined, 'separator')
211         return parseObject()
212
213       } else if (chr === '[') {
214         tokenEnd(undefined, 'separator')
215         return parseArray()
216
217       } else if (chr === '.'
218              ||  isDecDigit(chr)
219       ) {
220         return tokenEnd(parseNumber(true), 'key')
221
222       } else if (json5
223              &&  Uni.isIdentifierStart(chr) || (chr === '\\' && input[position] === 'u')) {
224         // unicode char or a unicode sequence
225         var rollback = position - 1
226         var result = parseIdentifier()
227
228         if (result === undefined) {
229           position = rollback
230           return tokenEnd(undefined)
231         } else {
232           return tokenEnd(result, 'key')
233         }
234
235       } else {
236         position--
237         return tokenEnd(undefined)
238       }
239     }
240   }
241
242   function skipWhiteSpace() {
243     tokenStart()
244     while (position < length) {
245       var chr = input[position++]
246
247       if (isLineTerminator(chr)) {
248         position--
249         tokenEnd(undefined, 'whitespace')
250         tokenStart()
251         position++
252         newline(chr)
253         tokenEnd(undefined, 'newline')
254         tokenStart()
255
256       } else if (isWhiteSpace(chr)) {
257         // nothing
258
259       } else if (chr === '/'
260              && json5
261              && (input[position] === '/' || input[position] === '*')
262       ) {
263         position--
264         tokenEnd(undefined, 'whitespace')
265         tokenStart()
266         position++
267         skipComment(input[position++] === '*')
268         tokenEnd(undefined, 'comment')
269         tokenStart()
270
271       } else {
272         position--
273         break
274       }
275     }
276     return tokenEnd(undefined, 'whitespace')
277   }
278
279   function skipComment(multi) {
280     while (position < length) {
281       var chr = input[position++]
282
283       if (isLineTerminator(chr)) {
284         // LineTerminator is an end of singleline comment
285         if (!multi) {
286           // let parent function deal with newline
287           position--
288           return
289         }
290
291         newline(chr)
292
293       } else if (chr === '*' && multi) {
294         // end of multiline comment
295         if (input[position] === '/') {
296           position++
297           return
298         }
299
300       } else {
301         // nothing
302       }
303     }
304
305     if (multi) {
306       fail('Unclosed multiline comment')
307     }
308   }
309
310   function parseKeyword(keyword) {
311     // keyword[0] is not checked because it should've checked earlier
312     var _pos = position
313     var len = keyword.length
314     for (var i=1; i<len; i++) {
315       if (position >= length || keyword[i] != input[position]) {
316         position = _pos-1
317         fail()
318       }
319       position++
320     }
321   }
322
323   function parseObject() {
324     var result = options.null_prototype ? Object.create(null) : {}
325       , empty_object = {}
326       , is_non_empty = false
327
328     while (position < length) {
329       skipWhiteSpace()
330       var item1 = parseKey()
331       skipWhiteSpace()
332       tokenStart()
333       var chr = input[position++]
334       tokenEnd(undefined, 'separator')
335
336       if (chr === '}' && item1 === undefined) {
337         if (!json5 && is_non_empty) {
338           position--
339           fail('Trailing comma in object')
340         }
341         return result
342
343       } else if (chr === ':' && item1 !== undefined) {
344         skipWhiteSpace()
345         stack.push(item1)
346         var item2 = parseGeneric()
347         stack.pop()
348
349         if (item2 === undefined) fail('No value found for key ' + item1)
350         if (typeof(item1) !== 'string') {
351           if (!json5 || typeof(item1) !== 'number') {
352             fail('Wrong key type: ' + item1)
353           }
354         }
355
356         if ((item1 in empty_object || empty_object[item1] != null) && options.reserved_keys !== 'replace') {
357           if (options.reserved_keys === 'throw') {
358             fail('Reserved key: ' + item1)
359           } else {
360             // silently ignore it
361           }
362         } else {
363           if (typeof(options.reviver) === 'function') {
364             item2 = options.reviver.call(null, item1, item2)
365           }
366
367           if (item2 !== undefined) {
368             is_non_empty = true
369             Object.defineProperty(result, item1, {
370               value: item2,
371               enumerable: true,
372               configurable: true,
373               writable: true,
374             })
375           }
376         }
377
378         skipWhiteSpace()
379
380         tokenStart()
381         var chr = input[position++]
382         tokenEnd(undefined, 'separator')
383
384         if (chr === ',') {
385           continue
386
387         } else if (chr === '}') {
388           return result
389
390         } else {
391           fail()
392         }
393
394       } else {
395         position--
396         fail()
397       }
398     }
399
400     fail()
401   }
402
403   function parseArray() {
404     var result = []
405
406     while (position < length) {
407       skipWhiteSpace()
408       stack.push(result.length)
409       var item = parseGeneric()
410       stack.pop()
411       skipWhiteSpace()
412       tokenStart()
413       var chr = input[position++]
414       tokenEnd(undefined, 'separator')
415
416       if (item !== undefined) {
417         if (typeof(options.reviver) === 'function') {
418           item = options.reviver.call(null, String(result.length), item)
419         }
420         if (item === undefined) {
421           result.length++
422           item = true // hack for check below, not included into result
423         } else {
424           result.push(item)
425         }
426       }
427
428       if (chr === ',') {
429         if (item === undefined) {
430           fail('Elisions are not supported')
431         }
432
433       } else if (chr === ']') {
434         if (!json5 && item === undefined && result.length) {
435           position--
436           fail('Trailing comma in array')
437         }
438         return result
439
440       } else {
441         position--
442         fail()
443       }
444     }
445   }
446
447   function parseNumber() {
448     // rewind because we don't know first char
449     position--
450
451     var start = position
452       , chr = input[position++]
453       , t
454
455     var to_num = function(is_octal) {
456       var str = input.substr(start, position - start)
457
458       if (is_octal) {
459         var result = parseInt(str.replace(/^0o?/, ''), 8)
460       } else {
461         var result = Number(str)
462       }
463
464       if (Number.isNaN(result)) {
465         position--
466         fail('Bad numeric literal - "' + input.substr(start, position - start + 1) + '"')
467       } else if (!json5 && !str.match(/^-?(0|[1-9][0-9]*)(\.[0-9]+)?(e[+-]?[0-9]+)?$/i)) {
468         // additional restrictions imposed by json
469         position--
470         fail('Non-json numeric literal - "' + input.substr(start, position - start + 1) + '"')
471       } else {
472         return result
473       }
474     }
475
476     // ex: -5982475.249875e+29384
477     //     ^ skipping this
478     if (chr === '-' || (chr === '+' && json5)) chr = input[position++]
479
480     if (chr === 'N' && json5) {
481       parseKeyword('NaN')
482       return NaN
483     }
484
485     if (chr === 'I' && json5) {
486       parseKeyword('Infinity')
487
488       // returning +inf or -inf
489       return to_num()
490     }
491
492     if (chr >= '1' && chr <= '9') {
493       // ex: -5982475.249875e+29384
494       //        ^^^ skipping these
495       while (position < length && isDecDigit(input[position])) position++
496       chr = input[position++]
497     }
498
499     // special case for leading zero: 0.123456
500     if (chr === '0') {
501       chr = input[position++]
502
503       //             new syntax, "0o777"           old syntax, "0777"
504       var is_octal = chr === 'o' || chr === 'O' || isOctDigit(chr)
505       var is_hex = chr === 'x' || chr === 'X'
506
507       if (json5 && (is_octal || is_hex)) {
508         while (position < length
509            &&  (is_hex ? isHexDigit : isOctDigit)( input[position] )
510         ) position++
511
512         var sign = 1
513         if (input[start] === '-') {
514           sign = -1
515           start++
516         } else if (input[start] === '+') {
517           start++
518         }
519
520         return sign * to_num(is_octal)
521       }
522     }
523
524     if (chr === '.') {
525       // ex: -5982475.249875e+29384
526       //                ^^^ skipping these
527       while (position < length && isDecDigit(input[position])) position++
528       chr = input[position++]
529     }
530
531     if (chr === 'e' || chr === 'E') {
532       chr = input[position++]
533       if (chr === '-' || chr === '+') position++
534       // ex: -5982475.249875e+29384
535       //                       ^^^ skipping these
536       while (position < length && isDecDigit(input[position])) position++
537       chr = input[position++]
538     }
539
540     // we have char in the buffer, so count for it
541     position--
542     return to_num()
543   }
544
545   function parseIdentifier() {
546     // rewind because we don't know first char
547     position--
548
549     var result = ''
550
551     while (position < length) {
552       var chr = input[position++]
553
554       if (chr === '\\'
555       &&  input[position] === 'u'
556       &&  isHexDigit(input[position+1])
557       &&  isHexDigit(input[position+2])
558       &&  isHexDigit(input[position+3])
559       &&  isHexDigit(input[position+4])
560       ) {
561         // UnicodeEscapeSequence
562         chr = String.fromCharCode(parseInt(input.substr(position+1, 4), 16))
563         position += 5
564       }
565
566       if (result.length) {
567         // identifier started
568         if (Uni.isIdentifierPart(chr)) {
569           result += chr
570         } else {
571           position--
572           return result
573         }
574
575       } else {
576         if (Uni.isIdentifierStart(chr)) {
577           result += chr
578         } else {
579           return undefined
580         }
581       }
582     }
583
584     fail()
585   }
586
587   function parseString(endChar) {
588     // 7.8.4 of ES262 spec
589     var result = ''
590
591     while (position < length) {
592       var chr = input[position++]
593
594       if (chr === endChar) {
595         return result
596
597       } else if (chr === '\\') {
598         if (position >= length) fail()
599         chr = input[position++]
600
601         if (unescapeMap[chr] && (json5 || (chr != 'v' && chr != "'"))) {
602           result += unescapeMap[chr]
603
604         } else if (json5 && isLineTerminator(chr)) {
605           // line continuation
606           newline(chr)
607
608         } else if (chr === 'u' || (chr === 'x' && json5)) {
609           // unicode/character escape sequence
610           var off = chr === 'u' ? 4 : 2
611
612           // validation for \uXXXX
613           for (var i=0; i<off; i++) {
614             if (position >= length) fail()
615             if (!isHexDigit(input[position])) fail('Bad escape sequence')
616             position++
617           }
618
619           result += String.fromCharCode(parseInt(input.substr(position-off, off), 16))
620         } else if (json5 && isOctDigit(chr)) {
621           if (chr < '4' && isOctDigit(input[position]) && isOctDigit(input[position+1])) {
622             // three-digit octal
623             var digits = 3
624           } else if (isOctDigit(input[position])) {
625             // two-digit octal
626             var digits = 2
627           } else {
628             var digits = 1
629           }
630           position += digits - 1
631           result += String.fromCharCode(parseInt(input.substr(position-digits, digits), 8))
632           /*if (!isOctDigit(input[position])) {
633             // \0 is allowed still
634             result += '\0'
635           } else {
636             fail('Octal literals are not supported')
637           }*/
638
639         } else if (json5) {
640           // \X -> x
641           result += chr
642
643         } else {
644           position--
645           fail()
646         }
647
648       } else if (isLineTerminator(chr)) {
649         fail()
650
651       } else {
652         if (!json5 && chr.charCodeAt(0) < 32) {
653           position--
654           fail('Unexpected control character')
655         }
656
657         // SourceCharacter but not one of " or \ or LineTerminator
658         result += chr
659       }
660     }
661
662     fail()
663   }
664
665   skipWhiteSpace()
666   var return_value = parseGeneric()
667   if (return_value !== undefined || position < length) {
668     skipWhiteSpace()
669
670     if (position >= length) {
671       if (typeof(options.reviver) === 'function') {
672         return_value = options.reviver.call(null, '', return_value)
673       }
674       return return_value
675     } else {
676       fail()
677     }
678
679   } else {
680     if (position) {
681       fail('No data, only a whitespace')
682     } else {
683       fail('No data, empty input')
684     }
685   }
686 }
687
688 /*
689  * parse(text, options)
690  * or
691  * parse(text, reviver)
692  *
693  * where:
694  * text - string
695  * options - object
696  * reviver - function
697  */
698 module.exports.parse = function parseJSON(input, options) {
699   // support legacy functions
700   if (typeof(options) === 'function') {
701     options = {
702       reviver: options
703     }
704   }
705
706   if (input === undefined) {
707     // parse(stringify(x)) should be equal x
708     // with JSON functions it is not 'cause of undefined
709     // so we're fixing it
710     return undefined
711   }
712
713   // JSON.parse compat
714   if (typeof(input) !== 'string') input = String(input)
715   if (options == null) options = {}
716   if (options.reserved_keys == null) options.reserved_keys = 'ignore'
717
718   if (options.reserved_keys === 'throw' || options.reserved_keys === 'ignore') {
719     if (options.null_prototype == null) {
720       options.null_prototype = true
721     }
722   }
723
724   try {
725     return parse(input, options)
726   } catch(err) {
727     // jju is a recursive parser, so JSON.parse("{{{{{{{") could blow up the stack
728     //
729     // this catch is used to skip all those internal calls
730     if (err instanceof SyntaxError && err.row != null && err.column != null) {
731       var old_err = err
732       err = SyntaxError(old_err.message)
733       err.column = old_err.column
734       err.row = old_err.row
735     }
736     throw err
737   }
738 }
739
740 module.exports.tokenize = function tokenizeJSON(input, options) {
741   if (options == null) options = {}
742
743   options._tokenize = function(smth) {
744     if (options._addstack) smth.stack.unshift.apply(smth.stack, options._addstack)
745     tokens.push(smth)
746   }
747
748   var tokens = []
749   tokens.data = module.exports.parse(input, options)
750   return tokens
751 }
752