nvison
============
- another json
- undefined
- bin
- hex
- oct
- scientific-notation
- bigInt
- Infinity
- NaN
- concatable-string
- comments
- hash
- ref
- optional-quote
- configurable-colons
- configurable-commas
- configurable-quotes
- configurable-array-blk
- configurable-object-blk
- post-dfs-generator


install
=======
- npm install nvison

requirements
============

- node need V15+ , coz it USE Event-Target  

usage
=====

    const ison = require("nvison");

parse
=====

examples
--------

### undefined

    var code = `[undefined,null,true,false]`
    var j = ison.parse_from_str(code)
    /*
    > j
    [ undefined, null, true, false ]
    >        
    */

### number

    var code = `
        [
            175 ,0x1101, 0b1101, 0o1101,
            -123.12e3, -1.1e2.5, .1E3.13,
            1234567891234567889123456789n,
            +Infinity, -Infinity, NaN
        ]
    `
    var j = ison.parse_from_str(code)

    /*
    > j
    [
      175,
      4353,
      13,
      577,
      -123120,
      -347.85054261852173,
      134.89628825916535,
      1234567891234567889123456789n,
      Infinity,
      -Infinity,
      NaN
    ]
    >
    */



### string 

    var code = `
        [
            abc-def,
            'def\tsquoted',
            "d    quoted",
            \`tock-quoted-line\ntock-quoted-line\`,
            str"@auto-concat@"str
        ]
    `
    var j = ison.parse_from_str(code)
    
    /*
        [
          'abc-def',
          'def\tsquoted',
          'd    quoted',
          'tock-quoted-line\ntock-quoted-line',
          'str@auto-concat@str'
        ]
    */


### commas 

- commas is optional
- support mixed-style
- by default it is  ","  ";"  and white-spaces
- its configurable: must-be-one-char 


#### 

    var code = `
        [1 2 3 a b c]
        
        [1,2,3,a,b,c]
        
        [1;2;3;a;b;c]
        
        [1 2,3;a b,c]
    `
    var j = ison.parse_from_str(code)
    
    /*
        [
          [ 1, 2, 3, 'a', 'b', 'c' ],
          [ 1, 2, 3, 'a', 'b', 'c' ],
          [ 1, 2, 3, 'a', 'b', 'c' ],
          [ 1, 2, 3, 'a', 'b', 'c' ]
        ]
    */

####  self-define


    //for example, I add a chinese period "。"  as a comma

    > ison.CFG.commas
    Set(2) { ',', ';' }
    >
    > ison.CFG.commas.add("。")
    > ison.CFG.commas
    Set(3) { ',', ';', '。' }
    >

    var code = `[i,you,he; yo,tú,Él; 我。你。他;]`

    var j = ison.parse_from_str(code)

    /*
       [
         'i',  'you', 'he',
         'yo', 'tú',  'Él',
         '我', '你',  '他'
       ]
    */


### colons

- colons is mandatory
- support mixed-style
- by default it is  ":" and  "="
- its configurable,must-be-one-char

####

    var code = `
        {a:b,c:d,e:f}
        {a=b,c=d,e=f}
        {a:b c=d,e=f; g:h}
    `
    

    var j = ison.parse_from_str(code)
    
    /*
       [
         { a: 'b', c: 'd', e: 'f' },
         { a: 'b', c: 'd', e: 'f' },
         { a: 'b', c: 'd', e: 'f', g: 'h' }
       ]
    */
  

#### self-define  

    ison.CFG.colons
    Set(2) { ':', '=' }

    ison.CFG.colons.add("|")
    Set(3) { ':', '=', '|' }


    var code = `
        { a|b,c|d,e|f }
    `
    
    var j = ison.parse_from_str(code)
    
    /*
        { a: 'b', c: 'd', e: 'f' }
    */    
    

####  Error auto recover

- colon before key will be treated as  whitespace
- multi-colons before value will be treated as  one
- colons following value  will be dropped
- unfinished key:value pair  will be dropped

