]> granicus.if.org Git - vim/commitdiff
patch 7.4.1279 v7.4.1279
authorBram Moolenaar <Bram@vim.org>
Sun, 7 Feb 2016 18:19:53 +0000 (19:19 +0100)
committerBram Moolenaar <Bram@vim.org>
Sun, 7 Feb 2016 18:19:53 +0000 (19:19 +0100)
Problem:    jsonencode() is not producing strict JSON.
Solution:   Add jsencode() and jsdecode().  Make jsonencode() and jsondecode()
            strict.

12 files changed:
runtime/doc/channel.txt
runtime/doc/eval.txt
src/channel.c
src/eval.c
src/json.c
src/json_test.c
src/proto/channel.pro
src/proto/json.pro
src/structs.h
src/testdir/test_json.vim
src/version.c
src/vim.h

index 855fda378a7e42c0c2aaf91c9b4cd9fe95bbf4a4..bc9e6414497bbe6ffbbabc079f7f31faeac23245 100644 (file)
@@ -1,4 +1,4 @@
-*channel.txt*      For Vim version 7.4.  Last change: 2016 Feb 06
+*channel.txt*      For Vim version 7.4.  Last change: 2016 Feb 07
 
 
                  VIM REFERENCE MANUAL    by Bram Moolenaar
@@ -16,7 +16,7 @@ The Netbeans interface also uses a channel. |netbeans|
 
 1. Demo                                        |channel-demo|
 2. Opening a channel                   |channel-open|
-3. Using a JSON channel                        |channel-use|
+3. Using a JSON or JS channel          |channel-use|
 4. Vim commands                                |channel-commands|
 5. Using a raw channel                 |channel-use|
 6. Job control                         |job-control|
@@ -77,6 +77,7 @@ To open a channel: >
 
 "mode" can be:                                         *channel-mode*
        "json" - Use JSON, see below; most convenient way. Default.
+       "js"   - Use JavaScript encoding, more efficient than JSON.
        "raw"  - Use raw messages
 
                                                        *channel-callback*
@@ -86,7 +87,7 @@ message. Example: >
        func Handle(handle, msg)
          echo 'Received: ' . a:msg
        endfunc
-       let handle = ch_open("localhost:8765", 'json', "Handle")
+       let handle = ch_open("localhost:8765", {"callback": "Handle"})
 
 "waittime" is the time to wait for the connection to be made in milliseconds.
 The default is zero, don't wait, which is useful if the server is supposed to
@@ -95,12 +96,12 @@ be running already.  A negative number waits forever.
 "timeout" is the time to wait for a request when blocking, using
 ch_sendexpr().  Again in milliseconds.  The default is 2000 (2 seconds).
 
-When "mode" is "json" the "msg" argument is the body of the received message,
-converted to Vim types.
+When "mode" is "json" or "js" the "msg" argument is the body of the received
+message, converted to Vim types.
 When "mode" is "raw" the "msg" argument is the whole message as a string.
 
-When "mode" is "json" the "callback" is optional.  When omitted it is only
-possible to receive a message after sending one.
+When "mode" is "json" or "js" the "callback" is optional.  When omitted it is
+only possible to receive a message after sending one.
 
 The handler can be added or changed later: >
     call ch_setcallback(handle, {callback})
@@ -123,12 +124,15 @@ If there is an error reading or writing a channel it will be closed.
 *E896* *E630* *E631* 
 
 ==============================================================================
-3. Using a JSON channel                                        *channel-use*
+3. Using a JSON or JS channel                                  *channel-use*
 
 If {mode} is "json" then a message can be sent synchronously like this: >
     let response = ch_sendexpr(handle, {expr})
 This awaits a response from the other side.
 
+When {mode} is "js" this works the same, except that the messages use
+JavaScript encoding.  See |jsencode()| for the difference.
+
 To send a message, without handling a response: >
     call ch_sendexpr(handle, {expr}, 0)
 
@@ -231,7 +235,8 @@ Here {number} is the same as what was in the request.  Use a negative number
 to avoid confusion with message that Vim sends.
 
 {result} is the result of the evaluation and is JSON encoded.  If the
-evaluation fails it is the string "ERROR".
+evaluation fails or the result can't be encoded in JSON it is the string
+"ERROR".
 
 
 Command "expr" ~
index b476ef0f05efb0c854c716b3679ac71c3bbf3f80..1f009cc7b9955f9cb70508e399da5d9b57b2d25d 100644 (file)
@@ -1956,6 +1956,8 @@ job_start({command} [, {options}]) Job    start a job
 job_status({job})              String  get the status of a job
 job_stop({job} [, {how}])      Number  stop a job
 join( {list} [, {sep}])                String  join {list} items into one String
+jsdecode( {string})            any     decode JS style JSON
+jsencode( {expr})              String  encode JS style JSON
 jsondecode( {string})          any     decode JSON
 jsonencode( {expr})            String  encode JSON
 keys( {dict})                  List    keys in {dict}
@@ -2439,7 +2441,6 @@ bufwinnr({expr})                                  *bufwinnr()*
                |:wincmd|.
                Only deals with the current tab page.
 
-
 byte2line({byte})                                      *byte2line()*
                Return the line number that contains the character at byte
                count {byte} in the current buffer.  This includes the
@@ -2688,7 +2689,7 @@ ch_open({address} [, {argdict}])                          *ch_open()*
 
                If {argdict} is given it must be a |Dictionary|.  The optional
                items are:
-                       mode        "raw" or "json".
+                       mode        "raw", "js" or "json".
                                    Default "json".
                        callback    function to call for requests with a zero
                                    sequence number.  See |channel-callback|.
@@ -4381,17 +4382,33 @@ join({list} [, {sep}])                                  *join()*
                converted into a string like with |string()|.
                The opposite function is |split()|.
 
+jsdecode({string})                                     *jsdecode()*
+               This is similar to |jsondecode()| with these differences:
+               - Object key names do not have to be in quotes.
+               - Empty items in an array (between two commas) are allowed and
+                 result in v:none items.
+
+jsencode({expr})                                       *jsencode()*
+               This is similar to |jsonencode()| with these differences:
+               - Object key names are not in quotes.
+               - v:none items in an array result in an empty item between
+                 commas.
+               For example, the Vim object:
+                       [1,v:none,{"one":1}],v:none ~
+               Will be encoded as:
+                       [1,,{one:1},,] ~
+               While jsonencode() would produce:
+                       [1,null,{"one":1},null] ~
+               This encoding is valid for JavaScript. It is more efficient
+               than JSON, especially when using an array with optional items.
+
+
 jsondecode({string})                                   *jsondecode()*
                This parses a JSON formatted string and returns the equivalent
                in Vim values.  See |jsonencode()| for the relation between
                JSON and Vim values.
                The decoding is permissive:
                - A trailing comma in an array and object is ignored.
