# HG changeset patch # User Christian Brabandt # Date 1454869805 -3600 # Node ID 45ea5ebf3a9850a54d5e5949380f9e26f83048dc # Parent 79c5a86fcdfe3cb840673709b7698ded5bbf047c commit https://github.com/vim/vim/commit/595e64e259faefb330866852e1b9f6168544572a Author: Bram Moolenaar Date: Sun Feb 7 19:19:53 2016 +0100 patch 7.4.1279 Problem: jsonencode() is not producing strict JSON. Solution: Add jsencode() and jsdecode(). Make jsonencode() and jsondecode() strict. diff --git a/runtime/doc/channel.txt b/runtime/doc/channel.txt --- 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 chann 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 w "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 *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 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 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -1956,6 +1956,8 @@ job_start({command} [, {options}]) Job s 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_ 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 --- 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 } /* - * 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, ty 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, ty 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, ty 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 --- a/src/eval.c +++ b/src/eval.c @@ -628,6 +628,8 @@ static void f_job_stop(typval_T *argvars 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 *r 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 *r { 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 *r 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_ 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 *rett } /* + * "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 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 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 --- 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 *va 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 { 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 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 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, typ 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, typ } 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, ty 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] != '"') + { + /* 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 { - key = get_tv_string_buf_chk(&tvkey, buf); - if (key == NULL || *key == NUL) + 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, ty ++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, ty * 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, typv 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, typv /* * 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, typva /* * 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 /* * 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 * 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 --- 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 --- 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 --- 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 --- 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 --- 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\" : {\} , \"c\" : \"cc\" } , \"3\" : 3 }\r\n" +let s:jsd2s = " { \"1\" : 1 , \"2\" :\n{ a\r: \"aa\" , b : {\} , 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", 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("")', "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 --- 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 --- 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);