"Fossies" - the Fresh Open Source Software Archive

Member "Atom/resources/app/apm/node_modules/jju/lib/parse.js" (11 Apr 2017, 18760 Bytes) of package /windows/misc/atom-windows.zip:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Javascript source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file.

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