-               - An empty item in an array, two commas with nothing or white
-                 space in between, results in v:none.
-               - When an object member name is not a string it is converted
-                 to a string.  E.g. the number 123 is used as the string
-                 "123".
                - More floating point numbers are recognized, e.g. "1." for
                  "1.0".
                The result must be a valid Vim type:
@@ -4413,7 +4430,7 @@ jsonencode({expr})                                        *jsonencode()*
                                        used recursively: {}
                   v:false              "false"
                   v:true               "true"
-                  v:none               nothing
+                  v:none               "null"
                   v:null               "null"
                Note that using v:none is permitted, although the JSON
                standard does not allow empty items.  This can be useful for
index a6458c65ed36b520ad5a07effc8d4ab4c8e88ae2..1d12ee72240d940df346e8351d357a2e06aa2b1f 100644 (file)
@@ -119,7 +119,7 @@ typedef struct {
     char_u    *ch_callback;    /* function to call when a msg is not handled */
     cbq_T     ch_cb_head;      /* dummy node for pre-request callbacks */
 
-    int              ch_json_mode;     /* TRUE for a json channel */
+    ch_mode_T ch_mode;
     jsonq_T   ch_json_head;    /* dummy node, header for circular queue */
 
     int       ch_timeout;      /* request timeout in msec */
@@ -526,12 +526,12 @@ channel_open(char *hostname, int port_in, int waittime, void (*close_cb)(void))
 }
 
 /*
- * Set the json mode of channel "idx" to TRUE or FALSE.
+ * Set the json mode of channel "idx" to "ch_mode".
  */
     void
-channel_set_json_mode(int idx, int json_mode)
+channel_set_json_mode(int idx, ch_mode_T ch_mode)
 {
-    channels[idx].ch_json_mode = json_mode;
+    channels[idx].ch_mode = ch_mode;
 }
 
 /*
@@ -672,7 +672,8 @@ channel_parse_json(int ch_idx)
     js_read_T  reader;
     typval_T   listtv;
     jsonq_T    *item;
-    jsonq_T    *head = &channels[ch_idx].ch_json_head;
+    channel_T  *channel = &channels[ch_idx];
+    jsonq_T    *head = &channel->ch_json_head;
     int                ret;
 
     if (channel_peek(ch_idx) == NULL)
@@ -685,7 +686,8 @@ channel_parse_json(int ch_idx)
     reader.js_fill = NULL;
     /* reader.js_fill = channel_fill; */
     reader.js_cookie = &ch_idx;
