summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBram Moolenaar <Bram@vim.org>2016-02-07 19:19:53 +0100
committerBram Moolenaar <Bram@vim.org>2016-02-07 19:19:53 +0100
commit595e64e259faefb330866852e1b9f6168544572a (patch)
tree87986bc108647e7c597195cea325ca130db69a40
parent55fab439a6f3bba6dbe780ac034b84d5822a1a96 (diff)
downloadvim-git-595e64e259faefb330866852e1b9f6168544572a.tar.gz
patch 7.4.1279v7.4.1279
Problem: jsonencode() is not producing strict JSON. Solution: Add jsencode() and jsdecode(). Make jsonencode() and jsondecode() strict.
-rw-r--r--runtime/doc/channel.txt23
-rw-r--r--runtime/doc/eval.txt33
-rw-r--r--src/channel.c26
-rw-r--r--src/eval.c47
-rw-r--r--src/json.c138
-rw-r--r--src/json_test.c98
-rw-r--r--src/proto/channel.pro2
-rw-r--r--src/proto/json.pro10
-rw-r--r--src/structs.h8
-rw-r--r--src/testdir/test_json.vim126
-rw-r--r--src/version.c2
-rw-r--r--src/vim.h4
12 files changed, 379 insertions, 138 deletions
diff --git a/runtime/doc/channel.txt b/runtime/doc/channel.txt
index 855fda378..bc9e64144 100644
--- a/runtime/doc/channel.txt
+++ b/runtime/doc/channel.txt
@@ -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" ~
diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt
index b476ef0f0..1f009cc7b 100644
--- a/runtime/doc/eval.txt
+++ b/runtime/doc/eval.txt
@@ -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
diff --git a/src/channel.c b/src/channel.c
index a6458c65e..1d12ee722 100644
--- a/src/channel.c
+++ b/src/channel.c
@@ -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)
diff --git a/src/eval.c b/src/eval.c
index bd0040e66..4b1250b59 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -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;
@@ -14443,6 +14451,31 @@ f_join(typval_T *argvars, typval_T *rettv)
}
/*
+ * "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
*/
static void
@@ -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);
}
/*
diff --git a/src/json.c b/src/json.c
index 17eed4fa1..31bac26d5 100644
--- a/src/json.c
+++ b/src/json.c
@@ -16,22 +16,23 @@
#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;
}
@@ -123,11 +125,29 @@ 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;
}
diff --git a/src/json_test.c b/src/json_test.c
index f50c95608..2a28c7405 100644
--- a/src/json_test.c
+++ b/src/json_test.c
@@ -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);
}
/*
diff --git a/src/proto/channel.pro b/src/proto/channel.pro
index f8e4a9b9f..693d2c223 100644
--- a/src/proto/channel.pro
+++ b/src/proto/channel.pro
@@ -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);
diff --git a/src/proto/json.pro b/src/proto/json.pro
index 5d2e7ba90..b98c2c9fb 100644
--- a/src/proto/json.pro
+++ b/src/proto/json.pro
@@ -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 : */
diff --git a/src/structs.h b/src/structs.h
index 5a2d6fdc4..d0823bc5e 100644
--- a/src/structs.h
+++ b/src/structs.h
@@ -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;
diff --git a/src/testdir/test_json.vim b/src/testdir/test_json.vim
index 52cffc825..2534de113 100644
--- a/src/testdir/test_json.vim
+++ b/src/testdir/test_json.vim
@@ -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
diff --git a/src/version.c b/src/version.c
index 00b7176f5..221d0a05a 100644
--- a/src/version.c
+++ b/src/version.c
@@ -748,6 +748,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
+ 1279,
+/**/
1278,
/**/
1277,
diff --git a/src/vim.h b/src/vim.h
index 02f303637..17034ef94 100644
--- 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);