#####

    //colon before key will be treated as  whitespace

    { a:b, : key : value}   ->  { a: 'b', key: 'value' }

    //multi-colons before value will be treated as  one

    { a:b, key ::::value }   ->  { a: 'b', key: 'value' }
    [100, ::::xy]            ->  [ 100, 'xy' ]

    //colons following value  will be dropped

    [abc::: 123]                   ->  [ 'abc', 123 ],
    {k:abc:::, k2:v2}              ->  { k: 'abc', k2: 'v2' }
    [abc : 666]                    ->  [ 'abc', 666 ]
    {k:v, key:value  : 100:200}    ->  { '100': 200, k: 'v', key: 'value' }


    //unfinished k:v  will be dropped
    {k:abc:, k2,  k3:v3}    -> { k: 'abc', k3: 'v3' }
    {k:abc:, k2:, k3:v3}    -> { k: 'abc', k3: 'v3' }
    {k:abc:, k2 :,k3:v3}    -> { k: 'abc', k3: 'v3' }



### quotes

- quotes is optional if without escape
- support mixed-style
- by default it is  "'" and  '"'  and '`'
- its configurable,must-be-one-char, except '`', '`' is reservered for future-using
- currently NOT support string-literal-template
- auto concat

####
    

    > ison.CFG.quotes
    Set(3) { '"', "'", '`' }
    > ison.CFG.reserved
    [ '/', '*', '#', '&', '`' ]
    > ison.CFG.quotes.add("%")
    > ison.CFG.quotes
    Set(4) { '"', "'", '`', '%' }
    >

    var code = `
        [
            a"\t\v\t"b,
            "cde",
            fgh,
            'ijk',
            %lmnopq%,
            \`rst \n uvw \n xyz\`
        ]
    `

    var j = ison.parse_from_str(code)

    /*
        [ 'a\t\x0B\tb', 'cde', 'fgh', 'ijk', 'lmnopq', 'rst \n uvw \n xyz' ]
    */