-    ret = json_decode(&reader, &listtv);
+    ret = json_decode(&reader, &listtv,
+                                  channel->ch_mode == MODE_JS ? JSON_JS : 0);
     if (ret == OK)
     {
        /* Only accept the response when it is a list with at least two
@@ -854,6 +856,8 @@ channel_exe_cmd(int idx, char_u *cmd, typval_T *arg2, typval_T *arg3)
            typval_T    *tv;
            typval_T    err_tv;
            char_u      *json = NULL;
+           channel_T   *channel = &channels[idx];
+           int         options = channel->ch_mode == MODE_JS ? JSON_JS : 0;
 
            /* Don't pollute the display with errors. */
            ++emsg_skip;
@@ -861,7 +865,8 @@ channel_exe_cmd(int idx, char_u *cmd, typval_T *arg2, typval_T *arg3)
            if (is_eval)
            {
                if (tv != NULL)
-                   json = json_encode_nr_expr(arg3->vval.v_number, tv);
+                   json = json_encode_nr_expr(arg3->vval.v_number, tv,
+                                                                    options);
                if (tv == NULL || (json != NULL && *json == NUL))
                {
                    /* If evaluation failed or the result can't be encoded
@@ -869,7 +874,8 @@ channel_exe_cmd(int idx, char_u *cmd, typval_T *arg2, typval_T *arg3)
                    err_tv.v_type = VAR_STRING;
                    err_tv.vval.v_string = (char_u *)"ERROR";
                    tv = &err_tv;
-                   json = json_encode_nr_expr(arg3->vval.v_number, tv);
+                   json = json_encode_nr_expr(arg3->vval.v_number, tv,
+                                                                    options);
                }
                if (json != NULL)
                {
@@ -900,13 +906,13 @@ may_invoke_callback(int idx)
     typval_T   argv[3];
     int                seq_nr = -1;
     channel_T  *channel = &channels[idx];
-    int                json_mode = channel->ch_json_mode;
+    ch_mode_T  ch_mode = channel->ch_mode;
 
     if (channel->ch_close_cb != NULL)
        /* this channel is handled elsewhere (netbeans) */
        return FALSE;
 
-    if (json_mode)
+    if (ch_mode != MODE_RAW)
     {
        /* Get any json message in the queue. */
        if (channel_get_json(idx, -1, &listtv) == FAIL)
index bd0040e66a2fd30314c7e811cb19bd3263636bfe..4b1250b593c82ea85575e2729f04e7e5fa36bfdc 100644 (file)
@@ -628,6 +628,8 @@ static void f_job_stop(typval_T *argvars, typval_T *rettv);
 static void f_job_status(typval_T *argvars, typval_T *rettv);
 #endif
 static void f_join(typval_T *argvars, typval_T *rettv);
+static void f_jsdecode(typval_T *argvars, typval_T *rettv);
+static void f_jsencode(typval_T *argvars, typval_T *rettv);
 static void f_jsondecode(typval_T *argvars, typval_T *rettv);
 static void f_jsonencode(typval_T *argvars, typval_T *rettv);
 static void f_keys(typval_T *argvars, typval_T *rettv);
@@ -8206,6 +8208,8 @@ static struct fst
     {"job_stop",       1, 1, f_job_stop},
 #endif
     {"join",           1, 2, f_join},
+    {"jsdecode",       1, 1, f_jsdecode},
+    {"jsencode",       1, 1, f_jsencode},
     {"jsondecode",     1, 1, f_jsondecode},
     {"jsonencode",     1, 1, f_jsonencode},
     {"keys",           1, 1, f_keys},
@@ -9829,7 +9833,7 @@ f_ch_open(typval_T *argvars, typval_T *rettv)
     int                port;
     int                waittime = 0;
     int                timeout = 2000;
-    int                json_mode = TRUE;
+    ch_mode_T  ch_mode = MODE_JSON;
     int                ch_idx;
 
     /* default: fail */
@@ -9868,8 +9872,12 @@ f_ch_open(typval_T *argvars, typval_T *rettv)
        {
            mode = get_dict_string(dict, (char_u *)"mode", FALSE);
            if (STRCMP(mode, "raw") == 0)
-               json_mode = FALSE;
-           else if (STRCMP(mode, "json") != 0)
+               ch_mode = MODE_RAW;
+           else if (STRCMP(mode, "js") == 0)
+               ch_mode = MODE_JS;
+           else if (STRCMP(mode, "json") == 0)
+               ch_mode = MODE_JSON;
+           else
            {
                EMSG2(_(e_invarg2), mode);
                return;
@@ -9891,7 +9899,7 @@ f_ch_open(typval_T *argvars, typval_T *rettv)
     ch_idx = channel_open((char *)address, port, waittime, NULL);
     if (ch_idx >= 0)
     {
-       channel_set_json_mode(ch_idx, json_mode);
+       channel_set_json_mode(ch_idx, ch_mode);
        channel_set_timeout(ch_idx, timeout);
        if (callback != NULL && *callback != NUL)
            channel_set_callback(ch_idx, callback);
@@ -9946,7 +9954,7 @@ f_ch_sendexpr(typval_T *argvars, typval_T *rettv)
     rettv->vval.v_string = NULL;
 
     id = channel_get_id();
-    text = json_encode_nr_expr(id, &argvars[1]);
+    text = json_encode_nr_expr(id, &argvars[1], 0);
     if (text == NULL)
        return;
 
@@ -14442,6 +14450,31 @@ f_join(typval_T *argvars, typval_T *rettv)
        rettv->vval.v_string = NULL;
 }
 
+/*
+ * "jsdecode()" function
+ */
+    static void
+f_jsdecode(typval_T *argvars, typval_T *rettv)
+{
+    js_read_T  reader;
+
+    reader.js_buf = get_tv_string(&argvars[0]);
+    reader.js_fill = NULL;
+    reader.js_used = 0;
+    if (json_decode_all(&reader, rettv, JSON_JS) != OK)
+       EMSG(_(e_invarg));
+}
+
+/*
+ * "jsencode()" function
+ */
+    static void
+f_jsencode(typval_T *argvars, typval_T *rettv)
+{
+    rettv->v_type = VAR_STRING;
+    rettv->vval.v_string = json_encode(&argvars[0], JSON_JS);
+}
+
 /*
  * "jsondecode()" function
  */
@@ -14453,7 +14486,7 @@ f_jsondecode(typval_T *argvars, typval_T *rettv)
     reader.js_buf = get_tv_string(&argvars[0]);
     reader.js_fill = NULL;
     reader.js_used = 0;
-    if (json_decode_all(&reader, rettv) != OK)
+    if (json_decode_all(&reader, rettv, 0) != OK)
        EMSG(_(e_invarg));
 }
 
@@ -14464,7 +14497,7 @@ f_jsondecode(typval_T *argvars, typval_T *rettv)
 f_jsonencode(typval_T *argvars, typval_T *rettv)
 {
     rettv->v_type = VAR_STRING;
-    rettv->vval.v_string = json_encode(&argvars[0]);
+    rettv->vval.v_string = json_encode(&argvars[0], 0);
 }
 
 /*
index 17eed4fa1094ab8d5d1cbd78e42778dee8b2472e..31bac26d5fe41cf3cbb44af6062ff55f392ac310 100644 (file)
 #include "vim.h"
 
 #if defined(FEAT_EVAL) || defined(PROTO)
-static int json_encode_item(garray_T *gap, typval_T *val, int copyID, int allow_none);
-static int json_decode_item(js_read_T *reader, typval_T *res);
+static int json_encode_item(garray_T *gap, typval_T *val, int copyID, int options);
+static int json_decode_item(js_read_T *reader, typval_T *res, int options);
 
 /*
  * Encode "val" into a JSON format string.
  * The result is in allocated memory.
  * The result is empty when encoding fails.
+ * "options" can be JSON_JS or zero;
  */
     char_u *
-json_encode(typval_T *val)
+json_encode(typval_T *val, int options)
 {
     garray_T ga;
 
     /* Store bytes in the growarray. */
     ga_init2(&ga, 1, 4000);
-    if (json_encode_item(&ga, val, get_copyID(), TRUE) == FAIL)
+    if (json_encode_item(&ga, val, get_copyID(), options) == FAIL)
     {
        vim_free(ga.ga_data);
        return vim_strsave((char_u *)"");
@@ -41,10 +42,11 @@ json_encode(typval_T *val)
 
 /*
  * Encode ["nr", "val"] into a JSON format string in allocated memory.
+ * "options" can be JSON_JS or zero;
  * Returns NULL when out of memory.
  */
     char_u *
-json_encode_nr_expr(int nr, typval_T *val)
+json_encode_nr_expr(int nr, typval_T *val, int options)
 {
     typval_T   listtv;
     typval_T   nrtv;
@@ -61,7 +63,7 @@ json_encode_nr_expr(int nr, typval_T *val)
        return NULL;
     }
 
-    text = json_encode(&listtv);
+    text = json_encode(&listtv, options);
     list_unref(listtv.vval.v_list);
     return text;
 }
@@ -122,12 +124,30 @@ write_string(garray_T *gap, char_u *str)
     }
 }
 
+/*
+ * Return TRUE if "key" can be used without quotes.
+ * That is when it starts with a letter and only contains letters, digits and
+ * underscore.
+ */
+    static int
+is_simple_key(char_u *key)
+{
+    char_u *p;
+
+    if (!ASCII_ISALPHA(*key))
+       return FALSE;
+    for (p = key + 1; *p != NUL; ++p)
+       if (!ASCII_ISALPHA(*p) && *p != '_' && !vim_isdigit(*p))
+           return FALSE;
+    return TRUE;
+}
+
 /*
  * Encode "val" into "gap".
  * Return FAIL or OK.
  */
     static int
-json_encode_item(garray_T *gap, typval_T *val, int copyID, int allow_none)
+json_encode_item(garray_T *gap, typval_T *val, int copyID, int options)
 {
     char_u     numbuf[NUMBUFLEN];
     char_u     *res;
@@ -141,13 +161,11 @@ json_encode_item(garray_T *gap, typval_T *val, int copyID, int allow_none)
            {
                case VVAL_FALSE: ga_concat(gap, (char_u *)"false"); break;
                case VVAL_TRUE: ga_concat(gap, (char_u *)"true"); break;
-               case VVAL_NONE: if (!allow_none)
-                               {
-                                   /* TODO: better error */
-                                   EMSG(_(e_invarg));
-                                   return FAIL;
-                               }
-                               break;
+               case VVAL_NONE: if ((options & JSON_JS) != 0
+                                            && (options & JSON_NO_NONE) == 0)
+                                   /* empty item */
+                                   break;
+                               /* FALLTHROUGH */
                case VVAL_NULL: ga_concat(gap, (char_u *)"null"); break;
            }
            break;
@@ -185,9 +203,15 @@ json_encode_item(garray_T *gap, typval_T *val, int copyID, int allow_none)
                    ga_append(gap, '[');
                    for (li = l->lv_first; li != NULL && !got_int; )
                    {
-                       if (json_encode_item(gap, &li->li_tv, copyID, TRUE)
-                                                                     == FAIL)
+                       if (json_encode_item(gap, &li->li_tv, copyID,
+                                                  options & JSON_JS) == FAIL)
                            return FAIL;
+                       if ((options & JSON_JS)
+                               && li->li_next == NULL
+                               && li->li_tv.v_type == VAR_SPECIAL
+                               && li->li_tv.vval.v_number == VVAL_NONE)
+                           /* add an extra comma if the last item is v:none */
+                           ga_append(gap, ',');
                        li = li->li_next;
                        if (li != NULL)
                            ga_append(gap, ',');
@@ -224,10 +248,14 @@ json_encode_item(garray_T *gap, typval_T *val, int copyID, int allow_none)
                                first = FALSE;
                            else
                                ga_append(gap, ',');
-                           write_string(gap, hi->hi_key);
+                           if ((options & JSON_JS)
+                                                && is_simple_key(hi->hi_key))
+                               ga_concat(gap, hi->hi_key);
+                           else
+                               write_string(gap, hi->hi_key);
                            ga_append(gap, ':');
                            if (json_encode_item(gap, &dict_lookup(hi)->di_tv,
-                                                      copyID, FALSE) == FAIL)
+                                     copyID, options | JSON_NO_NONE) == FAIL)
                                return FAIL;
                        }
                    ga_append(gap, '}');
@@ -265,7 +293,8 @@ fill_numbuflen(js_read_T *reader)
 }
 
 /*
- * Skip white space in "reader".
+ * Skip white space in "reader".  All characters <= space are considered white
+ * space.
  * Also tops up readahead when needed.
  */
     static void
@@ -282,7 +311,7 @@ json_skip_white(js_read_T *reader)
                reader->js_end = reader->js_buf + STRLEN(reader->js_buf);
            continue;
        }
-       if (c != ' ' && c != TAB && c != NL && c != CAR)
+       if (c == NUL || c > ' ')
            break;
        ++reader->js_used;
     }
@@ -290,7 +319,7 @@ json_skip_white(js_read_T *reader)
 }
 
     static int
-json_decode_array(js_read_T *reader, typval_T *res)
+json_decode_array(js_read_T *reader, typval_T *res, int options)
 {
     char_u     *p;
     typval_T   item;
@@ -317,7 +346,7 @@ json_decode_array(js_read_T *reader, typval_T *res)
            break;
        }
 
-       ret = json_decode_item(reader, res == NULL ? NULL : &item);
+       ret = json_decode_item(reader, res == NULL ? NULL : &item, options);
        if (ret != OK)
            return ret;
        if (res != NULL)
@@ -347,7 +376,7 @@ json_decode_array(js_read_T *reader, typval_T *res)
 }
 
     static int
-json_decode_object(js_read_T *reader, typval_T *res)
+json_decode_object(js_read_T *reader, typval_T *res, int options)
 {
     char_u     *p;
     typval_T   tvkey;
@@ -377,16 +406,31 @@ json_decode_object(js_read_T *reader, typval_T *res)
            break;
        }
 
-       ret = json_decode_item(reader, res == NULL ? NULL : &tvkey);
-       if (ret != OK)
-           return ret;
-       if (res != NULL)
+       if ((options & JSON_JS) && reader->js_buf[reader->js_used] != '"')
        {
-           key = get_tv_string_buf_chk(&tvkey, buf);
-           if (key == NULL || *key == NUL)
+           /* accept a key that is not in quotes */
+           key = p = reader->js_buf + reader->js_used;
+           while (*p != NUL && *p != ':' && *p > ' ')
+               ++p;
+           tvkey.v_type = VAR_STRING;
+           tvkey.vval.v_string = vim_strnsave(key, (int)(p - key));
+           reader->js_used += (int)(p - key);
+           key = tvkey.vval.v_string;
+       }
+       else
+       {
+           ret = json_decode_item(reader, res == NULL ? NULL : &tvkey,
+                                                                    options);
+           if (ret != OK)
+               return ret;
+           if (res != NULL)
            {
-               clear_tv(&tvkey);
-               return FAIL;
+               key = get_tv_string_buf_chk(&tvkey, buf);
+               if (key == NULL || *key == NUL)
+               {
+                   clear_tv(&tvkey);
+                   return FAIL;
+               }
            }
        }
 
@@ -403,7 +447,7 @@ json_decode_object(js_read_T *reader, typval_T *res)
        ++reader->js_used;
        json_skip_white(reader);
 