#### unclosed quotes will be dropped

    { k0:v0, key : "val-\t-....        -> {k0:v0}
    if you want the lefted parted for continue, use  

        require("nvison-parse-internal") 


        D {
          ....
          lefted: {
            type: 3,
            data: { rslt: 'val-\t-....\n\n\n', lefted: '', state: 3, quote: '"' }
          },
          ....
        }


###  comments 

- two kind :  line-comment(//....\n)  and block-comment(/*.....*/)
- four position supported: @after\_key, @before\_val(properties), @after\_val, @before\_val(elements)


#### valid and invalid comment

    // VALID 
    {
        .....
        /*text-node-comment*/ 

        key /*key-comment*/   :  /* before-val-comment*/  value  /*after-val-comment*/ ,

        /*text-node-comment*/

        key1 : value1,

        /*text-node-comment*/
        .....
    }
    
    [
        .....
        /*text-node-comment*/
        element0   /*after-val-comment*/ ,
        /*text-node-comment*/ 
        ....
    ]
    //VALID

    //INVALID
    [  abc/*in-value-comment-not-supported*/def, ....]
        
        it will be treated as  [ abc, def ....]


    {  kkkk/*in-key-comment-not-supported*/ey : value, ...}

        it will be treadted as  { ey:value,....} ,
        the pre-part of kkkkey(kkkk)  will be dropped 

    //INVALID


####

    var code = `
        {
            "total_rows": 129  ,  //line comment here
        
            /*
                block comment here
            */
        
            "offset": 0,  //line comment here
        
            "rows": [
                {
                    /*text  comment*/
        
                    "id": /*@before_val comment*/  "change1_0.6995461115147918",
        
                     /*text  comment*/
        
                    "key"   : "change1_0.6995461115147918"  /*@after_val comment*/  ,
        
                    /*text  comment*/
        
                    "value" /*key comment*/  : {
                        "rev": "1-e240bae28c7bb3667f02760f6398d508"
                    },
        
                    /*text  comment*/
        
                    "doc"  /*key comment*/  : {
                        "_id": "change1_0.6995461115147918",
                        "_rev": "1-e240bae28c7bb3667f02760f6398d508",
                        "hello": 1
                    }
                }
            ]
        }
    `
    


    var j = ison.parse_from_str(code)
    
    /*
        {
          total_rows: 129,
          offset: 0,
          rows: [
            {
              id: 'change1_0.6995461115147918',
              key: 'change1_0.6995461115147918',
              value: [Object],
              doc: [Object]
            }
          ]
        }
    */


### array

- support mixed-style
- by default it is  "[]" and  '()'  and '<>'
- its configurable,must-be-one-char-pair 


#### 

    ison.CFG.array_blks
    > Map(3) { '[' => ']', '(' => ')', '<' => '>' }

    ison.CFG.array_blks.add("【","】")
    ison.CFG.array_blks.add("《","》")
    > Map(5) { '[' => ']', '(' => ')', '<' => '>', '【' => '】', '《' => '》' }


    var code = `
        defun last-state <rewindable>
        【
            let
            [
                (size <rewind-count rewindable>)
            ]
            [
                if<zerop size> <values nil nil>
                (
                    values
                    <aref 《rewind-store rewindable》 《1 - size》>
                    t
                )
            ]
        】

    `


    var j = ison.parse_from_str(code)
    /*
    [
      'defun',
      'last-state',
      [ 'rewindable' ],
      [ 'let', [ [Array] ], [ 'if', [Array], [Array], [Array] ] ]
    ]
    */



### dict(object)    

- support mixed-style
- by default it is  "{}" 
- its configurable,must-be-one-char-pair


####

    ison.CFG.obj_blks
    ison.CFG.obj_blks.add('^','$')
    
    var code = `
        {
            a:b;
            c=d;
            e= {
                f:[1 2 3],
                g= ^ key:g,value:200 $
            }
        }
    `
    


    var j = ison.parse_from_str(code)
    /*
    {
      "a": "b",
      "c": "d",
      "e": {
        "f": [
          1,
          2,
          3
        ],
        "g": {
          "key": "g",
          "value": 200
        }
      }
    }
    */


### hash and ref

- pass {enable\_ref:true}
- hash can add more-than-one "key" to a value, for reference
- ref will search  along the "scope" chain, recursively
- ref can NOT  ref "behind" to right-sibling and children and descendants
- coz ison originally is used for streaming parse, it did NOT known what will come
- hash and ref performance is BAD when the ison-file is too Big,such as 4M+


#### valid and invalid

    //valid 
    {  #root
       
       key0: {k0:v0, k1:&root}  //ref to ancestor
       
       key1: value1,

       key2: &key0             //ref to preceding-sibling 

    } //will  be  {key0: {k0:v0,k1:<Circular>}, key1:value1,key2:{k0:v0,k1:<Circular>}}


    {
        ....
        key0  :  value0 #hash-after-val ,

        key1 : &hash-after-val
        ....
    }   // will be  {key0:value0, key1:value0}



    /* very very long string */  #comment

    [ &comment, &comment ,&comment]   //valid to ref to text-node-position-comment

     




    //invalid

    {
        key0  #hash-after-key-will-be-treated-as-comment  :  value0
    }


    {
        key0  :  #hash-before-value-will-be-treated-as-comment value0
    }


    {
        key0: &fsib,            //ref to following-sibling have no effect
        key1: value1,
        key2: value2 #fsib,

        key3: {
            k30:&des,           //ref to descendant have no effect
            k31: [
               des0,
               des1,
               des2  #des
            ]
        }

    }

    {
        k :  /*before_val*/ #hash  v     //hash between colon and value have no effect  
    }

    


#### example 1

  
    
    var code = `
        {
            arr: [1,2,3,4]  #ary0 ,
            x: &ary0 ,
            y: {
                k0:abcdefghijklmnopqrstuvwxyz,
                k1:&ary0,
                k2:&x,             //ref to x, it will be reursively point to [1,2,3,4]
                k3:&k0,            //along the scope(ArrayBlock or ObjectBlock), it will be point to abcdefghijklmnopqrstuvwxyz
                k4:&0             //if the ref is a number, it will be the sibling-position, it will be point to abcdefghijklmnopqrstuvwxyz
            }
        }
    `
    


    var j = ison.parse_from_str(code,{enable_ref:true})

    /*
    {
      arr: [ 1, 2, 3, 4 ],
      x: [ 1, 2, 3, 4 ],
      y: {
        k0: 'abcdefghijklmnopqrstuvwxyz',
        k1: [ 1, 2, 3, 4 ],
        k2: [ 1, 2, 3, 4 ],
        k3: 'abcdefghijklmnopqrstuvwxyz',
        k4: 'abcdefghijklmnopqrstuvwxyz'
      }
    }
    */

    > j.x === j.arr
    true
    > j.y.k1 === j.arr
    true
    > j.y.k2 === j.arr
    true
    >

#### example 2

    
    var code = `
        //use hash '#' to set a alias for ref
        
        "https://127.0.0.1/img.jpg"    #jpg
        "https://127.0.0.1/img.png"    #png
        
        
        //multi-hash permitted : the below has two hash
        
        \`this is a very long
        very long string
        ...string\`                     #long-string   #so-long
        
        
        
        [
            // different hashes point to the same value
            &long-string;
            &so-long;
        
            /*html-like, use ref '&' to get-value-of  value-with-hash */
            {
                div@1: {
                    attribs:{style={}}
                } #div ,
        
                div@2: &div,
                div@3: &div,
                div@4: &div,
        
                img@1: &jpg,
                img@2: &png,
        
            } #body;
        
            /*repeat the body*/
        
            &body;
        
        ] #html
    `
    var j = ison.parse_from_str(code,{enable_ref:true})

    /*
        > j
        [
          'https://127.0.0.1/img.jpg',
          'https://127.0.0.1/img.png',
          'this is a very long\n        very long string\n        ...string',
          [
            'this is a very long\n        very long string\n        ...string',
            'this is a very long\n        very long string\n        ...string',
            {
              'div@1': [Object],
              'div@2': [Object],
              'div@3': [Object],
              'div@4': [Object],
              'img@1': 'https://127.0.0.1/img.jpg',
              'img@2': 'https://127.0.0.1/img.png'
            },
            {
              'div@1': [Object],
              'div@2': [Object],
              'div@3': [Object],
              'div@4': [Object],
              'img@1': 'https://127.0.0.1/img.jpg',
              'img@2': 'https://127.0.0.1/img.png'
            }
          ]
        ]
        >
    */

     console.log(JSON.stringify(j,null,2))

    /*
        [
          "https://127.0.0.1/img.jpg",
          "https://127.0.0.1/img.png",
          "this is a very long\n        very long string\n        ...string",
          [
            "this is a very long\n        very long string\n        ...string",
            "this is a very long\n        very long string\n        ...string",
            {
              "div@1": {
                "attribs": {
                  "style": {}
                }
              },
              "div@2": {
                "attribs": {
                  "style": {}
                }
              },
              "div@3": {
                "attribs": {
                  "style": {}
                }
              },
              "div@4": {
                "attribs": {
                  "style": {}
                }
              },
              "img@1": "https://127.0.0.1/img.jpg",
              "img@2": "https://127.0.0.1/img.png"
            },
            {
              "div@1": {
                "attribs": {
                  "style": {}
                }
              },
              "div@2": {
                "attribs": {
                  "style": {}
                }
              },
              "div@3": {
                "attribs": {
                  "style": {}
                }
              },
              "div@4": {
                "attribs": {
                  "style": {}
                }
              },
              "img@1": "https://127.0.0.1/img.jpg",
              "img@2": "https://127.0.0.1/img.png"
            }
          ]
        ]
    */


### generator

- in-post-dfs sequence 
- async\_gen only support from\_file

#### sync gen

    var code = `
        {
            a:[1,2,3],
            b:{k0:v0,k1:v1,k2:v2},
        }
    `

    var g = ison.gen_from_str(code)

    > Array.from(g)
    [
      [ Symbol(empty), 1 ],
      [ Symbol(empty), 2 ],
      [ Symbol(empty), 3 ],
      [ 'a', [ 1, 2, 3 ] ],
      [ 'k0', 'v0' ],
      [ 'k1', 'v1' ],
      [ 'k2', 'v2' ],
      [ 'b', { k0: 'v0', k1: 'v1', k2: 'v2' } ],
      [ Symbol(empty), { a: [Array], b: [Object] } ]
    ]
    
    
### async
    

    var ag = ison.agen_from_file("./gen.ison");
    (
        async() => {
            for await(let entry of ag) {
                console.log(entry)
            }
        }
    )();
    /*
        [ Symbol(empty), 1 ]
        [ Symbol(empty), 2 ]
        [ 'k0', 'v0' ]
        [ 'k1', 'v1' ]
        [
          Symbol(empty),
          { a: [ 1, 2, 3 ], b: { k0: 'v0', k1: 'v1', k2: 'v2' } }
        ]
        [ Symbol(empty), [ { a: [Array], b: [Object] } ] ]
    */         





CFG
===

    > ison.CFG
    [
      ‍ [fixed] {
        hash: '#',
        ref: '&',
        tmpl_quote: '`',
        slash: '/',
        asterisk: '*',
        line_comment: '//',
        blk_comments: [ '/*', '*/' ]
      },
      ‌ [configurable-if-not-reserved] {
        obj_blks: Map(1) { '{' => '}' },
        array_blks: Map(3) { '[' => ']', '(' => ')', '<' => '>' },
        quotes: Set(3) { '"', "'", '`' },
        commas: Set(2) { ',', ';' },
        colons: Set(2) { ':', '=' }
      },
      ‍ [reserved] { reserved: [ '/', '*', '#', '&', '`' ] }
    ]
    >

    > ison.OPT_DICT
    { enable_ref: false, encoding: 'utf8' }
    >



APIS
====

parse
-----

- function parse\_from\_generator(g,opt={enable\_ref:false})
- function parse\_from\_str(s,opt={enable\_ref:false})
- function parse\_from\_file(fn,opt={enable\_ref:false,encoding:'utf8'})
- function * gen\_from\_generator(g,opt={enable\_ref:false,encoding:'utf8'})
- function * gen\_from\_str(s,opt={enable\_ref:false,encoding:'utf8'})
- function * gen\_from\_file(fn,opt={enable\_ref:false,encoding:'utf8'})
- async function * agen\_from\_generator(ag,opt={enable\_ref:false,encoding:'utf8'})
- async function * agen\_from\_file(fn,opt={enable\_ref:false,encoding:'utf8'})



CLI
===

parse
-----
    
    npm install nvison -g 


### plain without color

#### with input-file


          Usage: nvison_parse [options] <file>
          If <file> is not provided, then STDIN is used.
          Options:
            -c, --config [file]      config default see below
            -e, --encoding           default utf8
            -o, --output [file]      Output to the specified file, otherwise STDOUT
            -h, --help               Output usage information


        ---default config---
        {
          quotes: [ '"', "'", '`', '·' ],
          colons: [ ':', '=', '：' ],
          commas: [ ',', ';', '，', '；', '。', '、' ],
          array_blks: [
            [ '[', ']' ],
            [ '(', ')' ],
            [ '<', '>' ],
            [ '【', '】' ],
            [ '（', '）' ],
            [ '‘', '’' ],
            [ '“', '”' ]
          ],
          obj_blks: [ [ '{', '}' ], [ '《', '》' ] ]
        }
        ---default config---
       
       
#### using STDIN

    nvison_plain_parse
    
    {
        a:b;
        c=d;
        e= {
            f:[1 2 3],
            g= { key:g,value:200 }
        }
    }
    
    
    # ctrl+D   press ctrl+D <===============
    
    {
        "a": "b",
        "c": "d",
        "e": {
            "f": [
                1,
                2,
                3
            ],
            "g": {
                "key": "g",
                "value": 200
            }
        }
    }


TODO
====
- stringify with self-defined color
- coz the color file is too big, currently NOT expose the API ,soon 



IS IT FAST?
===========
- NO
- its suitable for config-like or html-like file 
- if file large than 4M  its slow 
- coz it has a internal structure ,and the parser parse the string-stream char-by-char
- for error tolerance and configurable operators



RESTRICT
========
- BigFloat NOT supported
- coz currently only QuickJS provide that API .

LICENSE
=======
- ISC 