-       ret = json_decode_item(reader, res == NULL ? NULL : &item);
+       ret = json_decode_item(reader, res == NULL ? NULL : &item, options);
        if (ret != OK)
        {
            if (res != NULL)
@@ -569,7 +613,7 @@ json_decode_string(js_read_T *reader, typval_T *res)
  * Return MAYBE for an incomplete message.
  */
     static int
-json_decode_item(js_read_T *reader, typval_T *res)
+json_decode_item(js_read_T *reader, typval_T *res, int options)
 {
     char_u     *p;
     int                len;
@@ -579,15 +623,18 @@ json_decode_item(js_read_T *reader, typval_T *res)
     switch (*p)
     {
        case '[': /* array */
-           return json_decode_array(reader, res);
+           return json_decode_array(reader, res, options);
 
        case '{': /* object */
-           return json_decode_object(reader, res);
+           return json_decode_object(reader, res, options);
 
        case '"': /* string */
            return json_decode_string(reader, res);
 
        case ',': /* comma: empty item */
+           if ((options & JSON_JS) == 0)
+               return FAIL;
+           /* FALLTHROUGH */
        case NUL: /* empty */
            if (res != NULL)
            {
@@ -691,17 +738,18 @@ json_decode_item(js_read_T *reader, typval_T *res)
 
 /*
  * Decode the JSON from "reader" and store the result in "res".
+ * "options" can be JSON_JS or zero;
  * Return FAIL if not the whole message was consumed.
  */
     int
-json_decode_all(js_read_T *reader, typval_T *res)
+json_decode_all(js_read_T *reader, typval_T *res, int options)
 {
     int ret;
 
-    /* We get the end once, to avoid calling strlen() many times. */
+    /* We find the end once, to avoid calling strlen() many times. */
     reader->js_end = reader->js_buf + STRLEN(reader->js_buf);
     json_skip_white(reader);
-    ret = json_decode_item(reader, res);
+    ret = json_decode_item(reader, res, options);
     if (ret != OK)
        return FAIL;
     json_skip_white(reader);
@@ -712,18 +760,19 @@ json_decode_all(js_read_T *reader, typval_T *res)
 
 /*
  * Decode the JSON from "reader" and store the result in "res".
+ * "options" can be JSON_JS or zero;
  * Return FAIL if the message has a decoding error or the message is
  * truncated.  Consumes the message anyway.
  */
     int
-json_decode(js_read_T *reader, typval_T *res)
+json_decode(js_read_T *reader, typval_T *res, int options)
 {
     int ret;
 
-    /* We get the end once, to avoid calling strlen() many times. */
+    /* We find the end once, to avoid calling strlen() many times. */
     reader->js_end = reader->js_buf + STRLEN(reader->js_buf);
     json_skip_white(reader);
-    ret = json_decode_item(reader, res);
+    ret = json_decode_item(reader, res, options);
     json_skip_white(reader);
 
     return ret == OK ? OK : FAIL;
@@ -731,6 +780,7 @@ json_decode(js_read_T *reader, typval_T *res)
 
 /*
  * Decode the JSON from "reader" to find the end of the message.
+ * "options" can be JSON_JS or zero;
  * Return FAIL if the message has a decoding error.
  * Return MAYBE if the message is truncated, need to read more.
  * This only works reliable if the message contains an object, array or
@@ -738,15 +788,15 @@ json_decode(js_read_T *reader, typval_T *res)
  * Does not advance the reader.
  */
     int
-json_find_end(js_read_T *reader)
+json_find_end(js_read_T *reader, int options)
 {
     int used_save = reader->js_used;
     int ret;
 
-    /* We get the end once, to avoid calling strlen() many times. */
+    /* We find the end once, to avoid calling strlen() many times. */
     reader->js_end = reader->js_buf + STRLEN(reader->js_buf);
     json_skip_white(reader);
-    ret = json_decode_item(reader, NULL);
+    ret = json_decode_item(reader, NULL, options);
     reader->js_used = used_save;
     return ret;
 }
index f50c95608b34f4e74907668ea3a7b788187af2d0..2a28c74059157d86e0f3ade6448f55c7695da55e 100644 (file)
@@ -35,107 +35,107 @@ test_decode_find_end(void)
 
     /* string and incomplete string */
     reader.js_buf = (char_u *)"\"hello\"";
-    assert(json_find_end(&reader) == OK);
+    assert(json_find_end(&reader, 0) == OK);
     reader.js_buf = (char_u *)"  \"hello\" ";
-    assert(json_find_end(&reader) == OK);
+    assert(json_find_end(&reader, 0) == OK);
     reader.js_buf = (char_u *)"\"hello";
-    assert(json_find_end(&reader) == MAYBE);
+    assert(json_find_end(&reader, 0) == MAYBE);
 
     /* number and dash (incomplete number) */
     reader.js_buf = (char_u *)"123";
-    assert(json_find_end(&reader) == OK);
+    assert(json_find_end(&reader, 0) == OK);
     reader.js_buf = (char_u *)"-";
-    assert(json_find_end(&reader) == MAYBE);
+    assert(json_find_end(&reader, 0) == MAYBE);
 
     /* false, true and null, also incomplete */
     reader.js_buf = (char_u *)"false";
-    assert(json_find_end(&reader) == OK);
+    assert(json_find_end(&reader, 0) == OK);
     reader.js_buf = (char_u *)"f";
-    assert(json_find_end(&reader) == MAYBE);
+    assert(json_find_end(&reader, 0) == MAYBE);
     reader.js_buf = (char_u *)"fa";
-    assert(json_find_end(&reader) == MAYBE);
+    assert(json_find_end(&reader, 0) == MAYBE);
     reader.js_buf = (char_u *)"fal";
-    assert(json_find_end(&reader) == MAYBE);
+    assert(json_find_end(&reader, 0) == MAYBE);
     reader.js_buf = (char_u *)"fals";
-    assert(json_find_end(&reader) == MAYBE);
+    assert(json_find_end(&reader, 0) == MAYBE);
 
     reader.js_buf = (char_u *)"true";
-    assert(json_find_end(&reader) == OK);
+    assert(json_find_end(&reader, 0) == OK);
     reader.js_buf = (char_u *)"t";
-    assert(json_find_end(&reader) == MAYBE);
+    assert(json_find_end(&reader, 0) == MAYBE);
     reader.js_buf = (char_u *)"tr";
-    assert(json_find_end(&reader) == MAYBE);
+    assert(json_find_end(&reader, 0) == MAYBE);
     reader.js_buf = (char_u *)"tru";
-    assert(json_find_end(&reader) == MAYBE);
+    assert(json_find_end(&reader, 0) == MAYBE);
 
     reader.js_buf = (char_u *)"null";
-    assert(json_find_end(&reader) == OK);
+    assert(json_find_end(&reader, 0) == OK);
     reader.js_buf = (char_u *)"n";
-    assert(json_find_end(&reader) == MAYBE);
+    assert(json_find_end(&reader, 0) == MAYBE);
     reader.js_buf = (char_u *)"nu";
-    assert(json_find_end(&reader) == MAYBE);
+    assert(json_find_end(&reader, 0) == MAYBE);
     reader.js_buf = (char_u *)"nul";
-    assert(json_find_end(&reader) == MAYBE);
+    assert(json_find_end(&reader, 0) == MAYBE);
 
     /* object without white space */
     reader.js_buf = (char_u *)"{\"a\":123}";
-    assert(json_find_end(&reader) == OK);
+    assert(json_find_end(&reader, 0) == OK);
     reader.js_buf = (char_u *)"{\"a\":123";
-    assert(json_find_end(&reader) == MAYBE);
+    assert(json_find_end(&reader, 0) == MAYBE);
     reader.js_buf = (char_u *)"{\"a\":";
-    assert(json_find_end(&reader) == MAYBE);
+    assert(json_find_end(&reader, 0) == MAYBE);
     reader.js_buf = (char_u *)"{\"a\"";
-    assert(json_find_end(&reader) == MAYBE);
+    assert(json_find_end(&reader, 0) == MAYBE);
     reader.js_buf = (char_u *)"{\"a";
-    assert(json_find_end(&reader) == MAYBE);
+    assert(json_find_end(&reader, 0) == MAYBE);
     reader.js_buf = (char_u *)"{\"";
-    assert(json_find_end(&reader) == MAYBE);
+    assert(json_find_end(&reader, 0) == MAYBE);
     reader.js_buf = (char_u *)"{";
-    assert(json_find_end(&reader) == MAYBE);
+    assert(json_find_end(&reader, 0) == MAYBE);
 
     /* object with white space */
     reader.js_buf = (char_u *)"  {  \"a\"  :  123  }  ";
-    assert(json_find_end(&reader) == OK);
+    assert(json_find_end(&reader, 0) == OK);
     reader.js_buf = (char_u *)"  {  \"a\"  :  123  ";
-    assert(json_find_end(&reader) == MAYBE);
+    assert(json_find_end(&reader, 0) == MAYBE);
     reader.js_buf = (char_u *)"  {  \"a\"  :  ";
-    assert(json_find_end(&reader) == MAYBE);
+    assert(json_find_end(&reader, 0) == MAYBE);
     reader.js_buf = (char_u *)"  {  \"a\"  ";
-    assert(json_find_end(&reader) == MAYBE);
+    assert(json_find_end(&reader, 0) == MAYBE);
     reader.js_buf = (char_u *)"  {  \"a  ";
-    assert(json_find_end(&reader) == MAYBE);
+    assert(json_find_end(&reader, 0) == MAYBE);
     reader.js_buf = (char_u *)"  {   ";
-    assert(json_find_end(&reader) == MAYBE);
+    assert(json_find_end(&reader, 0) == MAYBE);
 
     /* array without white space */
     reader.js_buf = (char_u *)"[\"a\",123]";
-    assert(json_find_end(&reader) == OK);
+    assert(json_find_end(&reader, 0) == OK);
     reader.js_buf = (char_u *)"[\"a\",123";
-    assert(json_find_end(&reader) == MAYBE);
+    assert(json_find_end(&reader, 0) == MAYBE);
     reader.js_buf = (char_u *)"[\"a\",";
-    assert(json_find_end(&reader) == MAYBE);
+    assert(json_find_end(&reader, 0) == MAYBE);
     reader.js_buf = (char_u *)"[\"a\"";
-    assert(json_find_end(&reader) == MAYBE);
+    assert(json_find_end(&reader, 0) == MAYBE);
     reader.js_buf = (char_u *)"[\"a";
-    assert(json_find_end(&reader) == MAYBE);
+    assert(json_find_end(&reader, 0) == MAYBE);
     reader.js_buf = (char_u *)"[\"";
-    assert(json_find_end(&reader) == MAYBE);
+    assert(json_find_end(&reader, 0) == MAYBE);
     reader.js_buf = (char_u *)"[";
-    assert(json_find_end(&reader) == MAYBE);
+    assert(json_find_end(&reader, 0) == MAYBE);
 
     /* array with white space */
     reader.js_buf = (char_u *)"  [  \"a\"  ,  123  ]  ";
-    assert(json_find_end(&reader) == OK);
+    assert(json_find_end(&reader, 0) == OK);
     reader.js_buf = (char_u *)"  [  \"a\"  ,  123  ";
-    assert(json_find_end(&reader) == MAYBE);
+    assert(json_find_end(&reader, 0) == MAYBE);
     reader.js_buf = (char_u *)"  [  \"a\"  ,  ";
-    assert(json_find_end(&reader) == MAYBE);
+    assert(json_find_end(&reader, 0) == MAYBE);
     reader.js_buf = (char_u *)"  [  \"a\"  ";
-    assert(json_find_end(&reader) == MAYBE);
+    assert(json_find_end(&reader, 0) == MAYBE);
     reader.js_buf = (char_u *)"  [  \"a  ";
-    assert(json_find_end(&reader) == MAYBE);
+    assert(json_find_end(&reader, 0) == MAYBE);
     reader.js_buf = (char_u *)"  [  ";
-    assert(json_find_end(&reader) == MAYBE);
+    assert(json_find_end(&reader, 0) == MAYBE);
 }
 
     static int
@@ -157,15 +157,15 @@ test_fill_called_on_find_end(void)
     reader.js_used = 0;
     reader.js_buf = (char_u *)"  [  \"a\"  ,  123  ";
     reader.js_cookie =        "  [  \"a\"  ,  123  ]  ";
-    assert(json_find_end(&reader) == OK);
+    assert(json_find_end(&reader, 0) == OK);
     reader.js_buf = (char_u *)"  [  \"a\"  ,  ";
-    assert(json_find_end(&reader) == OK);
+    assert(json_find_end(&reader, 0) == OK);
     reader.js_buf = (char_u *)"  [  \"a\"  ";
-    assert(json_find_end(&reader) == OK);
+    assert(json_find_end(&reader, 0) == OK);
     reader.js_buf = (char_u *)"  [  \"a";
-    assert(json_find_end(&reader) == OK);
+    assert(json_find_end(&reader, 0) == OK);
     reader.js_buf = (char_u *)"  [  ";
-    assert(json_find_end(&reader) == OK);
+    assert(json_find_end(&reader, 0) == OK);
 }
 
 /*
index f8e4a9b9f4214f53998a3b1f516753132b1864db..693d2c223d3005d49ef0912664633aa1c6550063 100644 (file)
@@ -1,7 +1,7 @@
 /* channel.c */
 void channel_gui_register_all(void);
 int channel_open(char *hostname, int port_in, int waittime, void (*close_cb)(void));
-void channel_set_json_mode(int idx, int json_mode);
+void channel_set_json_mode(int idx, ch_mode_T ch_mode);
 void channel_set_timeout(int idx, int timeout);
 void channel_set_callback(int idx, char_u *callback);
 void channel_set_req_callback(int idx, char_u *callback, int id);
index 5d2e7ba900a03d84e90ea42e8d1a69ab0b71c13c..b98c2c9fb451356888fd1579f9167e7e64490e34 100644 (file)
@@ -1,7 +1,7 @@
 /* json.c */
-char_u *json_encode(typval_T *val);
-char_u *json_encode_nr_expr(int nr, typval_T *val);
-int json_decode_all(js_read_T *reader, typval_T *res);
-int json_decode(js_read_T *reader, typval_T *res);
-int json_find_end(js_read_T *reader);
+char_u *json_encode(typval_T *val, int options);
+char_u *json_encode_nr_expr(int nr, typval_T *val, int options);
+int json_decode_all(js_read_T *reader, typval_T *res, int options);
+int json_decode(js_read_T *reader, typval_T *res, int options);
+int json_find_end(js_read_T *reader, int options);
 /* vim: set ft=c : */
index 5a2d6fdc4d105e101964d2e2c6392e3ab393c0ae..d0823bc5e88bc6f70c61cb396c8c84d1246e78c7 100644 (file)
@@ -2728,3 +2728,11 @@ struct js_reader
     void       *js_cookie;     /* can be used by js_fill */
 };
 typedef struct js_reader js_read_T;
+
+/* mode for a channel */
+typedef enum
+{
+    MODE_RAW = 0,
+    MODE_JSON,
+    MODE_JS
+} ch_mode_T;
index 52cffc8255e7b95cd020cf44f66a289de7fbafd9..2534de113dbadb3cf673277b3fa2fe5ac5e6bab4 100644 (file)
@@ -32,9 +32,12 @@ let l3 = [1, 2]
 let s:varl3 = [l3, l3]
 
 let s:jsond1 = '{"a":1,"b":"bee","c":[1,2]}'
+let s:jsd1 = '{a:1,b:"bee",c:[1,2]}'
 let s:vard1 = {"a": 1, "b": "bee","c": [1,2]}
 let s:jsond2 = '{"1":1,"2":{"a":"aa","b":{},"c":"cc"},"3":3}'
+let s:jsd2 = '{"1":1,"2":{a:"aa",b:{},c:"cc"},"3":3}'
 let s:jsond2s = "  { \"1\" : 1 , \"2\" :\n{ \"a\"\r: \"aa\" , \"b\" : {\<Tab>} , \"c\" : \"cc\" } , \"3\" : 3 }\r\n"
+let s:jsd2s = "  { \"1\" : 1 , \"2\" :\n{ a\r: \"aa\" , b : {\<Tab>} , c : \"cc\" } , \"3\" : 3 }\r\n"
 let s:vard2 = {"1": 1, "2": 2, "3": 3}
 let d2 = {"a": "aa", "b": s:vard2, "c": "cc"}
 let s:vard2["2"] = d2
@@ -42,11 +45,16 @@ let s:vard2x = {"1": 1, "2": {"a": "aa", "b": {}, "c": "cc"}, "3": 3}
 let d3 = {"a": 1, "b": 2}
 let s:vard3 = {"x": d3, "y": d3}
 let s:jsond3 = '{"x":{"a":1,"b":2},"y":{"a":1,"b":2}}'
+let s:jsd3 = '{x:{a:1,b:2},y:{a:1,b:2}}'
+let s:vard4 = {"key": v:none}
+let s:vard4x = {"key": v:null}
+let s:jsond4 = '{"key":null}'
+let s:jsd4 = '{key:null}'
 
-let s:jsonvals = '[true,false,,null]'
-let s:varvals = [v:true, v:false, v:none, v:null]
+let s:jsonvals = '[true,false,null,null]'
+let s:varvals = [v:true, v:false, v:null, v:null]
 
-func Test_encode()
+func Test_json_encode()
   call assert_equal(s:json1, jsonencode(s:var1))
   call assert_equal(s:json2, jsonencode(s:var2))
   call assert_equal(s:json3, jsonencode(s:var3))
@@ -69,18 +77,18 @@ func Test_encode()
   call assert_equal(s:jsond1, jsonencode(s:vard1))
   call assert_equal(s:jsond2, jsonencode(s:vard2))
   call assert_equal(s:jsond3, jsonencode(s:vard3))
+  call assert_equal(s:jsond4, jsonencode(s:vard4))
 
   call assert_equal(s:jsonvals, jsonencode(s:varvals))
 
   call assert_fails('echo jsonencode(function("tr"))', 'E474:')
   call assert_fails('echo jsonencode([function("tr")])', 'E474:')
-  call assert_fails('echo jsonencode({"key":v:none})', 'E474:')
 
   silent! let res = jsonencode(function("tr"))
   call assert_equal("", res)
 endfunc
 
-func Test_decode()
+func Test_json_decode()
   call assert_equal(s:var1, jsondecode(s:json1))
   call assert_equal(s:var2, jsondecode(s:json2))
   call assert_equal(s:var3, jsondecode(s:json3))
@@ -103,7 +111,9 @@ func Test_decode()
 
   call assert_equal(s:vard1, jsondecode(s:jsond1))
   call assert_equal(s:vard2x, jsondecode(s:jsond2))
+  call assert_equal(s:vard2x, jsondecode(s:jsond2s))
   call assert_equal(s:vard3, jsondecode(s:jsond3))
+  call assert_equal(s:vard4x, jsondecode(s:jsond4))
 
   call assert_equal(s:varvals, jsondecode(s:jsonvals))
 
@@ -134,4 +144,110 @@ func Test_decode()
   call assert_fails('call jsondecode("[1")', "E474:")
   call assert_fails('call jsondecode("[1,")', "E474:")
   call assert_fails('call jsondecode("[1 2]")', "E474:")
+
+  call assert_fails('call jsondecode("[1,,2]")', "E474:")
+endfunc
+
+let s:jsl5 = '[7,,,]'
+let s:varl5 = [7, v:none, v:none]
+
+func Test_js_encode()
+  call assert_equal(s:json1, jsencode(s:var1))
+  call assert_equal(s:json2, jsencode(s:var2))
+  call assert_equal(s:json3, jsencode(s:var3))
+  call assert_equal(s:json4, jsencode(s:var4))
+  call assert_equal(s:json5, jsencode(s:var5))
+
+  if has('multi_byte')
+    call assert_equal(s:jsonmb, jsencode(s:varmb))
+  endif
+
+  call assert_equal(s:jsonnr, jsencode(s:varnr))
+  if has('float')
+    call assert_equal(s:jsonfl, jsencode(s:varfl))
+  endif
+
+  call assert_equal(s:jsonl1, jsencode(s:varl1))
+  call assert_equal(s:jsonl2, jsencode(s:varl2))
+  call assert_equal(s:jsonl3, jsencode(s:varl3))
+
+  call assert_equal(s:jsd1, jsencode(s:vard1))
+  call assert_equal(s:jsd2, jsencode(s:vard2))
+  call assert_equal(s:jsd3, jsencode(s:vard3))
+  call assert_equal(s:jsd4, jsencode(s:vard4))
+
+  call assert_equal(s:jsonvals, jsencode(s:varvals))
+
+  call assert_fails('echo jsencode(function("tr"))', 'E474:')
+  call assert_fails('echo jsencode([function("tr")])', 'E474:')
+
+  silent! let res = jsencode(function("tr"))
+  call assert_equal("", res)
+
+  call assert_equal(s:jsl5, jsencode(s:varl5))
+endfunc
+
+func Test_js_decode()
+  call assert_equal(s:var1, jsdecode(s:json1))
+  call assert_equal(s:var2, jsdecode(s:json2))
+  call assert_equal(s:var3, jsdecode(s:json3))
+  call assert_equal(s:var4, jsdecode(s:json4))
+  call assert_equal(s:var5, jsdecode(s:json5))
+
+  if has('multi_byte')
+    call assert_equal(s:varmb, jsdecode(s:jsonmb))
+  endif
+
+  call assert_equal(s:varnr, jsdecode(s:jsonnr))
+  if has('float')
+    call assert_equal(s:varfl, jsdecode(s:jsonfl))
+  endif
+
+  call assert_equal(s:varl1, jsdecode(s:jsonl1))
+  call assert_equal(s:varl2x, jsdecode(s:jsonl2))
+  call assert_equal(s:varl2x, jsdecode(s:jsonl2s))
+  call assert_equal(s:varl3, jsdecode(s:jsonl3))
+
+  call assert_equal(s:vard1, jsdecode(s:jsond1))
+  call assert_equal(s:vard1, jsdecode(s:jsd1))
+  call assert_equal(s:vard2x, jsdecode(s:jsond2))
+  call assert_equal(s:vard2x, jsdecode(s:jsd2))
+  call assert_equal(s:vard2x, jsdecode(s:jsond2s))
+  call assert_equal(s:vard2x, jsdecode(s:jsd2s))
+  call assert_equal(s:vard3, jsdecode(s:jsond3))
+  call assert_equal(s:vard3, jsdecode(s:jsd3))
+  call assert_equal(s:vard4x, jsdecode(s:jsond4))
+  call assert_equal(s:vard4x, jsdecode(s:jsd4))
+
+  call assert_equal(s:varvals, jsdecode(s:jsonvals))
+
+  call assert_equal(v:true, jsdecode('true'))
+  call assert_equal(type(v:true), type(jsdecode('true')))
+  call assert_equal(v:none, jsdecode(''))
+  call assert_equal(type(v:none), type(jsdecode('')))
+  call assert_equal("", jsdecode('""'))
+
+  call assert_equal({'n': 1}, jsdecode('{"n":1,}'))
+
+  call assert_fails('call jsdecode("\"")', "E474:")
+  call assert_fails('call jsdecode("blah")', "E474:")
+  call assert_fails('call jsdecode("true blah")', "E474:")
+  call assert_fails('call jsdecode("<foobar>")', "E474:")
+
+  call assert_fails('call jsdecode("{")', "E474:")
+  call assert_fails('call jsdecode("{foobar}")', "E474:")
+  call assert_fails('call jsdecode("{\"n\",")', "E474:")
+  call assert_fails('call jsdecode("{\"n\":")', "E474:")
+  call assert_fails('call jsdecode("{\"n\":1")', "E474:")
+  call assert_fails('call jsdecode("{\"n\":1,")', "E474:")
+  call assert_fails('call jsdecode("{\"n\",1}")', "E474:")
+  call assert_fails('call jsdecode("{-}")', "E474:")
+
+  call assert_fails('call jsdecode("[foobar]")', "E474:")
+  call assert_fails('call jsdecode("[")', "E474:")
+  call assert_fails('call jsdecode("[1")', "E474:")
+  call assert_fails('call jsdecode("[1,")', "E474:")
+  call assert_fails('call jsdecode("[1 2]")', "E474:")
+
+  call assert_equal(s:varl5, jsdecode(s:jsl5))
 endfunc
index 00b7176f590af488aacea9494ecd7b10ee944c0b..221d0a05ae806458aff4648c8db4e625be040560 100644 (file)
@@ -747,6 +747,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    1279,
 /**/
     1278,
 /**/
index 02f30363712063731044d5779ec2fdefa3ce5ecf..17034ef942a4691d8c9441bed30c30ca54108b8d 100644 (file)
--- a/src/vim.h
+++ b/src/vim.h
@@ -2317,6 +2317,10 @@ typedef int sock_T;
 # define MAX_OPEN_CHANNELS 0
 #endif
 
+/* Options for json_encode() and json_decode. */
+#define JSON_JS                1   /* use JS instead of JSON */
+#define JSON_NO_NONE   2   /* v:none item not allowed */
+
 #ifdef FEAT_MZSCHEME
 /* this is in main.c, cproto can't handle it. */
 int vim_main2(int argc, char **argv);