From e47622f26b40d88bb8582c391df30474a64a082c Mon Sep 17 00:00:00 2001 From: Jakob Schnitzer Date: Sun, 26 Mar 2017 13:20:44 +0200 Subject: options: setlocal should only set local value For 'iminsert' and 'imsearch' the global value was always changed. --- src/nvim/option.c | 2 -- .../functional/options/setlocal_setglobal_spec.lua | 22 ++++++++++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 test/functional/options/setlocal_setglobal_spec.lua diff --git a/src/nvim/option.c b/src/nvim/option.c index 0bf81b4d3a..f622efeb0b 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -4120,7 +4120,6 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, errmsg = e_invarg; curbuf->b_p_iminsert = B_IMODE_NONE; } - p_iminsert = curbuf->b_p_iminsert; showmode(); /* Show/unshow value of 'keymap' in status lines. */ status_redraw_curbuf(); @@ -4134,7 +4133,6 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, errmsg = e_invarg; curbuf->b_p_imsearch = B_IMODE_NONE; } - p_imsearch = curbuf->b_p_imsearch; } /* if 'titlelen' has changed, redraw the title */ else if (pp == &p_titlelen) { diff --git a/test/functional/options/setlocal_setglobal_spec.lua b/test/functional/options/setlocal_setglobal_spec.lua new file mode 100644 index 0000000000..6902437403 --- /dev/null +++ b/test/functional/options/setlocal_setglobal_spec.lua @@ -0,0 +1,22 @@ +-- Tests for :setlocal and :setglobal + +local helpers = require('test.functional.helpers')(after_each) +local clear, execute, eval, eq, nvim = + helpers.clear, helpers.execute, helpers.eval, helpers.eq, helpers.nvim + +local function get_num_option_global(opt) + return nvim('command_output', 'setglobal ' .. opt .. '?'):match('%d+') +end + +describe(':setlocal', function() + before_each(clear) + + it('setlocal sets only local value', function() + eq('0', get_num_option_global('iminsert')) + execute('setlocal iminsert=1') + eq('0', get_num_option_global('iminsert')) + eq('0', get_num_option_global('imsearch')) + execute('setlocal imsearch=1') + eq('0', get_num_option_global('imsearch')) + end) +end) -- cgit From 628d0335b8e402008b3c03db9f5d2a109d5e8ef2 Mon Sep 17 00:00:00 2001 From: Jakob Schnitzer Date: Mon, 27 Mar 2017 09:51:14 +0200 Subject: options: add some tests --- test/functional/options/num_options_spec.lua | 42 ++++++++++++++++++++++ .../functional/options/setlocal_setglobal_spec.lua | 22 ------------ 2 files changed, 42 insertions(+), 22 deletions(-) create mode 100644 test/functional/options/num_options_spec.lua delete mode 100644 test/functional/options/setlocal_setglobal_spec.lua diff --git a/test/functional/options/num_options_spec.lua b/test/functional/options/num_options_spec.lua new file mode 100644 index 0000000000..c37bbeffc3 --- /dev/null +++ b/test/functional/options/num_options_spec.lua @@ -0,0 +1,42 @@ +-- Tests for :setlocal and :setglobal + +local helpers = require('test.functional.helpers')(after_each) +local clear, execute, eval, eq, nvim = + helpers.clear, helpers.execute, helpers.eval, helpers.eq, helpers.nvim + +local function get_num_option_global(opt) + return nvim('command_output', 'setglobal ' .. opt .. '?'):match('%d+') +end + +local function should_fail(opt, value, errmsg) + execute('let v:errmsg = ""') + execute('setglobal ' .. opt .. '=' .. value) + eq(errmsg, eval("v:errmsg"):match("E%d*:")) + execute('let v:errmsg = ""') + execute('setlocal ' .. opt .. '=' .. value) + eq(errmsg, eval("v:errmsg"):match("E%d*:")) + execute('let v:errmsg = ""') +end + +describe(':setlocal', function() + before_each(clear) + + it('setlocal sets only local value', function() + eq('0', get_num_option_global('iminsert')) + execute('setlocal iminsert=1') + eq('0', get_num_option_global('iminsert')) + eq('0', get_num_option_global('imsearch')) + execute('setlocal imsearch=1') + eq('0', get_num_option_global('imsearch')) + end) +end) + +describe(':set validation', function() + before_each(clear) + + it('setlocal and setglobal validate values', function() + should_fail('shiftwidth', -10, 'E487') + should_fail('tabstop', -10, 'E487') + should_fail('winheight', -10, 'E487') + end) +end) diff --git a/test/functional/options/setlocal_setglobal_spec.lua b/test/functional/options/setlocal_setglobal_spec.lua deleted file mode 100644 index 6902437403..0000000000 --- a/test/functional/options/setlocal_setglobal_spec.lua +++ /dev/null @@ -1,22 +0,0 @@ --- Tests for :setlocal and :setglobal - -local helpers = require('test.functional.helpers')(after_each) -local clear, execute, eval, eq, nvim = - helpers.clear, helpers.execute, helpers.eval, helpers.eq, helpers.nvim - -local function get_num_option_global(opt) - return nvim('command_output', 'setglobal ' .. opt .. '?'):match('%d+') -end - -describe(':setlocal', function() - before_each(clear) - - it('setlocal sets only local value', function() - eq('0', get_num_option_global('iminsert')) - execute('setlocal iminsert=1') - eq('0', get_num_option_global('iminsert')) - eq('0', get_num_option_global('imsearch')) - execute('setlocal imsearch=1') - eq('0', get_num_option_global('imsearch')) - end) -end) -- cgit From 79d3e9494299ae91f297c5a06dc3fd4f2f71f13e Mon Sep 17 00:00:00 2001 From: Jakob Schnitzer Date: Mon, 27 Mar 2017 18:52:16 +0200 Subject: options: move code around in set_num_option handle side-effects after validation --- src/nvim/option.c | 175 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 101 insertions(+), 74 deletions(-) diff --git a/src/nvim/option.c b/src/nvim/option.c index f622efeb0b..248b5205ec 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -4004,9 +4004,7 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, curbuf->b_p_sw = curbuf->b_p_ts; } - /* - * Number options that need some action when changed - */ + // Number options that need some validation when changed. if (pp == &p_wh || pp == &p_hh) { if (p_wh < 1) { errmsg = e_positive; @@ -4020,14 +4018,6 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, errmsg = e_positive; p_hh = 0; } - - /* Change window height NOW */ - if (lastwin != firstwin) { - if (pp == &p_wh && curwin->w_height < p_wh) - win_setheight((int)p_wh); - if (pp == &p_hh && curbuf->b_help && curwin->w_height < p_hh) - win_setheight((int)p_hh); - } } /* 'winminheight' */ else if (pp == &p_wmh) { @@ -4039,7 +4029,6 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, errmsg = e_winheight; p_wmh = p_wh; } - win_setminheight(); } else if (pp == &p_wiw) { if (p_wiw < 1) { errmsg = e_positive; @@ -4049,10 +4038,6 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, errmsg = e_winwidth; p_wiw = p_wmw; } - - /* Change window width NOW */ - if (lastwin != firstwin && curwin->w_width < p_wiw) - win_setwidth((int)p_wiw); } /* 'winminwidth' */ else if (pp == &p_wmw) { @@ -4064,29 +4049,11 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, errmsg = e_winwidth; p_wmw = p_wiw; } - win_setminheight(); - } else if (pp == &p_ls) { - /* (re)set last window status line */ - last_status(false); - } - /* (re)set tab page line */ - else if (pp == &p_stal) { - shell_new_rows(); /* recompute window positions and heights */ } /* 'foldlevel' */ else if (pp == &curwin->w_p_fdl) { if (curwin->w_p_fdl < 0) curwin->w_p_fdl = 0; - newFoldLevel(); - } - /* 'foldminlines' */ - else if (pp == &curwin->w_p_fml) { - foldUpdateAll(curwin); - } - /* 'foldnestmax' */ - else if (pp == &curwin->w_p_fdn) { - if (foldmethodIsSyntax(curwin) || foldmethodIsIndent(curwin)) - foldUpdateAll(curwin); } /* 'foldcolumn' */ else if (pp == &curwin->w_p_fdc) { @@ -4097,16 +4064,6 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, errmsg = e_invarg; curwin->w_p_fdc = 12; } - // 'shiftwidth' or 'tabstop' - } else if (pp == &curbuf->b_p_sw || pp == (long *)&curbuf->b_p_ts) { - if (foldmethodIsIndent(curwin)) { - foldUpdateAll(curwin); - } - // When 'shiftwidth' changes, or it's zero and 'tabstop' changes: - // parse 'cinoptions'. - if (pp == &curbuf->b_p_sw || curbuf->b_p_sw == 0) { - parse_cino(curbuf); - } } /* 'maxcombine' */ else if (pp == &p_mco) { @@ -4114,15 +4071,11 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, p_mco = MAX_MCO; else if (p_mco < 0) p_mco = 0; - screenclear(); /* will re-allocate the screen */ } else if (pp == &curbuf->b_p_iminsert) { if (curbuf->b_p_iminsert < 0 || curbuf->b_p_iminsert > B_IMODE_LAST) { errmsg = e_invarg; curbuf->b_p_iminsert = B_IMODE_NONE; } - showmode(); - /* Show/unshow value of 'keymap' in status lines. */ - status_redraw_curbuf(); } else if (pp == &p_window) { if (p_window < 1) p_window = 1; @@ -4140,8 +4093,6 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, errmsg = e_positive; p_titlelen = 85; } - if (starting != NO_SCREEN && old_value != p_titlelen) - need_maketitle = TRUE; } /* if p_ch changed value, change the command line height */ else if (pp == &p_ch) { @@ -4151,12 +4102,6 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, } if (p_ch > Rows - min_rows() + 1) p_ch = Rows - min_rows() + 1; - - /* Only compute the new window layout when startup has been - * completed. Otherwise the frame sizes may be wrong. */ - if (p_ch != old_value && full_screen - ) - command_height(); } /* when 'updatecount' changes from zero to non-zero, open swap files */ else if (pp == &p_uc) { @@ -4164,8 +4109,6 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, errmsg = e_positive; p_uc = 100; } - if (p_uc && !old_value) - ml_open_files(); } else if (pp == &curwin->w_p_cole) { if (curwin->w_p_cole < 0) { errmsg = e_positive; @@ -4175,18 +4118,6 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, curwin->w_p_cole = 3; } } - /* sync undo before 'undolevels' changes */ - else if (pp == &p_ul) { - /* use the old value, otherwise u_sync() may not work properly */ - p_ul = old_value; - u_sync(TRUE); - p_ul = value; - } else if (pp == &curbuf->b_p_ul) { - /* use the old value, otherwise u_sync() may not work properly */ - curbuf->b_p_ul = old_value; - u_sync(TRUE); - curbuf->b_p_ul = value; - } /* 'numberwidth' must be positive */ else if (pp == &curwin->w_p_nuw) { if (curwin->w_p_nuw < 1) { @@ -4203,10 +4134,6 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, errmsg = e_positive; curbuf->b_p_tw = 0; } - - FOR_ALL_TAB_WINDOWS(tp, wp) { - check_colorcolumn(wp); - } } else if (pp == &curbuf->b_p_scbk || pp == &p_scbk) { // 'scrollback' if (*pp < -1 || *pp > SB_MAX @@ -4219,6 +4146,106 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, } } + // Number options that need some action when changed + if (pp == &p_wh || pp == &p_hh) { + /* Change window height NOW */ + if (lastwin != firstwin) { + if (pp == &p_wh && curwin->w_height < p_wh) + win_setheight((int)p_wh); + if (pp == &p_hh && curbuf->b_help && curwin->w_height < p_hh) + win_setheight((int)p_hh); + } + } + else if (pp == &p_wmh) { + win_setminheight(); + } else if (pp == &p_wiw) { + /* Change window width NOW */ + if (lastwin != firstwin && curwin->w_width < p_wiw) + win_setwidth((int)p_wiw); + } + else if (pp == &p_wmw) { + win_setminheight(); + } else if (pp == &p_ls) { + /* (re)set last window status line */ + last_status(false); + } + /* (re)set tab page line */ + else if (pp == &p_stal) { + shell_new_rows(); /* recompute window positions and heights */ + } + else if (pp == &curwin->w_p_fdl) { + newFoldLevel(); + } + /* 'foldminlines' */ + else if (pp == &curwin->w_p_fml) { + foldUpdateAll(curwin); + } + /* 'foldnestmax' */ + else if (pp == &curwin->w_p_fdn) { + if (foldmethodIsSyntax(curwin) || foldmethodIsIndent(curwin)) + foldUpdateAll(curwin); + // 'shiftwidth' or 'tabstop' + } else if (pp == &curbuf->b_p_sw || pp == (long *)&curbuf->b_p_ts) { + if (foldmethodIsIndent(curwin)) { + foldUpdateAll(curwin); + } + // When 'shiftwidth' changes, or it's zero and 'tabstop' changes: + // parse 'cinoptions'. + if (pp == &curbuf->b_p_sw || curbuf->b_p_sw == 0) { + parse_cino(curbuf); + } + } + /* 'maxcombine' */ + else if (pp == &p_mco) { + screenclear(); /* will re-allocate the screen */ + } else if (pp == &curbuf->b_p_iminsert) { + showmode(); + /* Show/unshow value of 'keymap' in status lines. */ + status_redraw_curbuf(); + } + /* if 'titlelen' has changed, redraw the title */ + else if (pp == &p_titlelen) { + if (starting != NO_SCREEN && old_value != p_titlelen) + need_maketitle = TRUE; + } + /* if p_ch changed value, change the command line height */ + else if (pp == &p_ch) { + + /* Only compute the new window layout when startup has been + * completed. Otherwise the frame sizes may be wrong. */ + if (p_ch != old_value && full_screen + ) + command_height(); + } + /* when 'updatecount' changes from zero to non-zero, open swap files */ + else if (pp == &p_uc) { + if (p_uc && !old_value) + ml_open_files(); + } + /* sync undo before 'undolevels' changes */ + else if (pp == &p_ul) { + /* use the old value, otherwise u_sync() may not work properly */ + p_ul = old_value; + u_sync(TRUE); + p_ul = value; + } else if (pp == &curbuf->b_p_ul) { + /* use the old value, otherwise u_sync() may not work properly */ + curbuf->b_p_ul = old_value; + u_sync(TRUE); + curbuf->b_p_ul = value; + } else if (pp == &curbuf->b_p_tw) { + FOR_ALL_TAB_WINDOWS(tp, wp) { + check_colorcolumn(wp); + } + } + else if (pp == &curbuf->b_p_scbk || pp == &p_scbk) { + if (curbuf->terminal) { + // Force the scrollback to take effect. + terminal_resize(curbuf->terminal, UINT16_MAX, UINT16_MAX); + } + } + + /* * Check the bounds for numeric options here */ -- cgit From f4920fb485da92d546c1e01f5f10766d00c3791d Mon Sep 17 00:00:00 2001 From: Jakob Schnitzer Date: Mon, 27 Mar 2017 19:03:46 +0200 Subject: options: if invalid value is given, reset to old value --- src/nvim/option.c | 112 ++++++++++++++++++++---------------------------------- 1 file changed, 42 insertions(+), 70 deletions(-) diff --git a/src/nvim/option.c b/src/nvim/option.c index 248b5205ec..d069cceb39 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -4005,144 +4005,114 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, } // Number options that need some validation when changed. - if (pp == &p_wh || pp == &p_hh) { + if (pp == &p_wh) { if (p_wh < 1) { errmsg = e_positive; - p_wh = 1; } if (p_wmh > p_wh) { errmsg = e_winheight; - p_wh = p_wmh; } + } else if (pp == &p_hh) { if (p_hh < 0) { errmsg = e_positive; - p_hh = 0; } - } - /* 'winminheight' */ - else if (pp == &p_wmh) { + } else if (pp == &p_wmh) { if (p_wmh < 0) { errmsg = e_positive; - p_wmh = 0; } if (p_wmh > p_wh) { errmsg = e_winheight; - p_wmh = p_wh; } } else if (pp == &p_wiw) { if (p_wiw < 1) { errmsg = e_positive; - p_wiw = 1; } if (p_wmw > p_wiw) { errmsg = e_winwidth; - p_wiw = p_wmw; } - } - /* 'winminwidth' */ - else if (pp == &p_wmw) { + } else if (pp == &p_wmw) { if (p_wmw < 0) { errmsg = e_positive; - p_wmw = 0; } if (p_wmw > p_wiw) { errmsg = e_winwidth; - p_wmw = p_wiw; } - } - /* 'foldlevel' */ - else if (pp == &curwin->w_p_fdl) { - if (curwin->w_p_fdl < 0) - curwin->w_p_fdl = 0; - } - /* 'foldcolumn' */ - else if (pp == &curwin->w_p_fdc) { + } else if (pp == &curwin->w_p_fdl) { + if (curwin->w_p_fdl < 0) { + errmsg = e_positive; + } + } else if (pp == &curwin->w_p_fdc) { if (curwin->w_p_fdc < 0) { errmsg = e_positive; - curwin->w_p_fdc = 0; } else if (curwin->w_p_fdc > 12) { errmsg = e_invarg; - curwin->w_p_fdc = 12; } - } - /* 'maxcombine' */ - else if (pp == &p_mco) { - if (p_mco > MAX_MCO) - p_mco = MAX_MCO; - else if (p_mco < 0) - p_mco = 0; + } else if (pp == &p_mco) { + if (p_mco > MAX_MCO) { + errmsg = e_invarg; + } else if (p_mco < 0) { + errmsg = e_positive; + } } else if (pp == &curbuf->b_p_iminsert) { if (curbuf->b_p_iminsert < 0 || curbuf->b_p_iminsert > B_IMODE_LAST) { errmsg = e_invarg; - curbuf->b_p_iminsert = B_IMODE_NONE; } - } else if (pp == &p_window) { - if (p_window < 1) - p_window = 1; - else if (p_window >= Rows) - p_window = Rows - 1; } else if (pp == &curbuf->b_p_imsearch) { if (curbuf->b_p_imsearch < -1 || curbuf->b_p_imsearch > B_IMODE_LAST) { errmsg = e_invarg; - curbuf->b_p_imsearch = B_IMODE_NONE; } - } - /* if 'titlelen' has changed, redraw the title */ - else if (pp == &p_titlelen) { + } else if (pp == &p_titlelen) { if (p_titlelen < 0) { errmsg = e_positive; - p_titlelen = 85; - } - } - /* if p_ch changed value, change the command line height */ - else if (pp == &p_ch) { - if (p_ch < 1) { - errmsg = e_positive; - p_ch = 1; } - if (p_ch > Rows - min_rows() + 1) - p_ch = Rows - min_rows() + 1; - } - /* when 'updatecount' changes from zero to non-zero, open swap files */ - else if (pp == &p_uc) { + } else if (pp == &p_uc) { if (p_uc < 0) { errmsg = e_positive; - p_uc = 100; } } else if (pp == &curwin->w_p_cole) { if (curwin->w_p_cole < 0) { errmsg = e_positive; - curwin->w_p_cole = 0; } else if (curwin->w_p_cole > 3) { errmsg = e_invarg; - curwin->w_p_cole = 3; } - } - /* 'numberwidth' must be positive */ - else if (pp == &curwin->w_p_nuw) { + } else if (pp == &curwin->w_p_nuw) { if (curwin->w_p_nuw < 1) { errmsg = e_positive; - curwin->w_p_nuw = 1; } if (curwin->w_p_nuw > 10) { errmsg = e_invarg; - curwin->w_p_nuw = 10; } curwin->w_nrwidth_line_count = 0; } else if (pp == &curbuf->b_p_tw) { if (curbuf->b_p_tw < 0) { errmsg = e_positive; - curbuf->b_p_tw = 0; } } else if (pp == &curbuf->b_p_scbk || pp == &p_scbk) { - // 'scrollback' if (*pp < -1 || *pp > SB_MAX || (opt_flags == OPT_LOCAL && !curbuf->terminal)) { errmsg = e_invarg; - *pp = old_value; - } else if (curbuf->terminal) { - // Force the scrollback to take effect. - terminal_resize(curbuf->terminal, UINT16_MAX, UINT16_MAX); + } + } else if (pp == &p_ch) { + if (p_ch < 1) { + errmsg = e_positive; + } + } + + if (errmsg != NULL) { + *pp = old_value; + return errmsg; + } + + // For these options we want to fix some invalid values. + if (pp == &p_window) { + if (p_window < 1) { + p_window = 1; + } else if (p_window >= Rows) { + p_window = Rows - 1; + } + } else if (pp == &p_ch) { + if (p_ch > Rows - min_rows() + 1) { + p_ch = Rows - min_rows() + 1; } } @@ -4243,6 +4213,8 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, // Force the scrollback to take effect. terminal_resize(curbuf->terminal, UINT16_MAX, UINT16_MAX); } + } else if (pp == &curwin->w_p_nuw) { + curwin->w_nrwidth_line_count = 0; } -- cgit From 1a56a032fe6060c3b4c8532c209ccfaa90fdf74e Mon Sep 17 00:00:00 2001 From: Jakob Schnitzer Date: Mon, 27 Mar 2017 19:17:58 +0200 Subject: options: clean up num_options side-effects --- src/nvim/option.c | 118 +++++++++++++++++++++++------------------------------- 1 file changed, 50 insertions(+), 68 deletions(-) diff --git a/src/nvim/option.c b/src/nvim/option.c index d069cceb39..f6639d4f6a 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -3999,11 +3999,6 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, /* Remember where the option was set. */ set_option_scriptID_idx(opt_idx, opt_flags, current_SID); - if (curbuf->b_p_sw < 0) { - errmsg = e_positive; - curbuf->b_p_sw = curbuf->b_p_ts; - } - // Number options that need some validation when changed. if (pp == &p_wh) { if (p_wh < 1) { @@ -4091,13 +4086,18 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, if (*pp < -1 || *pp > SB_MAX || (opt_flags == OPT_LOCAL && !curbuf->terminal)) { errmsg = e_invarg; - } + } + } else if (pp == &curbuf->b_p_sw) { + if (curbuf->b_p_sw < 0) { + errmsg = e_positive; + } } else if (pp == &p_ch) { if (p_ch < 1) { errmsg = e_positive; } } + // If validation failed, reset to old value and return. if (errmsg != NULL) { *pp = old_value; return errmsg; @@ -4117,45 +4117,38 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, } // Number options that need some action when changed - if (pp == &p_wh || pp == &p_hh) { - /* Change window height NOW */ - if (lastwin != firstwin) { - if (pp == &p_wh && curwin->w_height < p_wh) - win_setheight((int)p_wh); - if (pp == &p_hh && curbuf->b_help && curwin->w_height < p_hh) - win_setheight((int)p_hh); + if (pp == &p_wh) { + if (lastwin != firstwin && curwin->w_height < p_wh) { + win_setheight((int)p_wh); } - } - else if (pp == &p_wmh) { + } else if (pp == &p_hh) { + if (lastwin != firstwin && curbuf->b_help && curwin->w_height < p_hh) { + win_setheight((int)p_hh); + } + } else if (pp == &p_wmh) { win_setminheight(); } else if (pp == &p_wiw) { - /* Change window width NOW */ - if (lastwin != firstwin && curwin->w_width < p_wiw) + if (lastwin != firstwin && curwin->w_width < p_wiw) { win_setwidth((int)p_wiw); - } - else if (pp == &p_wmw) { + } + } else if (pp == &p_wmw) { win_setminheight(); } else if (pp == &p_ls) { - /* (re)set last window status line */ + // (re)set last window status line. last_status(false); - } - /* (re)set tab page line */ - else if (pp == &p_stal) { - shell_new_rows(); /* recompute window positions and heights */ - } - else if (pp == &curwin->w_p_fdl) { + } else if (pp == &p_stal) { + // (re)set tab page line + shell_new_rows(); // recompute window positions and heights + } else if (pp == &curwin->w_p_fdl) { newFoldLevel(); - } - /* 'foldminlines' */ - else if (pp == &curwin->w_p_fml) { + } else if (pp == &curwin->w_p_fml) { foldUpdateAll(curwin); - } - /* 'foldnestmax' */ - else if (pp == &curwin->w_p_fdn) { - if (foldmethodIsSyntax(curwin) || foldmethodIsIndent(curwin)) + } else if (pp == &curwin->w_p_fdn) { + if (foldmethodIsSyntax(curwin) || foldmethodIsIndent(curwin)) { foldUpdateAll(curwin); - // 'shiftwidth' or 'tabstop' + } } else if (pp == &curbuf->b_p_sw || pp == (long *)&curbuf->b_p_ts) { + // 'shiftwidth' or 'tabstop' if (foldmethodIsIndent(curwin)) { foldUpdateAll(curwin); } @@ -4164,51 +4157,40 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, if (pp == &curbuf->b_p_sw || curbuf->b_p_sw == 0) { parse_cino(curbuf); } - } - /* 'maxcombine' */ - else if (pp == &p_mco) { - screenclear(); /* will re-allocate the screen */ + } else if (pp == &p_mco) { + screenclear(); // will re-allocate the screen } else if (pp == &curbuf->b_p_iminsert) { showmode(); - /* Show/unshow value of 'keymap' in status lines. */ + // Show/unshow value of 'keymap' in status lines. status_redraw_curbuf(); - } - /* if 'titlelen' has changed, redraw the title */ - else if (pp == &p_titlelen) { - if (starting != NO_SCREEN && old_value != p_titlelen) + } else if (pp == &p_titlelen) { + // if 'titlelen' has changed, redraw the title + if (starting != NO_SCREEN && old_value != p_titlelen) { need_maketitle = TRUE; - } - /* if p_ch changed value, change the command line height */ - else if (pp == &p_ch) { - - /* Only compute the new window layout when startup has been - * completed. Otherwise the frame sizes may be wrong. */ - if (p_ch != old_value && full_screen - ) + } + } else if (pp == &p_ch) { + // if p_ch changed value, change the command line height + // Only compute the new window layout when startup has been + // completed. Otherwise the frame sizes may be wrong. + if (p_ch != old_value && full_screen) { command_height(); - } - /* when 'updatecount' changes from zero to non-zero, open swap files */ - else if (pp == &p_uc) { - if (p_uc && !old_value) + } + } else if (pp == &p_uc) { + // when 'updatecount' changes from zero to non-zero, open swap files + if (p_uc && !old_value) { ml_open_files(); - } - /* sync undo before 'undolevels' changes */ - else if (pp == &p_ul) { - /* use the old value, otherwise u_sync() may not work properly */ - p_ul = old_value; - u_sync(TRUE); - p_ul = value; - } else if (pp == &curbuf->b_p_ul) { - /* use the old value, otherwise u_sync() may not work properly */ - curbuf->b_p_ul = old_value; + } + } else if (pp == &p_ul || pp == &curbuf->b_p_ul) { + // sync undo before 'undolevels' changes + // use the old value, otherwise u_sync() may not work properly + *pp = old_value; u_sync(TRUE); - curbuf->b_p_ul = value; + *pp = value; } else if (pp == &curbuf->b_p_tw) { FOR_ALL_TAB_WINDOWS(tp, wp) { check_colorcolumn(wp); } - } - else if (pp == &curbuf->b_p_scbk || pp == &p_scbk) { + } else if (pp == &curbuf->b_p_scbk || pp == &p_scbk) { if (curbuf->terminal) { // Force the scrollback to take effect. terminal_resize(curbuf->terminal, UINT16_MAX, UINT16_MAX); -- cgit From 0273f96ef69d2a70a9c2e77b7f5da3a811bca460 Mon Sep 17 00:00:00 2001 From: Jakob Schnitzer Date: Mon, 27 Mar 2017 19:26:41 +0200 Subject: options: move more validation together --- src/nvim/option.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/nvim/option.c b/src/nvim/option.c index f6639d4f6a..65457dec9f 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -4095,6 +4095,14 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, if (p_ch < 1) { errmsg = e_positive; } + } else if (pp == &curbuf->b_p_ts) { + if (curbuf->b_p_ts <= 0) { + errmsg = e_positive; + } + } else if (pp == &p_tm) { + if (p_tm < 0) { + errmsg = e_positive; + } } // If validation failed, reset to old value and return. @@ -4246,14 +4254,6 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, } } - if (curbuf->b_p_ts <= 0) { - errmsg = e_positive; - curbuf->b_p_ts = 8; - } - if (p_tm < 0) { - errmsg = e_positive; - p_tm = 0; - } if ((curwin->w_p_scr <= 0 || (curwin->w_p_scr > curwin->w_height && curwin->w_height > 0)) -- cgit From 2b0abdbd9adcdaee5fb4f8d056c6385749c45579 Mon Sep 17 00:00:00 2001 From: Jakob Schnitzer Date: Mon, 27 Mar 2017 19:29:51 +0200 Subject: options: more of the same --- src/nvim/option.c | 69 +++++++++++++++++++++++++++---------------------------- 1 file changed, 34 insertions(+), 35 deletions(-) diff --git a/src/nvim/option.c b/src/nvim/option.c index 65457dec9f..24c9b9b72e 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -4103,6 +4103,40 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, if (p_tm < 0) { errmsg = e_positive; } + } else if (pp == &p_hi) { + if (p_hi < 0) { + errmsg = e_positive; + } else if (p_hi > 10000) { + errmsg = e_invarg; + } + } else if (pp == &p_re) { + if (p_re < 0 || p_re > 2) { + errmsg = e_invarg; + } + } else if (pp == &p_report) { + if (p_report < 0) { + errmsg = e_positive; + } + } else if (pp == &p_so) { + if (p_so < 0 && full_screen) { + errmsg = e_scroll; + } + } else if (pp == &p_siso) { + if (p_siso < 0 && full_screen) { + errmsg = e_positive; + } + } else if (pp == &p_cwh) { + if (p_cwh < 1) { + errmsg = e_positive; + } + } else if (pp == &p_ut) { + if (p_ut < 0) { + errmsg = e_positive; + } + } else if (pp == &p_ss) { + if (p_ss < 0) { + errmsg = e_positive; + } } // If validation failed, reset to old value and return. @@ -4270,21 +4304,6 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, else /* curwin->w_p_scr > curwin->w_height */ curwin->w_p_scr = curwin->w_height; } - if (p_hi < 0) { - errmsg = e_positive; - p_hi = 0; - } else if (p_hi > 10000) { - errmsg = e_invarg; - p_hi = 10000; - } - if (p_re < 0 || p_re > 2) { - errmsg = e_invarg; - p_re = 0; - } - if (p_report < 0) { - errmsg = e_positive; - p_report = 1; - } if ((p_sj < -100 || p_sj >= Rows) && full_screen) { if (Rows != old_Rows) /* Rows changed, just adjust p_sj */ p_sj = Rows / 2; @@ -4293,26 +4312,6 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, p_sj = 1; } } - if (p_so < 0 && full_screen) { - errmsg = e_scroll; - p_so = 0; - } - if (p_siso < 0 && full_screen) { - errmsg = e_positive; - p_siso = 0; - } - if (p_cwh < 1) { - errmsg = e_positive; - p_cwh = 1; - } - if (p_ut < 0) { - errmsg = e_positive; - p_ut = 2000; - } - if (p_ss < 0) { - errmsg = e_positive; - p_ss = 0; - } /* May set global value for local option. */ if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) -- cgit From 2290a7a1b1f8ea3c04365667b751fdfff62b43f5 Mon Sep 17 00:00:00 2001 From: Jakob Schnitzer Date: Mon, 27 Mar 2017 19:37:39 +0200 Subject: options: group num_option validation by type --- src/nvim/option.c | 96 +++++++++++++++++++++++++++---------------------------- 1 file changed, 47 insertions(+), 49 deletions(-) diff --git a/src/nvim/option.c b/src/nvim/option.c index 24c9b9b72e..976aadbc9c 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -4032,30 +4032,12 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, if (p_wmw > p_wiw) { errmsg = e_winwidth; } - } else if (pp == &curwin->w_p_fdl) { - if (curwin->w_p_fdl < 0) { - errmsg = e_positive; - } - } else if (pp == &curwin->w_p_fdc) { - if (curwin->w_p_fdc < 0) { - errmsg = e_positive; - } else if (curwin->w_p_fdc > 12) { - errmsg = e_invarg; - } } else if (pp == &p_mco) { if (p_mco > MAX_MCO) { errmsg = e_invarg; } else if (p_mco < 0) { errmsg = e_positive; } - } else if (pp == &curbuf->b_p_iminsert) { - if (curbuf->b_p_iminsert < 0 || curbuf->b_p_iminsert > B_IMODE_LAST) { - errmsg = e_invarg; - } - } else if (pp == &curbuf->b_p_imsearch) { - if (curbuf->b_p_imsearch < -1 || curbuf->b_p_imsearch > B_IMODE_LAST) { - errmsg = e_invarg; - } } else if (pp == &p_titlelen) { if (p_titlelen < 0) { errmsg = e_positive; @@ -4064,41 +4046,10 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, if (p_uc < 0) { errmsg = e_positive; } - } else if (pp == &curwin->w_p_cole) { - if (curwin->w_p_cole < 0) { - errmsg = e_positive; - } else if (curwin->w_p_cole > 3) { - errmsg = e_invarg; - } - } else if (pp == &curwin->w_p_nuw) { - if (curwin->w_p_nuw < 1) { - errmsg = e_positive; - } - if (curwin->w_p_nuw > 10) { - errmsg = e_invarg; - } - curwin->w_nrwidth_line_count = 0; - } else if (pp == &curbuf->b_p_tw) { - if (curbuf->b_p_tw < 0) { - errmsg = e_positive; - } - } else if (pp == &curbuf->b_p_scbk || pp == &p_scbk) { - if (*pp < -1 || *pp > SB_MAX - || (opt_flags == OPT_LOCAL && !curbuf->terminal)) { - errmsg = e_invarg; - } - } else if (pp == &curbuf->b_p_sw) { - if (curbuf->b_p_sw < 0) { - errmsg = e_positive; - } } else if (pp == &p_ch) { if (p_ch < 1) { errmsg = e_positive; } - } else if (pp == &curbuf->b_p_ts) { - if (curbuf->b_p_ts <= 0) { - errmsg = e_positive; - } } else if (pp == &p_tm) { if (p_tm < 0) { errmsg = e_positive; @@ -4137,6 +4088,53 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, if (p_ss < 0) { errmsg = e_positive; } + } else if (pp == &curwin->w_p_fdl) { + if (curwin->w_p_fdl < 0) { + errmsg = e_positive; + } + } else if (pp == &curwin->w_p_fdc) { + if (curwin->w_p_fdc < 0) { + errmsg = e_positive; + } else if (curwin->w_p_fdc > 12) { + errmsg = e_invarg; + } + } else if (pp == &curwin->w_p_cole) { + if (curwin->w_p_cole < 0) { + errmsg = e_positive; + } else if (curwin->w_p_cole > 3) { + errmsg = e_invarg; + } + } else if (pp == &curwin->w_p_nuw) { + if (curwin->w_p_nuw < 1) { + errmsg = e_positive; + } else if (curwin->w_p_nuw > 10) { + errmsg = e_invarg; + } + } else if (pp == &curbuf->b_p_iminsert) { + if (curbuf->b_p_iminsert < 0 || curbuf->b_p_iminsert > B_IMODE_LAST) { + errmsg = e_invarg; + } + } else if (pp == &curbuf->b_p_imsearch) { + if (curbuf->b_p_imsearch < -1 || curbuf->b_p_imsearch > B_IMODE_LAST) { + errmsg = e_invarg; + } + } else if (pp == &curbuf->b_p_tw) { + if (curbuf->b_p_tw < 0) { + errmsg = e_positive; + } + } else if (pp == &curbuf->b_p_scbk || pp == &p_scbk) { + if (*pp < -1 || *pp > SB_MAX + || (opt_flags == OPT_LOCAL && !curbuf->terminal)) { + errmsg = e_invarg; + } + } else if (pp == &curbuf->b_p_sw) { + if (curbuf->b_p_sw < 0) { + errmsg = e_positive; + } + } else if (pp == &curbuf->b_p_ts) { + if (curbuf->b_p_ts <= 0) { + errmsg = e_positive; + } } // If validation failed, reset to old value and return. -- cgit From 44f039a1c8bf1a3111825f2d3385730e31536d9a Mon Sep 17 00:00:00 2001 From: Jakob Schnitzer Date: Mon, 27 Mar 2017 19:50:04 +0200 Subject: options: fix setglobal for buf-local number options --- src/nvim/option.c | 33 +++++++++++++--------------- test/functional/options/num_options_spec.lua | 6 ++--- 2 files changed, 18 insertions(+), 21 deletions(-) diff --git a/src/nvim/option.c b/src/nvim/option.c index 976aadbc9c..637591d7ca 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -4014,22 +4014,19 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, } else if (pp == &p_wmh) { if (p_wmh < 0) { errmsg = e_positive; - } - if (p_wmh > p_wh) { + } else if (p_wmh > p_wh) { errmsg = e_winheight; } } else if (pp == &p_wiw) { if (p_wiw < 1) { errmsg = e_positive; - } - if (p_wmw > p_wiw) { + } else if (p_wmw > p_wiw) { errmsg = e_winwidth; } } else if (pp == &p_wmw) { if (p_wmw < 0) { errmsg = e_positive; - } - if (p_wmw > p_wiw) { + } else if (p_wmw > p_wiw) { errmsg = e_winwidth; } } else if (pp == &p_mco) { @@ -4110,16 +4107,16 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, } else if (curwin->w_p_nuw > 10) { errmsg = e_invarg; } - } else if (pp == &curbuf->b_p_iminsert) { - if (curbuf->b_p_iminsert < 0 || curbuf->b_p_iminsert > B_IMODE_LAST) { + } else if (pp == &curbuf->b_p_iminsert || pp == &p_iminsert) { + if (*pp < 0 || *pp > B_IMODE_LAST) { errmsg = e_invarg; } - } else if (pp == &curbuf->b_p_imsearch) { - if (curbuf->b_p_imsearch < -1 || curbuf->b_p_imsearch > B_IMODE_LAST) { + } else if (pp == &curbuf->b_p_imsearch || pp == &p_imsearch) { + if (*pp < -1 || *pp > B_IMODE_LAST) { errmsg = e_invarg; } - } else if (pp == &curbuf->b_p_tw) { - if (curbuf->b_p_tw < 0) { + } else if (pp == &curbuf->b_p_tw || pp == &p_tw) { + if (*pp < 0) { errmsg = e_positive; } } else if (pp == &curbuf->b_p_scbk || pp == &p_scbk) { @@ -4127,12 +4124,12 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, || (opt_flags == OPT_LOCAL && !curbuf->terminal)) { errmsg = e_invarg; } - } else if (pp == &curbuf->b_p_sw) { - if (curbuf->b_p_sw < 0) { + } else if (pp == &curbuf->b_p_sw || pp == &p_sw) { + if (*pp < 0) { errmsg = e_positive; } - } else if (pp == &curbuf->b_p_ts) { - if (curbuf->b_p_ts <= 0) { + } else if (pp == &curbuf->b_p_ts || pp == &p_ts) { + if (*pp <= 0) { errmsg = e_positive; } } @@ -4206,7 +4203,7 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, } else if (pp == &p_titlelen) { // if 'titlelen' has changed, redraw the title if (starting != NO_SCREEN && old_value != p_titlelen) { - need_maketitle = TRUE; + need_maketitle = true; } } else if (pp == &p_ch) { // if p_ch changed value, change the command line height @@ -4224,7 +4221,7 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, // sync undo before 'undolevels' changes // use the old value, otherwise u_sync() may not work properly *pp = old_value; - u_sync(TRUE); + u_sync(true); *pp = value; } else if (pp == &curbuf->b_p_tw) { FOR_ALL_TAB_WINDOWS(tp, wp) { diff --git a/test/functional/options/num_options_spec.lua b/test/functional/options/num_options_spec.lua index c37bbeffc3..9b66d4f862 100644 --- a/test/functional/options/num_options_spec.lua +++ b/test/functional/options/num_options_spec.lua @@ -11,10 +11,10 @@ end local function should_fail(opt, value, errmsg) execute('let v:errmsg = ""') execute('setglobal ' .. opt .. '=' .. value) - eq(errmsg, eval("v:errmsg"):match("E%d*:")) + eq(errmsg, eval("v:errmsg"):match("E%d*")) execute('let v:errmsg = ""') execute('setlocal ' .. opt .. '=' .. value) - eq(errmsg, eval("v:errmsg"):match("E%d*:")) + eq(errmsg, eval("v:errmsg"):match("E%d*")) execute('let v:errmsg = ""') end @@ -37,6 +37,6 @@ describe(':set validation', function() it('setlocal and setglobal validate values', function() should_fail('shiftwidth', -10, 'E487') should_fail('tabstop', -10, 'E487') - should_fail('winheight', -10, 'E487') + should_fail('winheight', -10, 'E591') end) end) -- cgit From db095f65636664afb4b09a3920571bf0565c7763 Mon Sep 17 00:00:00 2001 From: Jakob Schnitzer Date: Tue, 28 Mar 2017 16:17:53 +0200 Subject: options: more tests; check first set later; stricter validation --- src/nvim/option.c | 141 ++++++++++++++------------- test/functional/options/num_options_spec.lua | 21 +++- 2 files changed, 91 insertions(+), 71 deletions(-) diff --git a/src/nvim/option.c b/src/nvim/option.c index 637591d7ca..d5bc3c1765 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -7,7 +7,8 @@ // - For a window option, add some code to copy_winopt(). // - For a buffer option, add some code to buf_copy_options(). // - For a buffer string option, add code to check_buf_options(). -// - If it's a numeric option, add any necessary bounds checks to do_set(). +// - If it's a numeric option, add any necessary bounds checks to +// set_num_option(). // - If it's a list of flags, add some code in do_set(), search for WW_ALL. // - When adding an option with expansion (P_EXPAND), but with a different // default for Vi and Vim (no P_VI_DEF), add some code at VIMEXP. @@ -1445,8 +1446,7 @@ do_set ( goto skip; } } else if (*arg == '-' || ascii_isdigit(*arg)) { - // Allow negative (for 'undolevels'), octal and - // hex numbers. + // Allow negative, octal and hex numbers. vim_str2nr(arg, NULL, &i, STR2NR_ALL, &value, NULL, 0); if (arg[i] != NUL && !ascii_iswhite(arg[i])) { errmsg = e_invarg; @@ -3995,151 +3995,158 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, return (char *)e_secure; } - *pp = value; - /* Remember where the option was set. */ - set_option_scriptID_idx(opt_idx, opt_flags, current_SID); + // Many number options assume their value is in the signed int range. + if (value < INT_MIN || value > INT_MAX) { + return e_invarg; + } - // Number options that need some validation when changed. + // Options that need some validation. if (pp == &p_wh) { - if (p_wh < 1) { + if (value < 1) { errmsg = e_positive; - } - if (p_wmh > p_wh) { + } else if (p_wmh > value) { errmsg = e_winheight; } } else if (pp == &p_hh) { - if (p_hh < 0) { + if (value < 0) { errmsg = e_positive; } } else if (pp == &p_wmh) { - if (p_wmh < 0) { + if (value < 0) { errmsg = e_positive; - } else if (p_wmh > p_wh) { + } else if (value > p_wh) { errmsg = e_winheight; } } else if (pp == &p_wiw) { - if (p_wiw < 1) { + if (value < 1) { errmsg = e_positive; - } else if (p_wmw > p_wiw) { + } else if (p_wmw > value) { errmsg = e_winwidth; } } else if (pp == &p_wmw) { - if (p_wmw < 0) { + if (value < 0) { errmsg = e_positive; - } else if (p_wmw > p_wiw) { + } else if (value > p_wiw) { errmsg = e_winwidth; } } else if (pp == &p_mco) { - if (p_mco > MAX_MCO) { + if (value > MAX_MCO) { errmsg = e_invarg; - } else if (p_mco < 0) { + } else if (value < 0) { errmsg = e_positive; } } else if (pp == &p_titlelen) { - if (p_titlelen < 0) { + if (value < 0) { errmsg = e_positive; } } else if (pp == &p_uc) { - if (p_uc < 0) { + if (value < 0) { errmsg = e_positive; } } else if (pp == &p_ch) { - if (p_ch < 1) { + if (value < 1) { errmsg = e_positive; } } else if (pp == &p_tm) { - if (p_tm < 0) { + if (value < 0) { errmsg = e_positive; } } else if (pp == &p_hi) { - if (p_hi < 0) { + if (value < 0) { errmsg = e_positive; - } else if (p_hi > 10000) { + } else if (value > 10000) { errmsg = e_invarg; } } else if (pp == &p_re) { - if (p_re < 0 || p_re > 2) { + if (value < 0 || value > 2) { errmsg = e_invarg; } } else if (pp == &p_report) { - if (p_report < 0) { + if (value < 0) { errmsg = e_positive; } } else if (pp == &p_so) { - if (p_so < 0 && full_screen) { + if (value < 0 && full_screen) { errmsg = e_scroll; } } else if (pp == &p_siso) { - if (p_siso < 0 && full_screen) { + if (value < 0 && full_screen) { errmsg = e_positive; } } else if (pp == &p_cwh) { - if (p_cwh < 1) { + if (value < 1) { errmsg = e_positive; } } else if (pp == &p_ut) { - if (p_ut < 0) { + if (value < 0) { errmsg = e_positive; } } else if (pp == &p_ss) { - if (p_ss < 0) { + if (value < 0) { errmsg = e_positive; } - } else if (pp == &curwin->w_p_fdl) { - if (curwin->w_p_fdl < 0) { + } else if (pp == &curwin->w_p_fdl + || pp == (long *)GLOBAL_WO(&curwin->w_p_fdl)) { + if (value < 0) { errmsg = e_positive; } - } else if (pp == &curwin->w_p_fdc) { - if (curwin->w_p_fdc < 0) { + } else if (pp == &curwin->w_p_fdc + || pp == (long *)GLOBAL_WO(&curwin->w_p_fdc)) { + if (value < 0) { errmsg = e_positive; - } else if (curwin->w_p_fdc > 12) { + } else if (value > 12) { errmsg = e_invarg; } - } else if (pp == &curwin->w_p_cole) { - if (curwin->w_p_cole < 0) { + } else if (pp == &curwin->w_p_cole + || pp == (long *)GLOBAL_WO(&curwin->w_p_cole)) { + if (value < 0) { errmsg = e_positive; - } else if (curwin->w_p_cole > 3) { + } else if (value > 3) { errmsg = e_invarg; } - } else if (pp == &curwin->w_p_nuw) { - if (curwin->w_p_nuw < 1) { + } else if (pp == &curwin->w_p_nuw + || pp == (long *)GLOBAL_WO(&curwin->w_p_nuw)) { + if (value < 1) { errmsg = e_positive; - } else if (curwin->w_p_nuw > 10) { + } else if (value > 10) { errmsg = e_invarg; } } else if (pp == &curbuf->b_p_iminsert || pp == &p_iminsert) { - if (*pp < 0 || *pp > B_IMODE_LAST) { + if (value < 0 || value > B_IMODE_LAST) { errmsg = e_invarg; } } else if (pp == &curbuf->b_p_imsearch || pp == &p_imsearch) { - if (*pp < -1 || *pp > B_IMODE_LAST) { + if (value < -1 || value > B_IMODE_LAST) { errmsg = e_invarg; } - } else if (pp == &curbuf->b_p_tw || pp == &p_tw) { - if (*pp < 0) { - errmsg = e_positive; - } } else if (pp == &curbuf->b_p_scbk || pp == &p_scbk) { - if (*pp < -1 || *pp > SB_MAX + if (value < -1 || value > SB_MAX || (opt_flags == OPT_LOCAL && !curbuf->terminal)) { errmsg = e_invarg; } } else if (pp == &curbuf->b_p_sw || pp == &p_sw) { - if (*pp < 0) { + if (value < 0) { errmsg = e_positive; } } else if (pp == &curbuf->b_p_ts || pp == &p_ts) { - if (*pp <= 0) { + if (value <= 0) { + errmsg = e_positive; + } + } else if (pp == &curbuf->b_p_tw || pp == &p_tw) { + if (value < 0) { errmsg = e_positive; } } - // If validation failed, reset to old value and return. + // Don't change the value and return early if validation failed. if (errmsg != NULL) { - *pp = old_value; return errmsg; } + *pp = value; + // Remember where the option was set. + set_option_scriptID_idx(opt_idx, opt_flags, current_SID); + // For these options we want to fix some invalid values. if (pp == &p_window) { if (p_window < 1) { @@ -4168,11 +4175,8 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, if (lastwin != firstwin && curwin->w_width < p_wiw) { win_setwidth((int)p_wiw); } - } else if (pp == &p_wmw) { - win_setminheight(); } else if (pp == &p_ls) { - // (re)set last window status line. - last_status(false); + last_status(false); // (re)set last window status line. } else if (pp == &p_stal) { // (re)set tab page line shell_new_rows(); // recompute window positions and heights @@ -4237,9 +4241,7 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, } - /* - * Check the bounds for numeric options here - */ + // Check the (new) bounds for Rows and Columns here. if (Rows < min_rows() && full_screen) { if (errbuf != NULL) { vim_snprintf((char *)errbuf, errbuflen, @@ -4259,19 +4261,17 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, limit_screen_size(); - /* - * If the screen (shell) height has been changed, assume it is the - * physical screenheight. - */ + // If the screen (shell) height has been changed, assume it is the + // physical screenheight. if (old_Rows != Rows || old_Columns != Columns) { - /* Changing the screen size is not allowed while updating the screen. */ + // Changing the screen size is not allowed while updating the screen. if (updating_screen) { *pp = old_value; } else if (full_screen) { screen_resize((int)Columns, (int)Rows); } else { - /* Postpone the resizing; check the size and cmdline position for - * messages. */ + // Postpone the resizing; check the size and cmdline position for + // messages. check_shellsize(); if (cmdline_row > Rows - p_ch && Rows > p_ch) { assert(p_ch >= 0 && Rows - p_ch <= INT_MAX); @@ -4308,9 +4308,10 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, } } - /* May set global value for local option. */ - if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) + // May set global value for local option. + if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) { *(long *)get_varp_scope(&(options[opt_idx]), OPT_GLOBAL) = *pp; + } if (pp == &curbuf->b_p_scbk && !curbuf->terminal) { // Normal buffer: reset local 'scrollback' after updating the global value. diff --git a/test/functional/options/num_options_spec.lua b/test/functional/options/num_options_spec.lua index 9b66d4f862..d0b63d3f91 100644 --- a/test/functional/options/num_options_spec.lua +++ b/test/functional/options/num_options_spec.lua @@ -37,6 +37,25 @@ describe(':set validation', function() it('setlocal and setglobal validate values', function() should_fail('shiftwidth', -10, 'E487') should_fail('tabstop', -10, 'E487') - should_fail('winheight', -10, 'E591') + should_fail('winheight', -10, 'E487') + should_fail('helpheight', -10, 'E487') + should_fail('maxcombine', 10, 'E474') + should_fail('history', 1000000, 'E474') + should_fail('regexpengine', 3, 'E474') + + should_fail('foldlevel', -5, 'E487') + should_fail('foldcolumn', 100, 'E474') + should_fail('conceallevel', 4, 'E474') + should_fail('numberwidth', 20, 'E474') + end) + + it('set wmh/wh wmw/wiw checks', function() + execute('set winheight=2') + execute('set winminheight=3') + eq('E591', eval("v:errmsg"):match("E%d*")) + + execute('set winwidth=2') + execute('set winminwidth=3') + eq('E592', eval("v:errmsg"):match("E%d*")) end) end) -- cgit From 8a55f9b1c8bab2cf22e266d7e1eecde85119d75d Mon Sep 17 00:00:00 2001 From: Jakob Schnitzer Date: Fri, 31 Mar 2017 18:30:06 +0200 Subject: update for changes in master; fix 'window'; tests --- src/nvim/option.c | 10 ++--- test/functional/options/num_options_spec.lua | 57 +++++++++++++++++++++------- 2 files changed, 48 insertions(+), 19 deletions(-) diff --git a/src/nvim/option.c b/src/nvim/option.c index d5bc3c1765..eddfdd6218 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -3997,7 +3997,7 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, // Many number options assume their value is in the signed int range. if (value < INT_MIN || value > INT_MAX) { - return e_invarg; + return (char *)e_invarg; } // Options that need some validation. @@ -4129,7 +4129,7 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, errmsg = e_positive; } } else if (pp == &curbuf->b_p_ts || pp == &p_ts) { - if (value <= 0) { + if (value < 1) { errmsg = e_positive; } } else if (pp == &curbuf->b_p_tw || pp == &p_tw) { @@ -4140,7 +4140,7 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, // Don't change the value and return early if validation failed. if (errmsg != NULL) { - return errmsg; + return (char *)errmsg; } *pp = value; @@ -4150,7 +4150,7 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, // For these options we want to fix some invalid values. if (pp == &p_window) { if (p_window < 1) { - p_window = 1; + p_window = Rows - 1; } else if (p_window >= Rows) { p_window = Rows - 1; } @@ -4188,7 +4188,7 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, if (foldmethodIsSyntax(curwin) || foldmethodIsIndent(curwin)) { foldUpdateAll(curwin); } - } else if (pp == &curbuf->b_p_sw || pp == (long *)&curbuf->b_p_ts) { + } else if (pp == &curbuf->b_p_sw || pp == &curbuf->b_p_ts) { // 'shiftwidth' or 'tabstop' if (foldmethodIsIndent(curwin)) { foldUpdateAll(curwin); diff --git a/test/functional/options/num_options_spec.lua b/test/functional/options/num_options_spec.lua index d0b63d3f91..620e758141 100644 --- a/test/functional/options/num_options_spec.lua +++ b/test/functional/options/num_options_spec.lua @@ -1,12 +1,8 @@ -- Tests for :setlocal and :setglobal local helpers = require('test.functional.helpers')(after_each) -local clear, execute, eval, eq, nvim = - helpers.clear, helpers.execute, helpers.eval, helpers.eq, helpers.nvim - -local function get_num_option_global(opt) - return nvim('command_output', 'setglobal ' .. opt .. '?'):match('%d+') -end +local clear, execute, eval, eq, meths = + helpers.clear, helpers.execute, helpers.eval, helpers.eq, helpers.meths local function should_fail(opt, value, errmsg) execute('let v:errmsg = ""') @@ -18,16 +14,23 @@ local function should_fail(opt, value, errmsg) execute('let v:errmsg = ""') end +local function should_succeed(opt, value) + execute('setglobal ' .. opt .. '=' .. value) + eq('', eval("v:errmsg")) + execute('setlocal ' .. opt .. '=' .. value) + eq('', eval("v:errmsg")) +end + describe(':setlocal', function() before_each(clear) it('setlocal sets only local value', function() - eq('0', get_num_option_global('iminsert')) + eq(0, meths.get_option('iminsert')) execute('setlocal iminsert=1') - eq('0', get_num_option_global('iminsert')) - eq('0', get_num_option_global('imsearch')) + eq(0, meths.get_option('iminsert')) + eq(0, meths.get_option('imsearch')) execute('setlocal imsearch=1') - eq('0', get_num_option_global('imsearch')) + eq(0, meths.get_option('imsearch')) end) end) @@ -36,17 +39,43 @@ describe(':set validation', function() it('setlocal and setglobal validate values', function() should_fail('shiftwidth', -10, 'E487') + should_succeed('shiftwidth', 0) should_fail('tabstop', -10, 'E487') should_fail('winheight', -10, 'E487') - should_fail('helpheight', -10, 'E487') - should_fail('maxcombine', 10, 'E474') + should_fail('winheight', 0, 'E487') + should_fail('winminheight', -1, 'E487') + should_succeed('winminheight', 0) + should_fail('winwidth', 0, 'E487') + should_fail('helpheight', -1, 'E487') + should_fail('maxcombine', 7, 'E474') + should_fail('iminsert', 3, 'E474') + should_fail('imsearch', 3, 'E474') + should_fail('titlelen', -1, 'E487') + should_fail('cmdheight', 0, 'E487') + should_fail('updatecount', -1, 'E487') + should_fail('textwidth', -1, 'E487') + should_fail('tabstop', 0, 'E487') + should_fail('timeoutlen', -1, 'E487') should_fail('history', 1000000, 'E474') + should_fail('regexpengine', -1, 'E474') should_fail('regexpengine', 3, 'E474') + should_succeed('regexpengine', 2) + should_fail('report', -1, 'E487') + should_succeed('report', 0) + should_fail('scrolloff', -1, 'E49') + should_fail('sidescrolloff', -1, 'E487') + should_fail('sidescroll', -1, 'E487') + should_fail('cmdwinheight', 0, 'E487') + should_fail('updatetime', -1, 'E487') should_fail('foldlevel', -5, 'E487') - should_fail('foldcolumn', 100, 'E474') + should_fail('foldcolumn', 13, 'E474') should_fail('conceallevel', 4, 'E474') - should_fail('numberwidth', 20, 'E474') + should_fail('numberwidth', 11, 'E474') + should_fail('numberwidth', 0, 'E487') + + -- If smaller than one this one is set to 'lines'-1 + should_succeed('window', -10) end) it('set wmh/wh wmw/wiw checks', function() -- cgit From 4049492b6d7b8805686b14dbacb3b729abd03308 Mon Sep 17 00:00:00 2001 From: Jakob Schnitzer Date: Sat, 1 Apr 2017 12:26:58 +0200 Subject: also test set_option --- test/functional/options/num_options_spec.lua | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/test/functional/options/num_options_spec.lua b/test/functional/options/num_options_spec.lua index 620e758141..42af4cb9b8 100644 --- a/test/functional/options/num_options_spec.lua +++ b/test/functional/options/num_options_spec.lua @@ -5,19 +5,23 @@ local clear, execute, eval, eq, meths = helpers.clear, helpers.execute, helpers.eval, helpers.eq, helpers.meths local function should_fail(opt, value, errmsg) - execute('let v:errmsg = ""') execute('setglobal ' .. opt .. '=' .. value) eq(errmsg, eval("v:errmsg"):match("E%d*")) execute('let v:errmsg = ""') execute('setlocal ' .. opt .. '=' .. value) eq(errmsg, eval("v:errmsg"):match("E%d*")) execute('let v:errmsg = ""') + local status, err = pcall(meths.set_option, opt, value) + eq(status, false) + eq(errmsg, err:match("E%d*")) + eq('', eval("v:errmsg")) end local function should_succeed(opt, value) execute('setglobal ' .. opt .. '=' .. value) - eq('', eval("v:errmsg")) execute('setlocal ' .. opt .. '=' .. value) + meths.set_option(opt, value) + eq(value, meths.get_option(opt)) eq('', eval("v:errmsg")) end @@ -74,8 +78,11 @@ describe(':set validation', function() should_fail('numberwidth', 11, 'E474') should_fail('numberwidth', 0, 'E487') - -- If smaller than one this one is set to 'lines'-1 - should_succeed('window', -10) + -- If smaller than 1 this one is set to 'lines'-1 + execute('setglobal window=-10') + meths.set_option('window', -10) + eq(23, meths.get_option('window')) + eq('', eval("v:errmsg")) end) it('set wmh/wh wmw/wiw checks', function() -- cgit From 4403864da3c48412595d439f36458d1e6ccfc49f Mon Sep 17 00:00:00 2001 From: Jakob Schnitzer Date: Wed, 28 Jun 2017 20:21:44 +0200 Subject: update tests --- test/functional/options/num_options_spec.lua | 30 ++++++++++++++-------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/test/functional/options/num_options_spec.lua b/test/functional/options/num_options_spec.lua index 42af4cb9b8..ed17ffdd3c 100644 --- a/test/functional/options/num_options_spec.lua +++ b/test/functional/options/num_options_spec.lua @@ -1,16 +1,16 @@ -- Tests for :setlocal and :setglobal local helpers = require('test.functional.helpers')(after_each) -local clear, execute, eval, eq, meths = - helpers.clear, helpers.execute, helpers.eval, helpers.eq, helpers.meths +local clear, feed_command, eval, eq, meths = + helpers.clear, helpers.feed_command, helpers.eval, helpers.eq, helpers.meths local function should_fail(opt, value, errmsg) - execute('setglobal ' .. opt .. '=' .. value) + feed_command('setglobal ' .. opt .. '=' .. value) eq(errmsg, eval("v:errmsg"):match("E%d*")) - execute('let v:errmsg = ""') - execute('setlocal ' .. opt .. '=' .. value) + feed_command('let v:errmsg = ""') + feed_command('setlocal ' .. opt .. '=' .. value) eq(errmsg, eval("v:errmsg"):match("E%d*")) - execute('let v:errmsg = ""') + feed_command('let v:errmsg = ""') local status, err = pcall(meths.set_option, opt, value) eq(status, false) eq(errmsg, err:match("E%d*")) @@ -18,8 +18,8 @@ local function should_fail(opt, value, errmsg) end local function should_succeed(opt, value) - execute('setglobal ' .. opt .. '=' .. value) - execute('setlocal ' .. opt .. '=' .. value) + feed_command('setglobal ' .. opt .. '=' .. value) + feed_command('setlocal ' .. opt .. '=' .. value) meths.set_option(opt, value) eq(value, meths.get_option(opt)) eq('', eval("v:errmsg")) @@ -30,10 +30,10 @@ describe(':setlocal', function() it('setlocal sets only local value', function() eq(0, meths.get_option('iminsert')) - execute('setlocal iminsert=1') + feed_command('setlocal iminsert=1') eq(0, meths.get_option('iminsert')) eq(0, meths.get_option('imsearch')) - execute('setlocal imsearch=1') + feed_command('setlocal imsearch=1') eq(0, meths.get_option('imsearch')) end) end) @@ -79,19 +79,19 @@ describe(':set validation', function() should_fail('numberwidth', 0, 'E487') -- If smaller than 1 this one is set to 'lines'-1 - execute('setglobal window=-10') + feed_command('setglobal window=-10') meths.set_option('window', -10) eq(23, meths.get_option('window')) eq('', eval("v:errmsg")) end) it('set wmh/wh wmw/wiw checks', function() - execute('set winheight=2') - execute('set winminheight=3') + feed_command('set winheight=2') + feed_command('set winminheight=3') eq('E591', eval("v:errmsg"):match("E%d*")) - execute('set winwidth=2') - execute('set winminwidth=3') + feed_command('set winwidth=2') + feed_command('set winminwidth=3') eq('E592', eval("v:errmsg"):match("E%d*")) end) end) -- cgit From 520c0b91a528e7943bc6163a65de7adde387869b Mon Sep 17 00:00:00 2001 From: ZyX Date: Fri, 29 Sep 2017 00:57:23 +0300 Subject: test/helpers: Add format_string and format_luav First intended to provide %r functionality like in Python (and also support for %*.*s, but this was not checked), second adds nice table formatting for use in cases similar to screen:snapshot_util(). --- test/helpers.lua | 79 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/test/helpers.lua b/test/helpers.lua index 260f10002e..d5356416af 100644 --- a/test/helpers.lua +++ b/test/helpers.lua @@ -307,6 +307,83 @@ local function dedent(str, leave_indent) return str end +local SUBTBL = { + '\\000', '\\001', '\\002', '\\003', '\\004', + '\\005', '\\006', '\\007', '\\008', '\\t', + '\\n', '\\011', '\\012', '\\r', '\\014', + '\\015', '\\016', '\\017', '\\018', '\\019', + '\\020', '\\021', '\\022', '\\023', '\\024', + '\\025', '\\026', '\\027', '\\028', '\\029', + '\\030', '\\031', +} + +local format_luav + +format_luav = function(v, indent) + local linesep = '\n' + local next_indent = nil + if indent == nil then + indent = '' + linesep = '' + else + next_indent = indent .. ' ' + end + local ret = '' + if type(v) == 'string' then + ret = '\'' .. tostring(v):gsub('[\'\\]', '\\%0'):gsub('[%z\1-\31]', function(match) + return SUBTBL[match:byte()] + end) .. '\'' + elseif type(v) == 'table' then + local processed_keys = {} + ret = '{' .. linesep + for i, subv in ipairs(v) do + ret = ret .. (next_indent or '') .. format_luav(subv, next_indent) .. ',\n' + processed_keys[i] = true + end + for k, subv in pairs(v) do + if not processed_keys[k] then + if type(k) == 'string' and k:match('^[a-zA-Z_][a-zA-Z0-9_]*$') then + ret = ret .. next_indent .. k .. ' = ' + else + ret = ret .. next_indent .. '[' .. format_luav(k) .. '] = ' + end + ret = ret .. format_luav(subv, next_indent) .. ',\n' + end + end + ret = ret .. indent .. '}' + else + -- Not implemented yet + assert(false) + end + return ret +end + +local function format_string(fmt, ...) + local i = 0 + local args = {...} + local function getarg() + i = i + 1 + return args[i] + end + local ret = fmt:gsub('%%[0-9*]*%.?[0-9*]*[cdEefgGiouXxqsr%%]', function(match) + local subfmt = match:gsub('%*', function(match) + return getarg() + end) + local arg = nil + if subfmt:sub(-1) ~= '%' then + arg = getarg() + end + if subfmt:sub(-1) == 'r' then + -- %r is like %q, but it is supposed to single-quote strings and not + -- double-quote them, and also work not only for strings. + subfmt = subfmt:sub(1, -2) .. 's' + arg = format_luav(arg) + end + return subfmt:format(arg) + end) + return ret +end + return { eq = eq, neq = neq, @@ -323,4 +400,6 @@ return { shallowcopy = shallowcopy, concat_tables = concat_tables, dedent = dedent, + format_luav = format_luav, + format_string = format_string, } -- cgit From 190c8516f534499f89d610fc78a20c025386b2f2 Mon Sep 17 00:00:00 2001 From: ZyX Date: Tue, 26 Sep 2017 23:13:55 +0300 Subject: unittests: Add a way to print trace on regular error --- test/README.md | 3 +++ test/unit/helpers.lua | 9 ++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/test/README.md b/test/README.md index 01db5960cd..15041b74e8 100644 --- a/test/README.md +++ b/test/README.md @@ -110,3 +110,6 @@ disables tracing (the fastest, but you get no data if tests crash and there was no core dump generated), `1` or empty/undefined leaves only C function cals and returns in the trace (faster then recording everything), `2` records all function calls, returns and lua source lines exuecuted. + +`NVIM_TEST_TRACE_ON_ERROR` (U) (1): makes unit tests yield trace on error in +addition to regular error message. diff --git a/test/unit/helpers.lua b/test/unit/helpers.lua index 4b9f185156..a629fca9a2 100644 --- a/test/unit/helpers.lua +++ b/test/unit/helpers.lua @@ -698,7 +698,14 @@ local function check_child_err(rd) local len_s = sc.read(rd, 5) local len = tonumber(len_s) neq(0, len) - local err = sc.read(rd, len + 1) + local err = '' + if os.getenv('NVIM_TEST_TRACE_ON_ERROR') == '1' and #trace ~= 0 then + err = '\nTest failed, trace:\n' .. tracehelp + for _, traceline in ipairs(trace) do + err = err .. traceline + end + end + err = err .. sc.read(rd, len + 1) assert.just_fail(err) end -- cgit From e479f3b944614f28c42ec597ec473652f3ac9912 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 10 Sep 2017 21:38:43 +0300 Subject: kvec: Add kv_drop() which is to be used like `(void)kv_pop(kvec)` --- src/nvim/lib/kvec.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/nvim/lib/kvec.h b/src/nvim/lib/kvec.h index 584282d773..4e5fb0d794 100644 --- a/src/nvim/lib/kvec.h +++ b/src/nvim/lib/kvec.h @@ -64,6 +64,14 @@ #define kv_max(v) ((v).capacity) #define kv_last(v) kv_A(v, kv_size(v) - 1) +/// Drop last n items from kvec without resizing +/// +/// Previously spelled as `(void)kv_pop(v)`, repeated n times. +/// +/// @param[out] v Kvec to drop items from. +/// @param[in] n Number of elements to drop. +#define kv_drop(v, n) ((v).size -= (n)) + #define kv_resize(v, s) \ ((v).capacity = (s), \ (v).items = xrealloc((v).items, sizeof((v).items[0]) * (v).capacity)) -- cgit From ad58e50b45ddb73f0582590b9e96da49f34174d0 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 8 Oct 2017 22:09:12 +0300 Subject: kvec: Add kv_Z which is like kv_A, but zero is the last value --- src/nvim/lib/kvec.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/nvim/lib/kvec.h b/src/nvim/lib/kvec.h index 4e5fb0d794..ee1b890cb9 100644 --- a/src/nvim/lib/kvec.h +++ b/src/nvim/lib/kvec.h @@ -62,7 +62,8 @@ #define kv_pop(v) ((v).items[--(v).size]) #define kv_size(v) ((v).size) #define kv_max(v) ((v).capacity) -#define kv_last(v) kv_A(v, kv_size(v) - 1) +#define kv_Z(v, i) kv_A(v, kv_size(v) - (i) - 1) +#define kv_last(v) kv_Z(v, 0) /// Drop last n items from kvec without resizing /// -- cgit From 0300c4d10991fb6ce218d45c4fe6d71a73f07d62 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 20 Aug 2017 18:40:22 +0300 Subject: viml/expressions: Add lexer with some basic tests --- src/nvim/CMakeLists.txt | 2 + src/nvim/viml/parser/expressions.c | 367 ++++++++++++++++++++++++++++++ src/nvim/viml/parser/expressions.h | 118 ++++++++++ src/nvim/viml/parser/parser.h | 129 +++++++++++ test/unit/viml/expressions/lexer_spec.lua | 337 +++++++++++++++++++++++++++ 5 files changed, 953 insertions(+) create mode 100644 src/nvim/viml/parser/expressions.c create mode 100644 src/nvim/viml/parser/expressions.h create mode 100644 src/nvim/viml/parser/parser.h create mode 100644 test/unit/viml/expressions/lexer_spec.lua diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index 688912eda6..77e4721853 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -81,6 +81,8 @@ foreach(subdir event eval lua + viml + viml/parser ) if(${subdir} MATCHES "tui" AND NOT FEAT_TUI) continue() diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c new file mode 100644 index 0000000000..0164de3a14 --- /dev/null +++ b/src/nvim/viml/parser/expressions.c @@ -0,0 +1,367 @@ +// This is an open source non-commercial project. Dear PVS-Studio, please check +// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + +/// VimL expression parser + +#include +#include +#include +#include + +#include "nvim/vim.h" +#include "nvim/memory.h" +#include "nvim/types.h" +#include "nvim/charset.h" +#include "nvim/ascii.h" + +#include "nvim/viml/parser/expressions.h" +#include "nvim/viml/parser/parser.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "viml/parser/expressions.c.generated.h" +#endif + +/// Character used as a separator in autoload function/variable names. +#define AUTOLOAD_CHAR '#' + +/// Get next token for the VimL expression input +LexExprToken viml_pexpr_next_token(ParserState *const pstate) + FUNC_ATTR_WARN_UNUSED_RESULT +{ + LexExprToken ret = { + .type = kExprLexInvalid, + .start = pstate->pos, + }; + ParserLine pline; + if (!viml_parser_get_remaining_line(pstate, &pline)) { + ret.type = kExprLexEOC; + return ret; + } + if (pline.size <= 0) { + ret.len = 0; + ret.type = kExprLexEOC; + goto viml_pexpr_next_token_adv_return; + } + ret.len = 1; + const uint8_t schar = (uint8_t)pline.data[0]; +#define GET_CCS(ret, pline) \ + do { \ + if (ret.len < pline.size \ + && strchr("?#", pline.data[ret.len]) != NULL) { \ + ret.data.cmp.ccs = \ + (CaseCompareStrategy)pline.data[ret.len]; \ + ret.len++; \ + } else { \ + ret.data.cmp.ccs = kCCStrategyUseOption; \ + } \ + } while (0) + switch (schar) { + // Paired brackets. +#define BRACKET(typ, opning, clsing) \ + case opning: \ + case clsing: { \ + ret.type = typ; \ + ret.data.brc.closing = (schar == clsing); \ + break; \ + } + BRACKET(kExprLexParenthesis, '(', ')') + BRACKET(kExprLexBracket, '[', ']') + BRACKET(kExprLexFigureBrace, '{', '}') +#undef BRACKET + + // Single character tokens without data. +#define CHAR(typ, ch) \ + case ch: { \ + ret.type = typ; \ + break; \ + } + CHAR(kExprLexQuestion, '?') + CHAR(kExprLexColon, ':') + CHAR(kExprLexDot, '.') + CHAR(kExprLexPlus, '+') + CHAR(kExprLexComma, ',') +#undef CHAR + + // Multiplication/division/modulo. +#define MUL(mul_type, ch) \ + case ch: { \ + ret.type = kExprLexMultiplication; \ + ret.data.mul.type = mul_type; \ + break; \ + } + MUL(kExprLexMulMul, '*') + MUL(kExprLexMulDiv, '/') + MUL(kExprLexMulMod, '%') +#undef MUL + +#define CHARREG(typ, cond) \ + do { \ + ret.type = typ; \ + for (; (ret.len < pline.size \ + && cond(pline.data[ret.len])) \ + ; ret.len++) { \ + } \ + } while (0) + + // Whitespace. + case ' ': + case TAB: { + CHARREG(kExprLexSpacing, ascii_iswhite); + break; + } + + // Control character, except for NUL, NL and TAB. + case Ctrl_A: case Ctrl_B: case Ctrl_C: case Ctrl_D: case Ctrl_E: + case Ctrl_F: case Ctrl_G: case Ctrl_H: + + case Ctrl_K: case Ctrl_L: case Ctrl_M: case Ctrl_N: case Ctrl_O: + case Ctrl_P: case Ctrl_Q: case Ctrl_R: case Ctrl_S: case Ctrl_T: + case Ctrl_U: case Ctrl_V: case Ctrl_W: case Ctrl_X: case Ctrl_Y: + case Ctrl_Z: { +#define ISCTRL(schar) (schar < ' ') + CHARREG(kExprLexInvalid, ISCTRL); + ret.data.err.type = kExprLexSpacing; + ret.data.err.msg = + _("E15: Invalid control character present in input: %.*s"); + break; +#undef ISCTRL + } + + // Number. + // Note: determining whether dot is (not) a part of a float needs more + // context, so lexer does not do this. + // FIXME: Resolve ambiguity by additional argument. + case '0': case '1': case '2': case '3': case '4': case '5': case '6': + case '7': case '8': case '9': { + CHARREG(kExprLexNumber, ascii_isdigit); + break; + } + + // Environment variable. + case '$': { + CHARREG(kExprLexEnv, vim_isIDc); + break; + } + + // Normal variable/function name. + case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': + case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': + case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': + case 'v': case 'w': case 'x': case 'y': case 'z': + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': + case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': + case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': + case 'V': case 'W': case 'X': case 'Y': case 'Z': + case '_': { +#define ISWORD_OR_AUTOLOAD(x) \ + (ASCII_ISALNUM(x) || (x) == AUTOLOAD_CHAR || (x) == '_') +#define ISWORD(x) \ + (ASCII_ISALNUM(x) || (x) == '_') + ret.data.var.scope = 0; + ret.data.var.autoload = false; + CHARREG(kExprLexPlainIdentifier, ISWORD); + // "is" and "isnot" operators. + if ((ret.len == 2 && memcmp(pline.data, "is", 2) == 0) + || (ret.len == 5 && memcmp(pline.data, "isnot", 5) == 0)) { + ret.type = kExprLexComparison; + ret.data.cmp.type = kExprLexCmpIdentical; + ret.data.cmp.inv = (ret.len == 5); + GET_CCS(ret, pline); + // Scope: `s:`, etc. + } else if (ret.len == 1 + && pline.size > 1 + && strchr("sgvbwtla", schar) != NULL + && pline.data[ret.len] == ':') { + ret.len++; + ret.data.var.scope = schar; + CHARREG(kExprLexPlainIdentifier, ISWORD_OR_AUTOLOAD); + ret.data.var.autoload = ( + memchr(pline.data + 2, AUTOLOAD_CHAR, ret.len - 2) + != NULL); + // Previous CHARREG stopped at autoload character in order to make it + // possible to detect `is#`. Continue now with autoload characters + // included. + // + // Warning: there is ambiguity for the lexer: `is#Foo(1)` is a call of + // function `is#Foo()`, `1is#Foo(1)` is a comparison `1 is# Foo(1)`. This + // needs to be resolved on the higher level where context is available. + } else if (pline.size > ret.len + && pline.data[ret.len] == AUTOLOAD_CHAR) { + ret.data.var.autoload = true; + CHARREG(kExprLexPlainIdentifier, ISWORD_OR_AUTOLOAD); + } + break; +#undef ISWORD_OR_AUTOLOAD +#undef ISWORD + } +#undef CHARREG + + // Option. + case '&': { +#define OPTNAMEMISS(ret) \ + do { \ + ret.type = kExprLexInvalid; \ + ret.data.err.type = kExprLexOption; \ + ret.data.err.msg = _("E112: Option name missing: %.*s"); \ + } while (0) + if (pline.size > 1 && pline.data[1] == '&') { + ret.type = kExprLexAnd; + ret.len++; + break; + } + if (pline.size == 1 || !ASCII_ISALPHA(pline.data[1])) { + OPTNAMEMISS(ret); + break; + } + ret.type = kExprLexOption; + if (pline.size > 2 + && pline.data[2] == ':' + && strchr("gl", pline.data[1]) != NULL) { + ret.len += 2; + ret.data.opt.scope = (pline.data[1] == 'g' + ? kExprLexOptGlobal + : kExprLexOptLocal); + ret.data.opt.name = pline.data + 3; + } else { + ret.data.opt.scope = kExprLexOptUnspecified; + ret.data.opt.name = pline.data + 1; + } + const char *p = ret.data.opt.name; + const char *const e = pline.data + pline.size; + if (e - p >= 4 && p[0] == 't' && p[1] == '_') { + ret.data.opt.len = 4; + ret.len += 4; + } else { + for (; p < e && ASCII_ISALPHA(*p); p++) { + } + ret.data.opt.len = (size_t)(p - ret.data.opt.name); + if (ret.data.opt.len == 0) { + OPTNAMEMISS(ret); + } else { + ret.len += ret.data.opt.len; + } + } + break; +#undef OPTNAMEMISS + } + + // Register. + case '@': { + ret.type = kExprLexRegister; + if (pline.size > 1) { + ret.len++; + ret.data.reg.name = (uint8_t)pline.data[1]; + } else { + ret.data.reg.name = -1; + } + break; + } + + // Single quoted string. + case '\'': { + ret.type = kExprLexSingleQuotedString; + ret.data.str.closed = false; + for (; ret.len < pline.size && !ret.data.str.closed; ret.len++) { + if (pline.data[ret.len] == '\'') { + if (ret.len + 1 < pline.size && pline.data[ret.len + 1] == '\'') { + ret.len++; + } else { + ret.data.str.closed = true; + } + } + } + break; + } + + // Double quoted string. + case '"': { + ret.type = kExprLexDoubleQuotedString; + ret.data.str.closed = false; + for (; ret.len < pline.size && !ret.data.str.closed; ret.len++) { + if (pline.data[ret.len] == '\\') { + if (ret.len + 1 < pline.size) { + ret.len++; + } + } else if (pline.data[ret.len] == '"') { + ret.data.str.closed = true; + } + } + break; + } + + // Unary not, (un)equality and regex (not) match comparison operators. + case '!': + case '=': { + if (pline.size == 1) { +viml_pexpr_next_token_invalid_comparison: + ret.type = (schar == '!' ? kExprLexNot : kExprLexInvalid); + if (ret.type == kExprLexInvalid) { + ret.data.err.msg = _("E15: Expected == or =~: %.*s"); + ret.data.err.type = kExprLexComparison; + } + break; + } + ret.type = kExprLexComparison; + ret.data.cmp.inv = (schar == '!'); + if (pline.data[1] == '=') { + ret.data.cmp.type = kExprLexCmpEqual; + ret.len++; + } else if (pline.data[1] == '~') { + ret.data.cmp.type = kExprLexCmpMatches; + ret.len++; + } else { + goto viml_pexpr_next_token_invalid_comparison; + } + GET_CCS(ret, pline); + break; + } + + // Less/greater [or equal to] comparison operators. + case '>': + case '<': { + ret.type = kExprLexComparison; + const bool haseqsign = (pline.size > 1 && pline.data[1] == '='); + if (haseqsign) { + ret.len++; + } + GET_CCS(ret, pline); + ret.data.cmp.inv = (schar == '<'); + ret.data.cmp.type = ((ret.data.cmp.inv ^ haseqsign) + ? kExprLexCmpGreaterOrEqual + : kExprLexCmpGreater); + break; + } + + // Minus sign or arrow from lambdas. + case '-': { + if (pline.size > 1 && pline.data[1] == '>') { + ret.len++; + ret.type = kExprLexArrow; + } else { + ret.type = kExprLexMinus; + } + break; + } + + // Expression end because Ex command ended. + case NUL: + case NL: { + ret.type = kExprLexEOC; + break; + } + + // Everything else is not valid. + default: { + ret.len = (size_t)utfc_ptr2len_len((const char_u *)pline.data, + (int)pline.size); + ret.type = kExprLexInvalid; + ret.data.err.type = kExprLexPlainIdentifier; + ret.data.err.msg = _("E15: Unidentified character: %.*s"); + break; + } + } +#undef GET_CCS +viml_pexpr_next_token_adv_return: + viml_parser_advance(pstate, ret.len); + return ret; +} diff --git a/src/nvim/viml/parser/expressions.h b/src/nvim/viml/parser/expressions.h new file mode 100644 index 0000000000..52354760a5 --- /dev/null +++ b/src/nvim/viml/parser/expressions.h @@ -0,0 +1,118 @@ +#ifndef NVIM_VIML_PARSER_EXPRESSIONS_H +#define NVIM_VIML_PARSER_EXPRESSIONS_H + +#include +#include + +#include "nvim/types.h" +#include "nvim/viml/parser/parser.h" + +// Defines whether to ignore case: +// == kCCStrategyUseOption +// ==# kCCStrategyMatchCase +// ==? kCCStrategyIgnoreCase +typedef enum { + kCCStrategyUseOption = 0, // 0 for xcalloc + kCCStrategyMatchCase = '#', + kCCStrategyIgnoreCase = '?', +} CaseCompareStrategy; + +/// Lexer token type +typedef enum { + kExprLexInvalid = 0, ///< Invalid token, indicaten an error. + kExprLexMissing, ///< Missing token, for use in parser. + kExprLexSpacing, ///< Spaces, tabs, newlines, etc. + kExprLexEOC, ///< End of command character: NL, |, just end of stream. + + kExprLexQuestion, ///< Question mark, for use in ternary. + kExprLexColon, ///< Colon, for use in ternary. + kExprLexOr, ///< Logical or operator. + kExprLexAnd, ///< Logical and operator. + kExprLexComparison, ///< One of the comparison operators. + kExprLexPlus, ///< Plus sign. + kExprLexMinus, ///< Minus sign. + kExprLexDot, ///< Dot: either concat or subscript, also part of the float. + kExprLexMultiplication, ///< Multiplication, division or modulo operator. + + kExprLexNot, ///< Not: !. + + kExprLexNumber, ///< Integer number literal, or part of a float. + kExprLexSingleQuotedString, ///< Single quoted string literal. + kExprLexDoubleQuotedString, ///< Double quoted string literal. + kExprLexOption, ///< &optionname option value. + kExprLexRegister, ///< @r register value. + kExprLexEnv, ///< Environment $variable value. + kExprLexPlainIdentifier, ///< Identifier without scope: `abc`, `foo#bar`. + + kExprLexBracket, ///< Bracket, either opening or closing. + kExprLexFigureBrace, ///< Figure brace, either opening or closing. + kExprLexParenthesis, ///< Parenthesis, either opening or closing. + kExprLexComma, ///< Comma. + kExprLexArrow, ///< Arrow, like from lambda expressions. +} LexExprTokenType; + +/// Lexer token +typedef struct { + ParserPosition start; + size_t len; + LexExprTokenType type; + union { + struct { + enum { + kExprLexCmpEqual, ///< Equality, unequality. + kExprLexCmpMatches, ///< Matches regex, not matches regex. + kExprLexCmpGreater, ///< `>` or `<=` + kExprLexCmpGreaterOrEqual, ///< `>=` or `<`. + kExprLexCmpIdentical, ///< `is` or `isnot` + } type; ///< Comparison type. + CaseCompareStrategy ccs; ///< Case comparison strategy. + bool inv; ///< True if comparison is to be inverted. + } cmp; ///< For kExprLexComparison. + + struct { + enum { + kExprLexMulMul, ///< Real multiplication. + kExprLexMulDiv, ///< Division. + kExprLexMulMod, ///< Modulo. + } type; ///< Multiplication type. + } mul; ///< For kExprLexMultiplication. + + struct { + bool closing; ///< True if bracket/etc is a closing one. + } brc; ///< For brackets/braces/parenthesis. + + struct { + int name; ///< Register name, may be -1 if name not present. + } reg; ///< For kExprLexRegister. + + struct { + bool closed; ///< True if quote was closed. + } str; ///< For kExprLexSingleQuotedString and kExprLexDoubleQuotedString. + + struct { + const char *name; ///< Option name start. + size_t len; ///< Option name length. + enum { + kExprLexOptUnspecified = 0, + kExprLexOptGlobal = 1, + kExprLexOptLocal = 2, + } scope; ///< Option scope: &l:, &g: or not specified. + } opt; ///< Option properties. + + struct { + int scope; ///< Scope character or 0 if not present. + bool autoload; ///< Has autoload characters. + } var; ///< For kExprLexPlainIdentifier + + struct { + LexExprTokenType type; ///< Suggested type for parsing incorrect code. + const char *msg; ///< Error message. + } err; ///< For kExprLexInvalid + } data; ///< Additional data, if needed. +} LexExprToken; + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "viml/parser/expressions.h.generated.h" +#endif + +#endif // NVIM_VIML_PARSER_EXPRESSIONS_H diff --git a/src/nvim/viml/parser/parser.h b/src/nvim/viml/parser/parser.h new file mode 100644 index 0000000000..ec582294e1 --- /dev/null +++ b/src/nvim/viml/parser/parser.h @@ -0,0 +1,129 @@ +#ifndef NVIM_VIML_PARSER_PARSER_H +#define NVIM_VIML_PARSER_PARSER_H + +#include +#include +#include + +#include "nvim/lib/kvec.h" +#include "nvim/func_attr.h" + +/// One parsed line +typedef struct { + const char *data; ///< Parsed line pointer + size_t size; ///< Parsed line size +} ParserLine; + +/// Line getter type for parser +/// +/// Line getter must return {NULL, 0} for EOF. +typedef void (*ParserLineGetter)(void *cookie, ParserLine *ret_pline); + +/// Parser position in the input +typedef struct { + size_t line; ///< Line index in ParserInputReader.lines. + size_t col; ///< Byte index in the line. +} ParserPosition; + +/// Parser state item. +typedef struct { + enum { + kPTopStateParsingCommand = 0, + kPTopStateParsingExpression, + } type; + union { + struct { + enum { + kExprUnknown = 0, + } type; + } expr; + } data; +} ParserStateItem; + +/// Structure defining input reader +typedef struct { + /// Function used to get next line. + ParserLineGetter get_line; + /// Data for get_line function. + void *cookie; + /// All lines obtained by get_line. + kvec_withinit_t(ParserLine, 4) lines; +} ParserInputReader; + +/// Highlighted region definition +/// +/// Note: one chunk may highlight only one line. +typedef struct { + ParserPosition start; ///< Start of the highlight: line and column. + size_t end_col; ///< End column, points to the start of the next character. + const char *group; ///< Highlight group. +} ParserHighlightChunk; + +/// Highlighting defined by a parser +typedef kvec_withinit_t(ParserHighlightChunk, 16) ParserHighlight; + +/// Structure defining parser state +typedef struct { + /// Line reader. + ParserInputReader reader; + /// Position up to which input was parsed. + ParserPosition pos; + /// Parser state stack. + kvec_withinit_t(ParserStateItem, 16) stack; + /// Highlighting support. + ParserHighlight *colors; + /// True if line continuation can be used. + bool can_continuate; +} ParserState; + +static inline bool viml_parser_get_remaining_line(ParserState *const pstate, + ParserLine *const ret_pline) + REAL_FATTR_ALWAYS_INLINE REAL_FATTR_WARN_UNUSED_RESULT REAL_FATTR_NONNULL_ALL; + +/// Get currently parsed line, shifted to pstate->pos.col +/// +/// @param pstate Parser state to operate on. +/// +/// @return True if there is a line, false in case of EOF. +static inline bool viml_parser_get_remaining_line(ParserState *const pstate, + ParserLine *const ret_pline) +{ + const size_t num_lines = kv_size(pstate->reader.lines); + if (pstate->pos.line == num_lines) { + pstate->reader.get_line(pstate->reader.cookie, ret_pline); + kvi_push(pstate->reader.lines, *ret_pline); + } else { + *ret_pline = kv_last(pstate->reader.lines); + } + assert(pstate->pos.line == kv_size(pstate->reader.lines) - 1); + if (ret_pline->data != NULL) { + ret_pline->data += pstate->pos.col; + ret_pline->size -= pstate->pos.col; + } + return ret_pline->data != NULL; +} + +static inline void viml_parser_advance(ParserState *const pstate, + const size_t len) + REAL_FATTR_ALWAYS_INLINE REAL_FATTR_NONNULL_ALL; + +/// Advance position by a given number of bytes +/// +/// At maximum advances to the next line. +/// +/// @param pstate Parser state to advance. +/// @param[in] len Number of bytes to advance. +static inline void viml_parser_advance(ParserState *const pstate, + const size_t len) +{ + assert(pstate->pos.line == kv_size(pstate->reader.lines) - 1); + const ParserLine pline = kv_last(pstate->reader.lines); + if (pstate->pos.col + len >= pline.size) { + pstate->pos.line++; + pstate->pos.col = 0; + } else { + pstate->pos.col += len; + } +} + +#endif // NVIM_VIML_PARSER_PARSER_H diff --git a/test/unit/viml/expressions/lexer_spec.lua b/test/unit/viml/expressions/lexer_spec.lua new file mode 100644 index 0000000000..bf5afe4eeb --- /dev/null +++ b/test/unit/viml/expressions/lexer_spec.lua @@ -0,0 +1,337 @@ +local helpers = require('test.unit.helpers')(after_each) +local itp = helpers.gen_itp(it) + +local child_call_once = helpers.child_call_once +local cimport = helpers.cimport +local ffi = helpers.ffi +local eq = helpers.eq + +local lib = cimport('./src/nvim/viml/parser/expressions.h') + +local eltkn_type_tab, eltkn_cmp_type_tab, ccs_tab, eltkn_mul_type_tab +local eltkn_opt_scope_tab +child_call_once(function() + eltkn_type_tab = { + [tonumber(lib.kExprLexInvalid)] = 'Invalid', + [tonumber(lib.kExprLexMissing)] = 'Missing', + [tonumber(lib.kExprLexSpacing)] = 'Spacing', + [tonumber(lib.kExprLexEOC)] = 'EOC', + + [tonumber(lib.kExprLexQuestion)] = 'Question', + [tonumber(lib.kExprLexColon)] = 'Colon', + [tonumber(lib.kExprLexOr)] = 'Or', + [tonumber(lib.kExprLexAnd)] = 'And', + [tonumber(lib.kExprLexComparison)] = 'Comparison', + [tonumber(lib.kExprLexPlus)] = 'Plus', + [tonumber(lib.kExprLexMinus)] = 'Minus', + [tonumber(lib.kExprLexDot)] = 'Dot', + [tonumber(lib.kExprLexMultiplication)] = 'Multiplication', + + [tonumber(lib.kExprLexNot)] = 'Not', + + [tonumber(lib.kExprLexNumber)] = 'Number', + [tonumber(lib.kExprLexSingleQuotedString)] = 'SingleQuotedString', + [tonumber(lib.kExprLexDoubleQuotedString)] = 'DoubleQuotedString', + [tonumber(lib.kExprLexOption)] = 'Option', + [tonumber(lib.kExprLexRegister)] = 'Register', + [tonumber(lib.kExprLexEnv)] = 'Env', + [tonumber(lib.kExprLexPlainIdentifier)] = 'PlainIdentifier', + + [tonumber(lib.kExprLexBracket)] = 'Bracket', + [tonumber(lib.kExprLexFigureBrace)] = 'FigureBrace', + [tonumber(lib.kExprLexParenthesis)] = 'Parenthesis', + [tonumber(lib.kExprLexComma)] = 'Comma', + [tonumber(lib.kExprLexArrow)] = 'Arrow', + } + + eltkn_cmp_type_tab = { + [tonumber(lib.kExprLexCmpEqual)] = 'Equal', + [tonumber(lib.kExprLexCmpMatches)] = 'Matches', + [tonumber(lib.kExprLexCmpGreater)] = 'Greater', + [tonumber(lib.kExprLexCmpGreaterOrEqual)] = 'GreaterOrEqual', + [tonumber(lib.kExprLexCmpIdentical)] = 'Identical', + } + + ccs_tab = { + [tonumber(lib.kCCStrategyUseOption)] = 'UseOption', + [tonumber(lib.kCCStrategyMatchCase)] = 'MatchCase', + [tonumber(lib.kCCStrategyIgnoreCase)] = 'IgnoreCase', + } + + eltkn_mul_type_tab = { + [tonumber(lib.kExprLexMulMul)] = 'Mul', + [tonumber(lib.kExprLexMulDiv)] = 'Div', + [tonumber(lib.kExprLexMulMod)] = 'Mod', + } + + eltkn_opt_scope_tab = { + [tonumber(lib.kExprLexOptUnspecified)] = 'Unspecified', + [tonumber(lib.kExprLexOptGlobal)] = 'Global', + [tonumber(lib.kExprLexOptLocal)] = 'Local', + } +end) + +local function array_size(arr) + return ffi.sizeof(arr) / ffi.sizeof(arr[0]) +end + +local function kvi_size(kvi) + return array_size(kvi.init_array) +end + +local function kvi_init(kvi) + kvi.capacity = kvi_size(kvi) + kvi.items = kvi.init_array + return kvi +end + +local function kvi_new(ct) + return kvi_init(ffi.new(ct)) +end + +local function new_pstate(strings) + local strings_idx = 0 + local function get_line(_, ret_pline) + strings_idx = strings_idx + 1 + local str = strings[strings_idx] + local data, size + if type(str) == 'string' then + data = str + size = #str + elseif type(str) == 'nil' then + data = nil + size = 0 + elseif type(str) == 'table' then + data = str.data + size = str.size + elseif type(str) == 'function' then + data, size = str() + size = size or 0 + end + ret_pline.data = data + ret_pline.size = size + end + local pline_init = { + data = nil, + size = 0, + } + local state = { + reader = { + get_line = get_line, + cookie = nil, + }, + pos = { line = 0, col = 0 }, + colors = kvi_new('ParserHighlight'), + can_continuate = false, + } + local ret = ffi.new('ParserState', state) + kvi_init(ret.reader.lines) + kvi_init(ret.stack) + return ret +end + +local function conv_enum(etab, eval) + local n = tonumber(eval) + return etab[n] or n +end + +local function conv_eltkn_type(typ) + return conv_enum(eltkn_type_tab, typ) +end + +local function pline2lua(pline) + return ffi.string(pline.data, pline.size) +end + +local bracket_types = { + Bracket = true, + FigureBrace = true, + Parenthesis = true, +} + +local function intchar2lua(ch) + ch = tonumber(ch) + return (20 <= ch and ch < 127) and ('%c'):format(ch) or ch +end + +local function eltkn2lua(pstate, tkn) + local ret = { + type = conv_eltkn_type(tkn.type), + len = tonumber(tkn.len), + start = { line = tonumber(tkn.start.line), col = tonumber(tkn.start.col) }, + } + if ret.start.line < pstate.reader.lines.size then + local pstr = pline2lua(pstate.reader.lines.items[ret.start.line]) + if ret.start.col >= #pstr then + ret.error = 'start.col >= #pstr' + else + ret.str = pstr:sub(ret.start.col + 1, ret.start.col + ret.len) + if #(ret.str) ~= ret.len then + ret.error = '#str /= len' + end + end + else + ret.error = 'start.line >= pstate.reader.lines.size' + end + if ret.type == 'Comparison' then + ret.data = { + type = conv_enum(eltkn_cmp_type_tab, tkn.data.cmp.type), + ccs = conv_enum(ccs_tab, tkn.data.cmp.ccs), + inv = (not not tkn.data.cmp.inv), + } + elseif ret.type == 'Multiplication' then + ret.data = { type = conv_enum(eltkn_mul_type_tab, tkn.data.mul.type) } + elseif bracket_types[ret.type] then + ret.data = { closing = (not not tkn.data.brc.closing) } + elseif ret.type == 'Register' then + ret.data = { name = intchar2lua(tkn.data.reg.name) } + elseif (ret.type == 'SingleQuotedString' + or ret.type == 'DoubleQuotedString') then + ret.data = { closed = (not not tkn.data.str.closed) } + elseif ret.type == 'Option' then + ret.data = { + scope = conv_enum(eltkn_opt_scope_tab, tkn.data.opt.scope), + name = ffi.string(tkn.data.opt.name, tkn.data.opt.len), + } + elseif ret.type == 'PlainIdentifier' then + ret.data = { + scope = intchar2lua(tkn.data.var.scope), + autoload = (not not tkn.data.var.autoload), + } + elseif ret.type == 'Invalid' then + ret.data = { error = ffi.string(tkn.data.err.msg) } + end + return ret, tkn +end + +local function next_eltkn(pstate) + return eltkn2lua(pstate, lib.viml_pexpr_next_token(pstate, false)) +end + +describe('Expressions lexer', function() + itp('works (single tokens)', function() + local function singl_eltkn_test(typ, str, data) + local pstate = new_pstate({str}) + eq({data=data, len=#str, start={col=0, line=0}, str=str, type=typ}, + next_eltkn(pstate)) + if not ( + typ == 'Spacing' + or (typ == 'Register' and str == '@') + or ((typ == 'SingleQuotedString' or typ == 'DoubleQuotedString') + and not data.closed) + ) then + pstate = new_pstate({str .. ' '}) + eq({data=data, len=#str, start={col=0, line=0}, str=str, type=typ}, + next_eltkn(pstate)) + end + pstate = new_pstate({'x' .. str}) + pstate.pos.col = 1 + eq({data=data, len=#str, start={col=1, line=0}, str=str, type=typ}, + next_eltkn(pstate)) + end + singl_eltkn_test('Parenthesis', '(', {closing=false}) + singl_eltkn_test('Parenthesis', ')', {closing=true}) + singl_eltkn_test('Bracket', '[', {closing=false}) + singl_eltkn_test('Bracket', ']', {closing=true}) + singl_eltkn_test('FigureBrace', '{', {closing=false}) + singl_eltkn_test('FigureBrace', '}', {closing=true}) + singl_eltkn_test('Question', '?') + singl_eltkn_test('Colon', ':') + singl_eltkn_test('Dot', '.') + singl_eltkn_test('Plus', '+') + singl_eltkn_test('Comma', ',') + singl_eltkn_test('Multiplication', '*', {type='Mul'}) + singl_eltkn_test('Multiplication', '/', {type='Div'}) + singl_eltkn_test('Multiplication', '%', {type='Mod'}) + singl_eltkn_test('Spacing', ' \t\t \t\t') + singl_eltkn_test('Spacing', ' ') + singl_eltkn_test('Spacing', '\t') + singl_eltkn_test('Invalid', '\x01\x02\x03', {error='E15: Invalid control character present in input: %.*s'}) + singl_eltkn_test('Number', '0123') + singl_eltkn_test('Number', '0') + singl_eltkn_test('Number', '9') + singl_eltkn_test('Env', '$abc') + singl_eltkn_test('Env', '$') + singl_eltkn_test('PlainIdentifier', 'test', {autoload=false, scope=0}) + singl_eltkn_test('PlainIdentifier', '_test', {autoload=false, scope=0}) + singl_eltkn_test('PlainIdentifier', '_test_foo', {autoload=false, scope=0}) + singl_eltkn_test('PlainIdentifier', 't', {autoload=false, scope=0}) + singl_eltkn_test('PlainIdentifier', 'test5', {autoload=false, scope=0}) + singl_eltkn_test('PlainIdentifier', 't0', {autoload=false, scope=0}) + singl_eltkn_test('PlainIdentifier', 'test#var', {autoload=true, scope=0}) + singl_eltkn_test('PlainIdentifier', 'test#var#val###', {autoload=true, scope=0}) + singl_eltkn_test('PlainIdentifier', 't#####', {autoload=true, scope=0}) + local function scope_test(scope) + singl_eltkn_test('PlainIdentifier', scope .. ':test#var', {autoload=true, scope=scope}) + singl_eltkn_test('PlainIdentifier', scope .. ':', {autoload=false, scope=scope}) + end + scope_test('s') + scope_test('g') + scope_test('v') + scope_test('b') + scope_test('w') + scope_test('t') + scope_test('l') + scope_test('a') + local function comparison_test(op, inv_op, cmp_type) + singl_eltkn_test('Comparison', op, {type=cmp_type, inv=false, ccs='UseOption'}) + singl_eltkn_test('Comparison', inv_op, {type=cmp_type, inv=true, ccs='UseOption'}) + singl_eltkn_test('Comparison', op .. '#', {type=cmp_type, inv=false, ccs='MatchCase'}) + singl_eltkn_test('Comparison', inv_op .. '#', {type=cmp_type, inv=true, ccs='MatchCase'}) + singl_eltkn_test('Comparison', op .. '?', {type=cmp_type, inv=false, ccs='IgnoreCase'}) + singl_eltkn_test('Comparison', inv_op .. '?', {type=cmp_type, inv=true, ccs='IgnoreCase'}) + end + comparison_test('is', 'isnot', 'Identical') + singl_eltkn_test('And', '&&') + singl_eltkn_test('Invalid', '&', {error='E112: Option name missing: %.*s'}) + singl_eltkn_test('Option', '&opt', {scope='Unspecified', name='opt'}) + singl_eltkn_test('Option', '&t_xx', {scope='Unspecified', name='t_xx'}) + singl_eltkn_test('Option', '&t_\r\r', {scope='Unspecified', name='t_\r\r'}) + singl_eltkn_test('Option', '&t_\t\t', {scope='Unspecified', name='t_\t\t'}) + singl_eltkn_test('Option', '&t_ ', {scope='Unspecified', name='t_ '}) + singl_eltkn_test('Option', '&g:opt', {scope='Global', name='opt'}) + singl_eltkn_test('Option', '&l:opt', {scope='Local', name='opt'}) + singl_eltkn_test('Invalid', '&l:', {error='E112: Option name missing: %.*s'}) + singl_eltkn_test('Invalid', '&g:', {error='E112: Option name missing: %.*s'}) + singl_eltkn_test('Register', '@', {name=-1}) + singl_eltkn_test('Register', '@a', {name='a'}) + singl_eltkn_test('Register', '@\r', {name=13}) + singl_eltkn_test('Register', '@ ', {name=' '}) + singl_eltkn_test('Register', '@\t', {name=9}) + singl_eltkn_test('SingleQuotedString', '\'test', {closed=false}) + singl_eltkn_test('SingleQuotedString', '\'test\'', {closed=true}) + singl_eltkn_test('SingleQuotedString', '\'\'\'\'', {closed=true}) + singl_eltkn_test('SingleQuotedString', '\'x\'\'\'', {closed=true}) + singl_eltkn_test('SingleQuotedString', '\'\'\'x\'', {closed=true}) + singl_eltkn_test('SingleQuotedString', '\'\'\'', {closed=false}) + singl_eltkn_test('SingleQuotedString', '\'x\'\'', {closed=false}) + singl_eltkn_test('SingleQuotedString', '\'\'\'x', {closed=false}) + singl_eltkn_test('DoubleQuotedString', '"test', {closed=false}) + singl_eltkn_test('DoubleQuotedString', '"test"', {closed=true}) + singl_eltkn_test('DoubleQuotedString', '"\\""', {closed=true}) + singl_eltkn_test('DoubleQuotedString', '"x\\""', {closed=true}) + singl_eltkn_test('DoubleQuotedString', '"\\"x"', {closed=true}) + singl_eltkn_test('DoubleQuotedString', '"\\"', {closed=false}) + singl_eltkn_test('DoubleQuotedString', '"x\\"', {closed=false}) + singl_eltkn_test('DoubleQuotedString', '"\\"x', {closed=false}) + singl_eltkn_test('Not', '!') + singl_eltkn_test('Invalid', '=', {error='E15: Expected == or =~: %.*s'}) + comparison_test('==', '!=', 'Equal') + comparison_test('=~', '!~', 'Matches') + comparison_test('>', '<=', 'Greater') + comparison_test('>=', '<', 'GreaterOrEqual') + singl_eltkn_test('Minus', '-') + singl_eltkn_test('Arrow', '->') + singl_eltkn_test('EOC', '\0') + singl_eltkn_test('EOC', '\n') + singl_eltkn_test('Invalid', '~', {error='E15: Unidentified character: %.*s'}) + + local pstate = new_pstate({{data=nil, size=0}}) + eq({len=0, error='start.col >= #pstr', start={col=0, line=0}, type='EOC'}, + next_eltkn(pstate)) + + local pstate = new_pstate({''}) + eq({len=0, error='start.col >= #pstr', start={col=0, line=0}, type='EOC'}, + next_eltkn(pstate)) + end) +end) -- cgit From 2d8b9937deae3731143f4ea44e5c41715fe1363a Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 20 Aug 2017 20:40:59 +0300 Subject: viml/parser: Handle encoding conversions --- src/nvim/viml/parser/expressions.c | 13 +++++++++--- src/nvim/viml/parser/parser.h | 34 +++++++++++++++++++++++++++++-- test/unit/viml/expressions/lexer_spec.lua | 7 +++++++ 3 files changed, 49 insertions(+), 5 deletions(-) diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index 0164de3a14..c29fac9cb4 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -25,8 +25,13 @@ #define AUTOLOAD_CHAR '#' /// Get next token for the VimL expression input -LexExprToken viml_pexpr_next_token(ParserState *const pstate) - FUNC_ATTR_WARN_UNUSED_RESULT +/// +/// @param pstate Parser state. +/// @param[in] peek If true, do not advance pstate cursor. +/// +/// @return Next token. +LexExprToken viml_pexpr_next_token(ParserState *const pstate, const bool peek) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { LexExprToken ret = { .type = kExprLexInvalid, @@ -362,6 +367,8 @@ viml_pexpr_next_token_invalid_comparison: } #undef GET_CCS viml_pexpr_next_token_adv_return: - viml_parser_advance(pstate, ret.len); + if (!peek) { + viml_parser_advance(pstate, ret.len); + } return ret; } diff --git a/src/nvim/viml/parser/parser.h b/src/nvim/viml/parser/parser.h index ec582294e1..231e43b4c7 100644 --- a/src/nvim/viml/parser/parser.h +++ b/src/nvim/viml/parser/parser.h @@ -7,11 +7,13 @@ #include "nvim/lib/kvec.h" #include "nvim/func_attr.h" +#include "nvim/mbyte.h" /// One parsed line typedef struct { const char *data; ///< Parsed line pointer size_t size; ///< Parsed line size + bool allocated; ///< True if line may be freed. } ParserLine; /// Line getter type for parser @@ -48,6 +50,8 @@ typedef struct { void *cookie; /// All lines obtained by get_line. kvec_withinit_t(ParserLine, 4) lines; + /// Conversion, for :scriptencoding. + vimconv_T conv; } ParserInputReader; /// Highlighted region definition @@ -76,6 +80,33 @@ typedef struct { bool can_continuate; } ParserState; +static inline void viml_preader_get_line(ParserInputReader *const preader, + ParserLine *const ret_pline) + REAL_FATTR_NONNULL_ALL; + +/// Get one line from ParserInputReader +static inline void viml_preader_get_line(ParserInputReader *const preader, + ParserLine *const ret_pline) +{ + ParserLine pline; + preader->get_line(preader->cookie, &pline); + if (preader->conv.vc_type != CONV_NONE && pline.size) { + ParserLine cpline = { + .allocated = true, + .size = pline.size, + }; + cpline.data = (char *)string_convert(&preader->conv, + (char_u *)pline.data, + &cpline.size); + if (pline.allocated) { + xfree((void *)pline.data); + } + pline = cpline; + } + kvi_push(preader->lines, pline); + *ret_pline = pline; +} + static inline bool viml_parser_get_remaining_line(ParserState *const pstate, ParserLine *const ret_pline) REAL_FATTR_ALWAYS_INLINE REAL_FATTR_WARN_UNUSED_RESULT REAL_FATTR_NONNULL_ALL; @@ -90,8 +121,7 @@ static inline bool viml_parser_get_remaining_line(ParserState *const pstate, { const size_t num_lines = kv_size(pstate->reader.lines); if (pstate->pos.line == num_lines) { - pstate->reader.get_line(pstate->reader.cookie, ret_pline); - kvi_push(pstate->reader.lines, *ret_pline); + viml_preader_get_line(&pstate->reader, ret_pline); } else { *ret_pline = kv_last(pstate->reader.lines); } diff --git a/test/unit/viml/expressions/lexer_spec.lua b/test/unit/viml/expressions/lexer_spec.lua index bf5afe4eeb..c877ce4bbf 100644 --- a/test/unit/viml/expressions/lexer_spec.lua +++ b/test/unit/viml/expressions/lexer_spec.lua @@ -110,15 +110,22 @@ local function new_pstate(strings) end ret_pline.data = data ret_pline.size = size + ret_pline.allocated = false end local pline_init = { data = nil, size = 0, + allocated = false, } local state = { reader = { get_line = get_line, cookie = nil, + conv = { + vc_type = 0, + vc_factor = 1, + vc_fail = false, + }, }, pos = { line = 0, col = 0 }, colors = kvi_new('ParserHighlight'), -- cgit From 1265da028882d9877a5ebbd3f3f52cb4b52a4b94 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 3 Sep 2017 19:53:41 +0300 Subject: viml/parser: Add helper functions for highlighting --- src/nvim/viml/parser/parser.h | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/nvim/viml/parser/parser.h b/src/nvim/viml/parser/parser.h index 231e43b4c7..a17edac403 100644 --- a/src/nvim/viml/parser/parser.h +++ b/src/nvim/viml/parser/parser.h @@ -156,4 +156,33 @@ static inline void viml_parser_advance(ParserState *const pstate, } } +static inline void viml_parser_highlight(ParserState *const pstate, + const ParserPosition start, + const size_t end_col, + const char *const group) + REAL_FATTR_ALWAYS_INLINE REAL_FATTR_NONNULL_ALL; + +/// Record highlighting of some region of text +/// +/// @param pstate Parser state to work with. +/// @param[in] start Start position of the highlight. +/// @param[in] len Highlighting chunk length. +/// @param[in] group Highlight group. +static inline void viml_parser_highlight(ParserState *const pstate, + const ParserPosition start, + const size_t len, + const char *const group) +{ + if (pstate->colors == NULL) { + return; + } + // TODO(ZyX-I): May do some assert() sanitizing here. + // TODO(ZyX-I): May join chunks. + kvi_push(*pstate->colors, ((ParserHighlightChunk) { + .start = start, + .end_col = start.col + len, + .group = group, + })); +} + #endif // NVIM_VIML_PARSER_PARSER_H -- cgit From 919223c23ae3c8c904f35e7d605b1cf14d44a5f0 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 3 Sep 2017 19:57:24 +0300 Subject: unittests: Move some functions into helpers modules --- test/unit/helpers.lua | 28 ++++++++ test/unit/viml/expressions/lexer_spec.lua | 104 +++--------------------------- test/unit/viml/helpers.lua | 97 ++++++++++++++++++++++++++++ 3 files changed, 135 insertions(+), 94 deletions(-) create mode 100644 test/unit/viml/helpers.lua diff --git a/test/unit/helpers.lua b/test/unit/helpers.lua index a629fca9a2..a5ca7b069b 100644 --- a/test/unit/helpers.lua +++ b/test/unit/helpers.lua @@ -760,6 +760,29 @@ end cimport('./src/nvim/types.h', './src/nvim/main.h', './src/nvim/os/time.h') +local function conv_enum(etab, eval) + local n = tonumber(eval) + return etab[n] or n +end + +local function array_size(arr) + return ffi.sizeof(arr) / ffi.sizeof(arr[0]) +end + +local function kvi_size(kvi) + return array_size(kvi.init_array) +end + +local function kvi_init(kvi) + kvi.capacity = kvi_size(kvi) + kvi.items = kvi.init_array + return kvi +end + +local function kvi_new(ct) + return kvi_init(ffi.new(ct)) +end + local module = { cimport = cimport, cppimport = cppimport, @@ -780,6 +803,11 @@ local module = { child_call_once = child_call_once, child_cleanup_once = child_cleanup_once, sc = sc, + conv_enum = conv_enum, + array_size = array_sive, + kvi_size = kvi_size, + kvi_init = kvi_init, + kvi_new = kvi_new, } return function() return module diff --git a/test/unit/viml/expressions/lexer_spec.lua b/test/unit/viml/expressions/lexer_spec.lua index c877ce4bbf..32182f650d 100644 --- a/test/unit/viml/expressions/lexer_spec.lua +++ b/test/unit/viml/expressions/lexer_spec.lua @@ -1,11 +1,18 @@ local helpers = require('test.unit.helpers')(after_each) +local viml_helpers = require('test.unit.viml.helpers') local itp = helpers.gen_itp(it) local child_call_once = helpers.child_call_once +local conv_enum = helpers.conv_enum local cimport = helpers.cimport local ffi = helpers.ffi local eq = helpers.eq +local pline2lua = viml_helpers.pline2lua +local new_pstate = viml_helpers.new_pstate +local intchar2lua = viml_helpers.intchar2lua +local pstate_set_str = viml_helpers.pstate_set_str + local lib = cimport('./src/nvim/viml/parser/expressions.h') local eltkn_type_tab, eltkn_cmp_type_tab, ccs_tab, eltkn_mul_type_tab @@ -71,114 +78,23 @@ child_call_once(function() } end) -local function array_size(arr) - return ffi.sizeof(arr) / ffi.sizeof(arr[0]) -end - -local function kvi_size(kvi) - return array_size(kvi.init_array) -end - -local function kvi_init(kvi) - kvi.capacity = kvi_size(kvi) - kvi.items = kvi.init_array - return kvi -end - -local function kvi_new(ct) - return kvi_init(ffi.new(ct)) -end - -local function new_pstate(strings) - local strings_idx = 0 - local function get_line(_, ret_pline) - strings_idx = strings_idx + 1 - local str = strings[strings_idx] - local data, size - if type(str) == 'string' then - data = str - size = #str - elseif type(str) == 'nil' then - data = nil - size = 0 - elseif type(str) == 'table' then - data = str.data - size = str.size - elseif type(str) == 'function' then - data, size = str() - size = size or 0 - end - ret_pline.data = data - ret_pline.size = size - ret_pline.allocated = false - end - local pline_init = { - data = nil, - size = 0, - allocated = false, - } - local state = { - reader = { - get_line = get_line, - cookie = nil, - conv = { - vc_type = 0, - vc_factor = 1, - vc_fail = false, - }, - }, - pos = { line = 0, col = 0 }, - colors = kvi_new('ParserHighlight'), - can_continuate = false, - } - local ret = ffi.new('ParserState', state) - kvi_init(ret.reader.lines) - kvi_init(ret.stack) - return ret -end - -local function conv_enum(etab, eval) - local n = tonumber(eval) - return etab[n] or n -end - local function conv_eltkn_type(typ) return conv_enum(eltkn_type_tab, typ) end -local function pline2lua(pline) - return ffi.string(pline.data, pline.size) -end - local bracket_types = { Bracket = true, FigureBrace = true, Parenthesis = true, } -local function intchar2lua(ch) - ch = tonumber(ch) - return (20 <= ch and ch < 127) and ('%c'):format(ch) or ch -end - local function eltkn2lua(pstate, tkn) local ret = { type = conv_eltkn_type(tkn.type), - len = tonumber(tkn.len), - start = { line = tonumber(tkn.start.line), col = tonumber(tkn.start.col) }, } - if ret.start.line < pstate.reader.lines.size then - local pstr = pline2lua(pstate.reader.lines.items[ret.start.line]) - if ret.start.col >= #pstr then - ret.error = 'start.col >= #pstr' - else - ret.str = pstr:sub(ret.start.col + 1, ret.start.col + ret.len) - if #(ret.str) ~= ret.len then - ret.error = '#str /= len' - end - end - else - ret.error = 'start.line >= pstate.reader.lines.size' + pstate_set_str(pstate, tkn.start, tkn.len, ret) + if not ret.error and (#(ret.str) ~= ret.len) then + ret.error = '#str /= len' end if ret.type == 'Comparison' then ret.data = { diff --git a/test/unit/viml/helpers.lua b/test/unit/viml/helpers.lua new file mode 100644 index 0000000000..2cb60499eb --- /dev/null +++ b/test/unit/viml/helpers.lua @@ -0,0 +1,97 @@ +local helpers = require('test.unit.helpers')(nil) + +local ffi = helpers.ffi +local kvi_new = helpers.kvi_new +local kvi_init = helpers.kvi_init + +local function new_pstate(strings) + local strings_idx = 0 + local function get_line(_, ret_pline) + strings_idx = strings_idx + 1 + local str = strings[strings_idx] + local data, size + if type(str) == 'string' then + data = str + size = #str + elseif type(str) == 'nil' then + data = nil + size = 0 + elseif type(str) == 'table' then + data = str.data + size = str.size + elseif type(str) == 'function' then + data, size = str() + size = size or 0 + end + ret_pline.data = data + ret_pline.size = size + ret_pline.allocated = false + end + local pline_init = { + data = nil, + size = 0, + allocated = false, + } + local state = { + reader = { + get_line = get_line, + cookie = nil, + conv = { + vc_type = 0, + vc_factor = 1, + vc_fail = false, + }, + }, + pos = { line = 0, col = 0 }, + colors = kvi_new('ParserHighlight'), + can_continuate = false, + } + local ret = ffi.new('ParserState', state) + kvi_init(ret.reader.lines) + kvi_init(ret.stack) + return ret +end + +local function intchar2lua(ch) + ch = tonumber(ch) + return (20 <= ch and ch < 127) and ('%c'):format(ch) or ch +end + +local function pline2lua(pline) + return ffi.string(pline.data, pline.size) +end + +local function pstate_str(pstate, start, len) + local str = nil + local err = nil + if start.line < pstate.reader.lines.size then + local pstr = pline2lua(pstate.reader.lines.items[start.line]) + if start.col >= #pstr then + err = 'start.col >= #pstr' + else + str = pstr:sub(tonumber(start.col) + 1, tonumber(start.col + len)) + end + else + err = 'start.line >= pstate.reader.lines.size' + end + return str, err +end + +local function pstate_set_str(pstate, start, len, ret) + ret = ret or {} + ret.start = { + line = tonumber(start.line), + col = tonumber(start.col) + } + ret.len = tonumber(len) + ret.str, ret.error = pstate_str(pstate, start, len) + return ret +end + +return { + pline2lua = pline2lua, + pstate_str = pstate_str, + new_pstate = new_pstate, + intchar2lua = intchar2lua, + pstate_set_str = pstate_set_str, +} -- cgit From 430e516d3ac1235c1ee3009a8a36089bf278440e Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 3 Sep 2017 21:58:16 +0300 Subject: viml/parser/expressions: Start creating expressions parser Currently supported nodes: - Register as it is one of the simplest value nodes (even numbers are not that simple with that dot handling). - Plus, both unary and binary. - Parenthesis, both nesting and calling. Note regarding unit tests: it stores data for AST in highlighting in strings in place of tables because luassert fails to do a good job at representing big tables. Squashing a bunch of data into a single string simply yields more readable result. --- src/nvim/viml/parser/expressions.c | 625 ++++++++++++++++++++ src/nvim/viml/parser/expressions.h | 74 +++ test/unit/eval/helpers.lua | 5 +- test/unit/helpers.lua | 28 + test/unit/viml/expressions/parser_spec.lua | 887 +++++++++++++++++++++++++++++ 5 files changed, 1615 insertions(+), 4 deletions(-) create mode 100644 test/unit/viml/expressions/parser_spec.lua diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index c29fac9cb4..b54f2eb237 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -13,10 +13,18 @@ #include "nvim/types.h" #include "nvim/charset.h" #include "nvim/ascii.h" +#include "nvim/lib/kvec.h" #include "nvim/viml/parser/expressions.h" #include "nvim/viml/parser/parser.h" +typedef kvec_withinit_t(ExprASTNode **, 16) ExprASTStack; + +typedef enum { + kELvlOperator, ///< Operators: function call, subscripts, binary operators, … + kELvlValue, ///< Actual value: literals, variables, nested expressions. +} ExprASTLevel; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "viml/parser/expressions.c.generated.h" #endif @@ -144,6 +152,7 @@ LexExprToken viml_pexpr_next_token(ParserState *const pstate, const bool peek) // Environment variable. case '$': { + // FIXME: Parser function can’t be thread-safe with vim_isIDc. CHARREG(kExprLexEnv, vim_isIDc); break; } @@ -183,6 +192,7 @@ LexExprToken viml_pexpr_next_token(ParserState *const pstate, const bool peek) ret.data.var.autoload = ( memchr(pline.data + 2, AUTOLOAD_CHAR, ret.len - 2) != NULL); + // FIXME: Resolve ambiguity with an argument to the lexer function. // Previous CHARREG stopped at autoload character in order to make it // possible to detect `is#`. Continue now with autoload characters // included. @@ -372,3 +382,618 @@ viml_pexpr_next_token_adv_return: } return ret; } + +// start = s ternary_expr s EOC +// ternary_expr = binop_expr +// ( s Question s ternary_expr s Colon s ternary_expr s )? +// binop_expr = unaryop_expr ( binop unaryop_expr )? +// unaryop_expr = ( unaryop )? subscript_expr +// subscript_expr = subscript_expr subscript +// | value_expr +// subscript = Bracket('[') s ternary_expr s Bracket(']') +// | s Parenthesis('(') call_args Parenthesis(')') +// | Dot ( PlainIdentifier | Number )+ +// # Note: `s` before Parenthesis('(') is only valid if preceding subscript_expr +// # is PlainIdentifier +// value_expr = ( float | Number +// | DoubleQuotedString | SingleQuotedString +// | paren_expr +// | list_literal +// | lambda_literal +// | dict_literal +// | Environment +// | Option +// | Register +// | var ) +// float = Number Dot Number ( PlainIdentifier('e') ( Plus | Minus )? Number )? +// # Note: `1.2.3` is concat and not float. `"abc".2.3` is also concat without +// # floats. +// paren_expr = Parenthesis('(') s ternary_expr s Parenthesis(')') +// list_literal = Bracket('[') s +// ( ternary_expr s Comma s )* +// ternary_expr? s +// Bracket(']') +// dict_literal = FigureBrace('{') s +// ( ternary_expr s Colon s ternary_expr s Comma s )* +// ( ternary_expr s Colon s ternary_expr s )? +// FigureBrace('}') +// lambda_literal = FigureBrace('{') s +// ( PlainIdentifier s Comma s )* +// PlainIdentifier s +// Arrow s +// ternary_expr s +// FigureBrace('}') +// var = varchunk+ +// varchunk = PlainIdentifier +// | Comparison("is" | "is#" | "isnot" | "isnot#") +// | FigureBrace('{') s ternary_expr s FigureBrace('}') +// call_args = ( s ternary_expr s Comma s )* s ternary_expr? s +// binop = s ( Plus | Minus | Dot +// | Comparison +// | Multiplication +// | Or +// | And ) s +// unaryop = s ( Not | Plus | Minus ) s +// s = Spacing? +// +// Binary operator precedence and associativity: +// +// Operator | Precedence | Associativity +// ---------+------------+----------------- +// || | 2 | left +// && | 3 | left +// cmp* | 4 | not associative +// + - . | 5 | left +// * / % | 6 | left +// +// * comparison operators: +// +// == ==# ==? != !=# !=? +// =~ =~# =~? !~ !~# !~? +// > ># >? <= <=# <=? +// < <# = >=# >=? +// is is# is? isnot isnot# isnot? +// +// Used highlighting groups and assumed linkage: +// +// NVimInvalid -> Error +// NVimInvalidValue -> NVimInvalid +// NVimInvalidOperator -> NVimInvalid +// NVimInvalidDelimiter -> NVimInvalid +// +// NVimOperator -> Operator +// NVimUnaryOperator -> NVimOperator +// NVimBinaryOperator -> NVimOperator +// NVimComparisonOperator -> NVimOperator +// NVimTernaryOperator -> NVimOperator +// +// NVimParenthesis -> Delimiter +// +// NVimInvalidSpacing -> NVimInvalid +// NVimInvalidTernaryOperator -> NVimInvalidOperator +// NVimInvalidRegister -> NVimInvalidValue +// NVimInvalidClosingBracket -> NVimInvalidDelimiter +// NVimInvalidSpacing -> NVimInvalid +// +// NVimUnaryPlus -> NVimUnaryOperator +// NVimBinaryPlus -> NVimBinaryOperator +// NVimRegister -> SpecialChar +// NVimNestingParenthesis -> NVimParenthesis +// NVimCallingParenthesis -> NVimParenthesis + +/// Allocate a new node and set some of the values +/// +/// @param[in] type Node type to allocate. +/// @param[in] level Node level to allocate +static inline ExprASTNode *viml_pexpr_new_node(const ExprASTNodeType type) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_MALLOC +{ + ExprASTNode *ret = xmalloc(sizeof(*ret)); + ret->type = type; + ret->children = NULL; + ret->next = NULL; + return ret; +} + +typedef enum { + kEOpLvlInvalid = 0, + kEOpLvlParens, + kEOpLvlTernary, + kEOpLvlOr, + kEOpLvlAnd, + kEOpLvlComparison, + kEOpLvlAddition, ///< Addition, subtraction and concatenation. + kEOpLvlMultiplication, ///< Multiplication, division and modulo. + kEOpLvlUnary, ///< Unary operations: not, minus, plus. + kEOpLvlSubscript, ///< Subscripts. + kEOpLvlValue, ///< Values: literals, variables, nested expressions, … +} ExprOpLvl; + +typedef enum { + kEOpAssNo= 'n', ///< Not associative / not applicable. + kEOpAssLeft = 'l', ///< Left associativity. + kEOpAssRight = 'r', ///< Right associativity. +} ExprOpAssociativity; + +static const ExprOpLvl node_type_to_op_lvl[] = { + [kExprNodeMissing] = kEOpLvlInvalid, + [kExprNodeOpMissing] = kEOpLvlMultiplication, + + [kExprNodeNested] = kEOpLvlParens, + [kExprNodeComplexIdentifier] = kEOpLvlParens, + + [kExprNodeTernary] = kEOpLvlTernary, + + [kExprNodeBinaryPlus] = kEOpLvlAddition, + + [kExprNodeUnaryPlus] = kEOpLvlUnary, + + [kExprNodeSubscript] = kEOpLvlSubscript, + [kExprNodeCall] = kEOpLvlSubscript, + + [kExprNodeRegister] = kEOpLvlValue, + [kExprNodeListLiteral] = kEOpLvlValue, + [kExprNodePlainIdentifier] = kEOpLvlValue, +}; + +static const ExprOpAssociativity node_type_to_op_ass[] = { + [kExprNodeMissing] = kEOpAssNo, + [kExprNodeOpMissing] = kEOpAssNo, + + [kExprNodeNested] = kEOpAssNo, + [kExprNodeComplexIdentifier] = kEOpAssLeft, + + [kExprNodeTernary] = kEOpAssNo, + + [kExprNodeBinaryPlus] = kEOpAssLeft, + + [kExprNodeUnaryPlus] = kEOpAssNo, + + [kExprNodeSubscript] = kEOpAssLeft, + [kExprNodeCall] = kEOpAssLeft, + + [kExprNodeRegister] = kEOpAssNo, + [kExprNodeListLiteral] = kEOpAssNo, + [kExprNodePlainIdentifier] = kEOpAssNo, +}; + +#ifdef UNIT_TESTING +#include +REAL_FATTR_UNUSED +static inline void viml_pexpr_debug_print_ast_stack( + const ExprASTStack *const ast_stack, + const char *const msg) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_ALWAYS_INLINE +{ + fprintf(stderr, "\n%sstack: %zu:\n", msg, kv_size(*ast_stack)); + for (size_t i = 0; i < kv_size(*ast_stack); i++) { + const ExprASTNode *const *const eastnode_p = ( + (const ExprASTNode *const *)kv_A(*ast_stack, i)); + if (*eastnode_p == NULL) { + fprintf(stderr, "- %p : NULL\n", (void *)eastnode_p); + } else { + fprintf(stderr, "- %p : %p : %c : %zu:%zu:%zu\n", + (void *)eastnode_p, (void *)(*eastnode_p), (*eastnode_p)->type, + (*eastnode_p)->start.line, (*eastnode_p)->start.col, + (*eastnode_p)->len); + } + } +} +#define PSTACK(msg) \ + viml_pexpr_debug_print_ast_stack(&ast_stack, #msg) +#define PSTACK_P(msg) \ + viml_pexpr_debug_print_ast_stack(ast_stack, #msg) +#endif + +/// Handle binary operator +/// +/// This function is responsible for handling priority levels as well. +static void viml_pexpr_handle_bop(ExprASTStack *const ast_stack, + ExprASTNode *const bop_node, + ExprASTLevel *const want_level_p) + FUNC_ATTR_NONNULL_ALL +{ + ExprASTNode **top_node_p = NULL; + ExprASTNode *top_node; + ExprOpLvl top_node_lvl; + ExprOpAssociativity top_node_ass; + assert(kv_size(*ast_stack)); + const ExprOpLvl bop_node_lvl = node_type_to_op_lvl[bop_node->type]; + do { + ExprASTNode **new_top_node_p = kv_last(*ast_stack); + ExprASTNode *new_top_node = *new_top_node_p; + assert(new_top_node != NULL); + const ExprOpLvl new_top_node_lvl = node_type_to_op_lvl[new_top_node->type]; + const ExprOpAssociativity new_top_node_ass = ( + node_type_to_op_ass[new_top_node->type]); + if (top_node_p != NULL + && ((bop_node_lvl > new_top_node_lvl + || (bop_node_lvl == new_top_node_lvl + && new_top_node_ass == kEOpAssNo)))) { + break; + } + kv_drop(*ast_stack, 1); + top_node_p = new_top_node_p; + top_node = new_top_node; + top_node_lvl = new_top_node_lvl; + top_node_ass = new_top_node_ass; + } while (kv_size(*ast_stack)); + // FIXME Handle right and no associativity correctly + *top_node_p = bop_node; + bop_node->children = top_node; + assert(bop_node->children->next == NULL); + kvi_push(*ast_stack, top_node_p); + kvi_push(*ast_stack, &bop_node->children->next); + *want_level_p = kELvlValue; +} + +/// Get highlight group name +#define HL(g) (is_invalid ? "NVimInvalid" #g : "NVim" #g) + +/// Highlight current token with the given group +#define HL_CUR_TOKEN(g) \ + viml_parser_highlight(pstate, cur_token.start, cur_token.len, \ + HL(g)) + +/// Allocate new node, saving some values +#define NEW_NODE(type) \ + viml_pexpr_new_node(type) + +/// Set position of the given node to position from the given token +/// +/// @param cur_node Node to modify. +/// @param cur_token Token to set position from. +#define POS_FROM_TOKEN(cur_node, cur_token) \ + do { \ + cur_node->start = cur_token.start; \ + cur_node->len = cur_token.len; \ + } while (0) + +/// Allocate new node and set its position from the current token +/// +/// If previous token happened to contain spacing then it will be included. +/// +/// @param cur_node Variable to save allocated node to. +/// @param typ Node type. +#define NEW_NODE_WITH_CUR_POS(cur_node, typ) \ + do { \ + cur_node = NEW_NODE(typ); \ + POS_FROM_TOKEN(cur_node, cur_token); \ + if (prev_token.type == kExprLexSpacing) { \ + cur_node->start = prev_token.start; \ + cur_node->len += prev_token.len; \ + } \ + } while (0) + +// TODO(ZyX-I): actual condition +/// Check whether it is possible to have next expression after current +/// +/// For :echo: `:echo @a @a` is a valid expression. `:echo (@a @a)` is not. +#define MAY_HAVE_NEXT_EXPR \ + (kv_size(ast_stack) == 1) + +/// Record missing operator: for things like +/// +/// :echo @a @a +/// +/// (allowed) or +/// +/// :echo (@a @a) +/// +/// (parsed as OpMissing(@a, @a)). +#define OP_MISSING \ + do { \ + if (flags & kExprFlagsMulti && MAY_HAVE_NEXT_EXPR) { \ + /* Multiple expressions allowed, return without calling */ \ + /* viml_parser_advance(). */ \ + goto viml_pexpr_parse_end; \ + } else { \ + assert(*top_node_p != NULL); \ + ERROR_FROM_TOKEN_AND_MSG(cur_token, _("E15: Missing operator: %.*s")); \ + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeOpMissing); \ + cur_node->len = 0; \ + viml_pexpr_handle_bop(&ast_stack, cur_node, &want_level); \ + is_invalid = true; \ + goto viml_pexpr_parse_process_token; \ + } \ + } while (0) + +/// Set AST error, unless AST already is not correct +/// +/// @param[out] ret_ast AST to set error in. +/// @param[in] pstate Parser state, used to get error message argument. +/// @param[in] msg Error message, assumed to be already translated and +/// containing a single %token "%.*s". +/// @param[in] start Position at which error occurred. +static inline void east_set_error(ExprAST *const ret_ast, + const ParserState *const pstate, + const char *const msg, + const ParserPosition start) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_ALWAYS_INLINE +{ + if (!ret_ast->correct) { + return; + } + const ParserLine pline = pstate->reader.lines.items[start.line]; + ret_ast->correct = false; + ret_ast->err.msg = msg; + ret_ast->err.arg_len = (int)(pline.size - start.col); + ret_ast->err.arg = pline.data + start.col; +} + +/// Set error from the given kExprLexInvalid token and given message +#define ERROR_FROM_TOKEN_AND_MSG(cur_token, msg) \ + east_set_error(&ast, pstate, msg, cur_token.start) + +/// Set error from the given kExprLexInvalid token +#define ERROR_FROM_TOKEN(cur_token) \ + ERROR_FROM_TOKEN_AND_MSG(cur_token, cur_token.data.err.msg) + +/// Parse one VimL expression +/// +/// @param pstate Parser state. +/// @param[in] flags Additional flags, see ExprParserFlags +/// +/// @return Parsed AST. +ExprAST viml_pexpr_parse(ParserState *const pstate, const int flags) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL +{ + ExprAST ast = { + .correct = true, + .err = { + .msg = NULL, + .arg_len = 0, + .arg = NULL, + }, + .root = NULL, + }; + ExprASTStack ast_stack; + kvi_init(ast_stack); + kvi_push(ast_stack, &ast.root); + // Expressions stack: + // 1. *last is NULL if want_level is kExprLexValue. Indicates where expression + // is to be put. + // 2. *last is not NULL otherwise, indicates current expression to be used as + // an operator argument. + ExprASTLevel want_level = kELvlValue; + LexExprToken prev_token = { .type = kExprLexMissing }; + bool highlighted_prev_spacing = false; + do { + LexExprToken cur_token = viml_pexpr_next_token(pstate, true); + if (cur_token.type == kExprLexEOC) { + if (flags & kExprFlagsDisallowEOC) { + if (cur_token.len == 0) { + // It is end of string, break. + break; + } else { + // It is NL, NUL or bar. + // + // Note: `=1 | 2` actually yields 1 in Vim without any + // errors. This will be changed here. + cur_token.type = kExprLexInvalid; + cur_token.data.err.msg = _("E15: Unexpected EOC character: %.*s"); + const ParserLine pline = ( + pstate->reader.lines.items[cur_token.start.line]); + const char eoc_char = pline.data[cur_token.start.col]; + cur_token.data.err.type = ((eoc_char == NUL || eoc_char == NL) + ? kExprLexSpacing + : kExprLexOr); + } + } else { + break; + } + } + LexExprTokenType tok_type = cur_token.type; + const bool token_invalid = (tok_type == kExprLexInvalid); + bool is_invalid = token_invalid; +viml_pexpr_parse_process_token: + if (tok_type == kExprLexSpacing) { + if (is_invalid) { + viml_parser_highlight(pstate, cur_token.start, cur_token.len, + HL(Spacing)); + } else { + // Do not do anything: let regular spacing be highlighted as normal. + // This also allows later to highlight spacing as invalid. + } + goto viml_pexpr_parse_cycle_end; + } else if (is_invalid && prev_token.type == kExprLexSpacing + && !highlighted_prev_spacing) { + viml_parser_highlight(pstate, prev_token.start, prev_token.len, + HL(Spacing)); + is_invalid = false; + highlighted_prev_spacing = true; + } + ExprASTNode **const top_node_p = kv_last(ast_stack); + ExprASTNode *cur_node = NULL; + // Keep these two asserts separate for debugging purposes. + assert(want_level == kELvlValue || *top_node_p != NULL); + assert(want_level != kELvlValue || *top_node_p == NULL); + switch (tok_type) { + case kExprLexEOC: { + assert(false); + } + case kExprLexInvalid: { + ERROR_FROM_TOKEN(cur_token); + tok_type = cur_token.data.err.type; + goto viml_pexpr_parse_process_token; + } + case kExprLexRegister: { + if (want_level == kELvlValue) { + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeRegister); + cur_node->data.reg.name = cur_token.data.reg.name; + *top_node_p = cur_node; + want_level = kELvlOperator; + viml_parser_highlight(pstate, cur_token.start, cur_token.len, + HL(Register)); + } else { + // Register in operator position: e.g. @a @a + OP_MISSING; + } + break; + } + case kExprLexPlus: { + if (want_level == kELvlValue) { + // Value level: assume unary plus + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeUnaryPlus); + *top_node_p = cur_node; + kvi_push(ast_stack, &cur_node->children); + HL_CUR_TOKEN(UnaryPlus); + } else if (want_level < kELvlValue) { + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeBinaryPlus); + viml_pexpr_handle_bop(&ast_stack, cur_node, &want_level); + HL_CUR_TOKEN(BinaryPlus); + } + want_level = kELvlValue; + break; + } + case kExprLexParenthesis: { + if (cur_token.data.brc.closing) { + if (want_level == kELvlValue) { + if (kv_size(ast_stack) > 1) { + const ExprASTNode *const prev_top_node = *kv_Z(ast_stack, 1); + if (prev_top_node->type == kExprNodeCall) { + // Function call without arguments, this is not an error. + // But further code does not expect NULL nodes. + kv_drop(ast_stack, 1); + goto viml_pexpr_parse_no_paren_closing_error; + } + } + is_invalid = true; + ERROR_FROM_TOKEN_AND_MSG(cur_token, _("E15: Expected value: %.*s")); + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeMissing); + cur_node->len = 0; + *top_node_p = cur_node; + } else { + // Always drop the topmost value: when want_level != kELvlValue + // topmost item on stack is a *finished* left operand, which may as + // well be "(@a)" which needs not be finished. + kv_drop(ast_stack, 1); + } +viml_pexpr_parse_no_paren_closing_error: {} + ExprASTNode **new_top_node_p = NULL; + while (kv_size(ast_stack) + && (new_top_node_p == NULL + || ((*new_top_node_p)->type != kExprNodeNested + && (*new_top_node_p)->type != kExprNodeCall))) { + new_top_node_p = kv_pop(ast_stack); + } + if (new_top_node_p != NULL + && ((*new_top_node_p)->type == kExprNodeNested + || (*new_top_node_p)->type == kExprNodeCall)) { + if ((*new_top_node_p)->type == kExprNodeNested) { + HL_CUR_TOKEN(NestingParenthesis); + } else { + HL_CUR_TOKEN(CallingParenthesis); + } + } else { + // “Always drop the topmost value” branch has got rid of the single + // value stack had, so there is nothing known to enclose. Correct + // this. + if (new_top_node_p == NULL) { + new_top_node_p = top_node_p; + } + is_invalid = true; + HL_CUR_TOKEN(NestingParenthesis); + ERROR_FROM_TOKEN_AND_MSG( + cur_token, _("E15: Unexpected closing parenthesis: %.*s")); + cur_node = NEW_NODE(kExprNodeNested); + cur_node->start = cur_token.start; + cur_node->len = 0; + // Unexpected closing parenthesis, assume that it was wanted to + // enclose everything in (). + cur_node->children = *new_top_node_p; + *new_top_node_p = cur_node; + assert(cur_node->next == NULL); + } + kvi_push(ast_stack, new_top_node_p); + want_level = kELvlOperator; + } else { + if (want_level == kELvlValue) { + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeNested); + *top_node_p = cur_node; + kvi_push(ast_stack, &cur_node->children); + HL_CUR_TOKEN(NestingParenthesis); + } else if (want_level == kELvlOperator) { + if (prev_token.type == kExprLexSpacing) { + // For some reason "function (args)" is a function call, but + // "(funcref) (args)" is not. AFAIR this somehow involves + // compatibility and Bram was commenting that this is + // intentionally inconsistent and he is not very happy with the + // situation himself. + if ((*top_node_p)->type != kExprNodePlainIdentifier + && (*top_node_p)->type != kExprNodeComplexIdentifier) { + OP_MISSING; + } + } + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeCall); + viml_pexpr_handle_bop(&ast_stack, cur_node, &want_level); + HL_CUR_TOKEN(CallingParenthesis); + } else { + // Currently it is impossible to reach this. + assert(false); + } + want_level = kELvlValue; + } + break; + } + } +viml_pexpr_parse_cycle_end: + prev_token = cur_token; + highlighted_prev_spacing = false; + viml_parser_advance(pstate, cur_token.len); + } while (true); +viml_pexpr_parse_end: + if (want_level == kELvlValue) { + east_set_error(&ast, pstate, _("E15: Expected value: %.*s"), pstate->pos); + } else if (kv_size(ast_stack) != 1) { + // Something may be wrong, check whether it really is. + + // Pointer to ast.root must never be dropped, so “!= 1” is expected to be + // the same as “> 1”. + assert(kv_size(ast_stack)); + // Topmost stack item must be a *finished* value, so it must not be + // analyzed. E.g. it may contain an already finished nested expression. + kv_drop(ast_stack, 1); + while (ast.correct && kv_size(ast_stack)) { + const ExprASTNode *const cur_node = (*kv_pop(ast_stack)); + // This should only happen when want_level == kELvlValue. + assert(cur_node != NULL); + switch (cur_node->type) { + case kExprNodeOpMissing: + case kExprNodeMissing: { + // Error should’ve been already reported. + break; + } + case kExprNodeCall: { + // TODO(ZyX-I): Rehighlight as invalid? + east_set_error( + &ast, pstate, + _("E116: Missing closing parenthesis for function call: %.*s"), + cur_node->start); + break; + } + case kExprNodeNested: { + // TODO(ZyX-I): Rehighlight as invalid? + east_set_error( + &ast, pstate, + _("E110: Missing closing parenthesis for nested expression" + ": %.*s"), + cur_node->start); + break; + } + case kExprNodeBinaryPlus: + case kExprNodeUnaryPlus: + case kExprNodeRegister: { + // It is OK to see these in the stack. + break; + } + // TODO(ZyX-I): handle other values + } + } + } + kvi_destroy(ast_stack); + return ast; +} + +#undef NEW_NODE +#undef HL diff --git a/src/nvim/viml/parser/expressions.h b/src/nvim/viml/parser/expressions.h index 52354760a5..13888562df 100644 --- a/src/nvim/viml/parser/expressions.h +++ b/src/nvim/viml/parser/expressions.h @@ -111,6 +111,80 @@ typedef struct { } data; ///< Additional data, if needed. } LexExprToken; +/// Expression AST node type +typedef enum { + kExprNodeMissing = 'X', + kExprNodeOpMissing = '_', + kExprNodeTernary = '?', ///< Ternary operator, valid one has three children. + kExprNodeRegister = '@', ///< Register, no children. + kExprNodeSubscript = 's', ///< Subscript, should have two or three children. + kExprNodeListLiteral = 'l', ///< List literal, any number of children. + kExprNodeUnaryPlus = 'p', + kExprNodeBinaryPlus = '+', + kExprNodeNested = 'e', ///< Nested parenthesised expression. + kExprNodeCall = 'c', ///< Function call. + /// Plain identifier: simple variable/function name + /// + /// Looks like "string", "g:Foo", etc: consists from a single + /// kExprLexPlainIdentifier token. + kExprNodePlainIdentifier = 'i', + /// Complex identifier: variable/function name with curly braces + kExprNodeComplexIdentifier = 'I', +} ExprASTNodeType; + +typedef struct expr_ast_node ExprASTNode; + +/// Structure representing one AST node +struct expr_ast_node { + ExprASTNodeType type; ///< Node type. + /// Node children: e.g. for 1 + 2 nodes 1 and 2 will be children of +. + ExprASTNode *children; + /// Next node: e.g. for 1 + 2 child nodes 1 and 2 are put into a single-linked + /// list: `(+)->children` references only node 1, node 2 is in + /// `(+)->children->next`. + ExprASTNode *next; + ParserPosition start; + size_t len; + union { + struct { + int name; ///< Register name, may be -1 if name not present. + } reg; ///< For kExprNodeRegister. + } data; +}; + +enum { + /// Allow multiple expressions in a row: e.g. for :echo + /// + /// Parser will still parse only one of them though. + kExprFlagsMulti = (1 << 0), + /// Allow NL, NUL and bar to be EOC + /// + /// When parsing expressions input by user bar is assumed to be a binary + /// operator and other two are spacings. + kExprFlagsDisallowEOC = (1 << 1), + /// Print errors when encountered + /// + /// Without the flag they are only taken into account when parsing. + kExprFlagsPrintError = (1 << 2), +} ExprParserFlags; + +/// Structure representing complety AST for one expression +typedef struct { + /// True if represented AST is correct and can be executed. Incorrect ones may + /// still be used for completion, or in linters. + bool correct; + /// When AST is not correct this message will be printed. + /// + /// Uses `emsgf(msg, arg_len, arg);`, `msg` is assumed to contain only `%.*s`. + struct { + const char *msg; + int arg_len; + const char *arg; + } err; + /// Root node of the AST. + ExprASTNode *root; +} ExprAST; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "viml/parser/expressions.h.generated.h" #endif diff --git a/test/unit/eval/helpers.lua b/test/unit/eval/helpers.lua index 5bc482216e..d7399182f7 100644 --- a/test/unit/eval/helpers.lua +++ b/test/unit/eval/helpers.lua @@ -1,5 +1,6 @@ local helpers = require('test.unit.helpers')(nil) +local ptr2key = helpers.ptr2key local cimport = helpers.cimport local to_cstr = helpers.to_cstr local ffi = helpers.ffi @@ -91,10 +92,6 @@ local function populate_partial(pt, lua_pt, processed) return pt end -local ptr2key = function(ptr) - return tostring(ptr) -end - local lst2tbl local dct2tbl diff --git a/test/unit/helpers.lua b/test/unit/helpers.lua index a5ca7b069b..d3d14a5ca2 100644 --- a/test/unit/helpers.lua +++ b/test/unit/helpers.lua @@ -783,6 +783,31 @@ local function kvi_new(ct) return kvi_init(ffi.new(ct)) end +local function make_enum_conv_tab(lib, values, skip_pref, set_cb) + child_call_once(function() + local ret = {} + for _, v in ipairs(values) do + local str_v = v + if v:sub(1, #skip_pref) == skip_pref then + str_v = v:sub(#skip_pref + 1) + end + ret[tonumber(lib[v])] = str_v + end + set_cb(ret) + end) +end + +local function ptr2addr(ptr) + return tonumber(ffi.cast('intptr_t', ffi.cast('void *', ptr))) +end + +local s = ffi.new('char[64]', {0}) + +local function ptr2key(ptr) + ffi.C.snprintf(s, ffi.sizeof(s), '%p', ffi.cast('void *', ptr)) + return ffi.string(s) +end + local module = { cimport = cimport, cppimport = cppimport, @@ -808,6 +833,9 @@ local module = { kvi_size = kvi_size, kvi_init = kvi_init, kvi_new = kvi_new, + make_enum_conv_tab = make_enum_conv_tab, + ptr2addr = ptr2addr, + ptr2key = ptr2key, } return function() return module diff --git a/test/unit/viml/expressions/parser_spec.lua b/test/unit/viml/expressions/parser_spec.lua new file mode 100644 index 0000000000..c4bb067391 --- /dev/null +++ b/test/unit/viml/expressions/parser_spec.lua @@ -0,0 +1,887 @@ +local helpers = require('test.unit.helpers')(after_each) +local viml_helpers = require('test.unit.viml.helpers') +local itp = helpers.gen_itp(it) + +local make_enum_conv_tab = helpers.make_enum_conv_tab +local child_call_once = helpers.child_call_once +local conv_enum = helpers.conv_enum +local ptr2key = helpers.ptr2key +local cimport = helpers.cimport +local ffi = helpers.ffi +local eq = helpers.eq + +local pline2lua = viml_helpers.pline2lua +local new_pstate = viml_helpers.new_pstate +local intchar2lua = viml_helpers.intchar2lua +local pstate_set_str = viml_helpers.pstate_set_str + +local lib = cimport('./src/nvim/viml/parser/expressions.h') + +local east_node_type_tab +make_enum_conv_tab(lib, { + 'kExprNodeMissing', + 'kExprNodeOpMissing', + 'kExprNodeTernary', + 'kExprNodeRegister', + 'kExprNodeSubscript', + 'kExprNodeListLiteral', + 'kExprNodeUnaryPlus', + 'kExprNodeBinaryPlus', + 'kExprNodeNested', + 'kExprNodeCall', + 'kExprNodePlainIdentifier', + 'kExprNodeComplexIdentifier', +}, 'kExprNode', function(ret) east_node_type_tab = ret end) + +local function conv_east_node_type(typ) + return conv_enum(east_node_type_tab, typ) +end + +local eastnodelist2lua + +local function eastnode2lua(pstate, eastnode, checked_nodes) + local key = ptr2key(eastnode) + if checked_nodes[key] then + checked_nodes[key].duplicate_key = key + return { duplicate = key } + end + local typ = conv_east_node_type(eastnode.type) + local ret = {} + checked_nodes[key] = ret + ret.children = eastnodelist2lua(pstate, eastnode.children, checked_nodes) + local str = pstate_set_str(pstate, eastnode.start, eastnode.len) + local ret_str + if str.error then + ret_str = 'error:' .. str.error + else + ret_str = ('%u:%u:%s'):format(str.start.line, str.start.col, str.str) + end + if typ == 'Register' then + typ = typ .. ('(name=%s)'):format( + tostring(intchar2lua(eastnode.data.reg.name))) + end + ret_str = typ .. ':' .. ret_str + local can_simplify = true + for k, v in pairs(ret) do + can_simplify = false + end + if can_simplify then + ret = ret_str + else + ret[1] = ret_str + end + return ret +end + +eastnodelist2lua = function(pstate, eastnode, checked_nodes) + local ret = {} + while eastnode ~= nil do + ret[#ret + 1] = eastnode2lua(pstate, eastnode, checked_nodes) + eastnode = eastnode.next + end + if #ret == 0 then + ret = nil + end + return ret +end + +local function east2lua(pstate, east) + local checked_nodes = {} + return { + err = (not east.correct) and { + msg = ffi.string(east.err.msg), + arg = ('%u:%s'):format( + tonumber(east.err.arg_len), + ffi.string(east.err.arg, east.err.arg_len)), + } or nil, + ast = eastnodelist2lua(pstate, east.root, checked_nodes), + } +end + +local function phl2lua(pstate) + local ret = {} + for i = 0, (tonumber(pstate.colors.size) - 1) do + local chunk = pstate.colors.items[i] + local chunk_tbl = pstate_set_str( + pstate, chunk.start, chunk.end_col - chunk.start.col, { + group = ffi.string(chunk.group), + }) + chunk_str = ('%s:%u:%u:%s'):format( + chunk_tbl.group, + chunk_tbl.start.line, + chunk_tbl.start.col, + chunk_tbl.str) + ret[i + 1] = chunk_str + end + return ret +end + +child_call_once(function() + assert:set_parameter('TableFormatLevel', 1000000) +end) + +describe('Expressions parser', function() + itp('works', function() + local function check_parsing(str, flags, exp_ast, exp_highlighting_fs) + local pstate = new_pstate({str}) + local east = lib.viml_pexpr_parse(pstate, flags) + local ast = east2lua(pstate, east) + eq(exp_ast, ast) + if exp_highlighting_fs then + local exp_highlighting = {} + local next_col = 0 + for i, h in ipairs(exp_highlighting_fs) do + exp_highlighting[i], next_col = h(next_col) + end + eq(exp_highlighting, phl2lua(pstate)) + end + end + local function hl(group, str, shift) + return function(next_col) + local col = next_col + (shift or 0) + return (('%s:%u:%u:%s'):format( + 'NVim' .. group, + 0, + col, + str)), (col + #str) + end + end + check_parsing('@a', 0, { + ast = { + 'Register(name=a):0:0:@a', + }, + }, { + hl('Register', '@a'), + }) + check_parsing('+@a', 0, { + ast = { + { + 'UnaryPlus:0:0:+', + children = { + 'Register(name=a):0:1:@a', + }, + }, + }, + }, { + hl('UnaryPlus', '+'), + hl('Register', '@a'), + }) + check_parsing('@a+@b', 0, { + ast = { + { + 'BinaryPlus:0:2:+', + children = { + 'Register(name=a):0:0:@a', + 'Register(name=b):0:3:@b', + }, + }, + }, + }, { + hl('Register', '@a'), + hl('BinaryPlus', '+'), + hl('Register', '@b'), + }) + check_parsing('@a+@b+@c', 0, { + ast = { + { + 'BinaryPlus:0:5:+', + children = { + { + 'BinaryPlus:0:2:+', + children = { + 'Register(name=a):0:0:@a', + 'Register(name=b):0:3:@b', + }, + }, + 'Register(name=c):0:6:@c', + }, + }, + }, + }, { + hl('Register', '@a'), + hl('BinaryPlus', '+'), + hl('Register', '@b'), + hl('BinaryPlus', '+'), + hl('Register', '@c'), + }) + check_parsing('+@a+@b', 0, { + ast = { + { + 'BinaryPlus:0:3:+', + children = { + { + 'UnaryPlus:0:0:+', + children = { + 'Register(name=a):0:1:@a', + }, + }, + 'Register(name=b):0:4:@b', + }, + }, + }, + }, { + hl('UnaryPlus', '+'), + hl('Register', '@a'), + hl('BinaryPlus', '+'), + hl('Register', '@b'), + }) + check_parsing('+@a++@b', 0, { + ast = { + { + 'BinaryPlus:0:3:+', + children = { + { + 'UnaryPlus:0:0:+', + children = { + 'Register(name=a):0:1:@a', + }, + }, + { + 'UnaryPlus:0:4:+', + children = { + 'Register(name=b):0:5:@b', + }, + }, + }, + }, + }, + }, { + hl('UnaryPlus', '+'), + hl('Register', '@a'), + hl('BinaryPlus', '+'), + hl('UnaryPlus', '+'), + hl('Register', '@b'), + }) + check_parsing('@a@b', 0, { + ast = { + { + 'OpMissing:0:2:', + children = { + 'Register(name=a):0:0:@a', + 'Register(name=b):0:2:@b', + }, + }, + }, + err = { + arg = '2:@b', + msg = 'E15: Missing operator: %.*s', + }, + }, { + hl('Register', '@a'), + hl('InvalidRegister', '@b'), + }) + check_parsing(' @a \t @b', 0, { + ast = { + { + 'OpMissing:0:3:', + children = { + 'Register(name=a):0:0: @a', + 'Register(name=b):0:3: \t @b', + }, + }, + }, + err = { + arg = '2:@b', + msg = 'E15: Missing operator: %.*s', + }, + }, { + hl('Register', '@a', 1), + hl('InvalidSpacing', ' \t '), + hl('Register', '@b'), + }) + check_parsing('+', 0, { + ast = { + 'UnaryPlus:0:0:+', + }, + err = { + arg = '0:', + msg = 'E15: Expected value: %.*s', + }, + }, { + hl('UnaryPlus', '+'), + }) + check_parsing(' +', 0, { + ast = { + 'UnaryPlus:0:0: +', + }, + err = { + arg = '0:', + msg = 'E15: Expected value: %.*s', + }, + }, { + hl('UnaryPlus', '+', 1), + }) + check_parsing('@a+ ', 0, { + ast = { + { + 'BinaryPlus:0:2:+', + children = { + 'Register(name=a):0:0:@a', + }, + }, + }, + err = { + arg = '0:', + msg = 'E15: Expected value: %.*s', + }, + }, { + hl('Register', '@a'), + hl('BinaryPlus', '+'), + }) + check_parsing('(@a)', 0, { + ast = { + { + 'Nested:0:0:(', + children = { + 'Register(name=a):0:1:@a', + }, + }, + }, + }, { + hl('NestingParenthesis', '('), + hl('Register', '@a'), + hl('NestingParenthesis', ')'), + }) + check_parsing('()', 0, { + ast = { + { + 'Nested:0:0:(', + children = { + 'Missing:0:1:', + }, + }, + }, + err = { + arg = '1:)', + msg = 'E15: Expected value: %.*s', + }, + }, { + hl('NestingParenthesis', '('), + hl('InvalidNestingParenthesis', ')'), + }) + check_parsing(')', 0, { + ast = { + { + 'Nested:0:0:', + children = { + 'Missing:0:0:', + }, + }, + }, + err = { + arg = '1:)', + msg = 'E15: Expected value: %.*s', + }, + }, { + hl('InvalidNestingParenthesis', ')'), + }) + check_parsing('+)', 0, { + ast = { + { + 'Nested:0:1:', + children = { + { + 'UnaryPlus:0:0:+', + children = { + 'Missing:0:1:', + }, + }, + }, + }, + }, + err = { + arg = '1:)', + msg = 'E15: Expected value: %.*s', + }, + }, { + hl('UnaryPlus', '+'), + hl('InvalidNestingParenthesis', ')'), + }) + check_parsing('+@a(@b)', 0, { + ast = { + { + 'UnaryPlus:0:0:+', + children = { + { + 'Call:0:3:(', + children = { + 'Register(name=a):0:1:@a', + 'Register(name=b):0:4:@b', + }, + }, + }, + }, + }, + }, { + hl('UnaryPlus', '+'), + hl('Register', '@a'), + hl('CallingParenthesis', '('), + hl('Register', '@b'), + hl('CallingParenthesis', ')'), + }) + check_parsing('@a+@b(@c)', 0, { + ast = { + { + 'BinaryPlus:0:2:+', + children = { + 'Register(name=a):0:0:@a', + { + 'Call:0:5:(', + children = { + 'Register(name=b):0:3:@b', + 'Register(name=c):0:6:@c', + }, + }, + }, + }, + }, + }, { + hl('Register', '@a'), + hl('BinaryPlus', '+'), + hl('Register', '@b'), + hl('CallingParenthesis', '('), + hl('Register', '@c'), + hl('CallingParenthesis', ')'), + }) + check_parsing('@a()', 0, { + ast = { + { + 'Call:0:2:(', + children = { + 'Register(name=a):0:0:@a', + }, + }, + }, + }, { + hl('Register', '@a'), + hl('CallingParenthesis', '('), + hl('CallingParenthesis', ')'), + }) + check_parsing('@a ()', 0, { + ast = { + { + 'OpMissing:0:2:', + children = { + 'Register(name=a):0:0:@a', + { + 'Nested:0:2: (', + children = { + 'Missing:0:4:', + }, + }, + }, + }, + }, + err = { + arg = '2:()', + msg = 'E15: Missing operator: %.*s', + }, + }, { + hl('Register', '@a'), + hl('InvalidSpacing', ' '), + hl('NestingParenthesis', '('), + hl('InvalidNestingParenthesis', ')'), + }) + check_parsing( + '@a + (@b)', 0, { + ast = { + { + 'BinaryPlus:0:2: +', + children = { + 'Register(name=a):0:0:@a', + { + 'Nested:0:4: (', + children = { + 'Register(name=b):0:6:@b', + }, + }, + }, + }, + }, + }, { + hl('Register', '@a'), + hl('BinaryPlus', '+', 1), + hl('NestingParenthesis', '(', 1), + hl('Register', '@b'), + hl('NestingParenthesis', ')'), + }) + check_parsing( + '@a + (+@b)', 0, { + ast = { + { + 'BinaryPlus:0:2: +', + children = { + 'Register(name=a):0:0:@a', + { + 'Nested:0:4: (', + children = { + { + 'UnaryPlus:0:6:+', + children = { + 'Register(name=b):0:7:@b', + }, + }, + }, + }, + }, + }, + }, + }, { + hl('Register', '@a'), + hl('BinaryPlus', '+', 1), + hl('NestingParenthesis', '(', 1), + hl('UnaryPlus', '+'), + hl('Register', '@b'), + hl('NestingParenthesis', ')'), + }) + check_parsing( + '@a + (@b + @c)', 0, { + ast = { + { + 'BinaryPlus:0:2: +', + children = { + 'Register(name=a):0:0:@a', + { + 'Nested:0:4: (', + children = { + { + 'BinaryPlus:0:8: +', + children = { + 'Register(name=b):0:6:@b', + 'Register(name=c):0:10: @c', + }, + }, + }, + }, + }, + }, + }, + }, { + hl('Register', '@a'), + hl('BinaryPlus', '+', 1), + hl('NestingParenthesis', '(', 1), + hl('Register', '@b'), + hl('BinaryPlus', '+', 1), + hl('Register', '@c', 1), + hl('NestingParenthesis', ')'), + }) + check_parsing('(@a)+@b', 0, { + ast = { + { + 'BinaryPlus:0:4:+', + children = { + { + 'Nested:0:0:(', + children = { + 'Register(name=a):0:1:@a', + }, + }, + 'Register(name=b):0:5:@b', + }, + }, + }, + }, { + hl('NestingParenthesis', '('), + hl('Register', '@a'), + hl('NestingParenthesis', ')'), + hl('BinaryPlus', '+'), + hl('Register', '@b'), + }) + check_parsing('@a+(@b)(@c)', 0, { + -- 01234567890 + ast = { + { + 'BinaryPlus:0:2:+', + children = { + 'Register(name=a):0:0:@a', + { + 'Call:0:7:(', + children = { + { + 'Nested:0:3:(', + children = { 'Register(name=b):0:4:@b' }, + }, + 'Register(name=c):0:8:@c', + }, + }, + }, + }, + }, + }, { + hl('Register', '@a'), + hl('BinaryPlus', '+'), + hl('NestingParenthesis', '('), + hl('Register', '@b'), + hl('NestingParenthesis', ')'), + hl('CallingParenthesis', '('), + hl('Register', '@c'), + hl('CallingParenthesis', ')'), + }) + check_parsing('@a+((@b))(@c)', 0, { + -- 01234567890123456890123456789 + -- 0 1 2 + ast = { + { + 'BinaryPlus:0:2:+', + children = { + 'Register(name=a):0:0:@a', + { + 'Call:0:9:(', + children = { + { + 'Nested:0:3:(', + children = { + { + 'Nested:0:4:(', + children = { 'Register(name=b):0:5:@b' } + }, + }, + }, + 'Register(name=c):0:10:@c', + }, + }, + }, + }, + }, + }, { + hl('Register', '@a'), + hl('BinaryPlus', '+'), + hl('NestingParenthesis', '('), + hl('NestingParenthesis', '('), + hl('Register', '@b'), + hl('NestingParenthesis', ')'), + hl('NestingParenthesis', ')'), + hl('CallingParenthesis', '('), + hl('Register', '@c'), + hl('CallingParenthesis', ')'), + }) + check_parsing('@a+((@b))+@c', 0, { + -- 01234567890123456890123456789 + -- 0 1 2 + ast = { + { + 'BinaryPlus:0:9:+', + children = { + { + 'BinaryPlus:0:2:+', + children = { + 'Register(name=a):0:0:@a', + { + 'Nested:0:3:(', + children = { + { + 'Nested:0:4:(', + children = { 'Register(name=b):0:5:@b' } + }, + }, + }, + }, + }, + 'Register(name=c):0:10:@c', + }, + }, + }, + }, { + hl('Register', '@a'), + hl('BinaryPlus', '+'), + hl('NestingParenthesis', '('), + hl('NestingParenthesis', '('), + hl('Register', '@b'), + hl('NestingParenthesis', ')'), + hl('NestingParenthesis', ')'), + hl('BinaryPlus', '+'), + hl('Register', '@c'), + }) + check_parsing( + '@a + (@b + @c) + @d(@e) + (+@f) + ((+@g(@h))(@j)(@k))(@l)', 0, {--[[ + | | | | | | | | || | | || | | ||| || || || || + 000000000011111111112222222222333333333344444444445555555 + 012345678901234567890123456789012345678901234567890123456 + ]] + ast = {{ + 'BinaryPlus:0:31: +', + children = { + { + 'BinaryPlus:0:23: +', + children = { + { + 'BinaryPlus:0:14: +', + children = { + { + 'BinaryPlus:0:2: +', + children = { + 'Register(name=a):0:0:@a', + { + 'Nested:0:4: (', + children = { + { + 'BinaryPlus:0:8: +', + children = { + 'Register(name=b):0:6:@b', + 'Register(name=c):0:10: @c', + }, + }, + }, + }, + }, + }, + { + 'Call:0:19:(', + children = { + 'Register(name=d):0:16: @d', + 'Register(name=e):0:20:@e', + }, + }, + }, + }, + { + 'Nested:0:25: (', + children = { + { + 'UnaryPlus:0:27:+', + children = { + 'Register(name=f):0:28:@f', + }, + }, + }, + }, + }, + }, + { + 'Call:0:53:(', + children = { + { + 'Nested:0:33: (', + children = { + { + 'Call:0:48:(', + children = { + { + 'Call:0:44:(', + children = { + { + 'Nested:0:35:(', + children = { + { + 'UnaryPlus:0:36:+', + children = { + { + 'Call:0:39:(', + children = { + 'Register(name=g):0:37:@g', + 'Register(name=h):0:40:@h', + }, + }, + }, + }, + }, + }, + 'Register(name=j):0:45:@j', + }, + }, + 'Register(name=k):0:49:@k', + }, + }, + }, + }, + 'Register(name=l):0:54:@l', + }, + }, + }, + }}, + }, { + hl('Register', '@a'), + hl('BinaryPlus', '+', 1), + hl('NestingParenthesis', '(', 1), + hl('Register', '@b'), + hl('BinaryPlus', '+', 1), + hl('Register', '@c', 1), + hl('NestingParenthesis', ')'), + hl('BinaryPlus', '+', 1), + hl('Register', '@d', 1), + hl('CallingParenthesis', '('), + hl('Register', '@e'), + hl('CallingParenthesis', ')'), + hl('BinaryPlus', '+', 1), + hl('NestingParenthesis', '(', 1), + hl('UnaryPlus', '+'), + hl('Register', '@f'), + hl('NestingParenthesis', ')'), + hl('BinaryPlus', '+', 1), + hl('NestingParenthesis', '(', 1), + hl('NestingParenthesis', '('), + hl('UnaryPlus', '+'), + hl('Register', '@g'), + hl('CallingParenthesis', '('), + hl('Register', '@h'), + hl('CallingParenthesis', ')'), + hl('NestingParenthesis', ')'), + hl('CallingParenthesis', '('), + hl('Register', '@j'), + hl('CallingParenthesis', ')'), + hl('CallingParenthesis', '('), + hl('Register', '@k'), + hl('CallingParenthesis', ')'), + hl('NestingParenthesis', ')'), + hl('CallingParenthesis', '('), + hl('Register', '@l'), + hl('CallingParenthesis', ')'), + }) + check_parsing('@a)', 0, { + -- 012 + ast = { + { + 'Nested:0:2:', + children = { + 'Register(name=a):0:0:@a', + }, + }, + }, + err = { + arg = '1:)', + msg = 'E15: Unexpected closing parenthesis: %.*s', + }, + }, { + hl('Register', '@a'), + hl('InvalidNestingParenthesis', ')'), + }) + check_parsing('(@a', 0, { + -- 012 + ast = { + { + 'Nested:0:0:(', + children = { + 'Register(name=a):0:1:@a', + }, + }, + }, + err = { + arg = '3:(@a', + msg = 'E110: Missing closing parenthesis for nested expression: %.*s', + }, + }, { + hl('NestingParenthesis', '('), + hl('Register', '@a'), + }) + check_parsing('@a(@b', 0, { + -- 01234 + ast = { + { + 'Call:0:2:(', + children = { + 'Register(name=a):0:0:@a', + 'Register(name=b):0:3:@b', + }, + }, + }, + err = { + arg = '3:(@b', + msg = 'E116: Missing closing parenthesis for function call: %.*s', + }, + }, { + hl('Register', '@a'), + hl('CallingParenthesis', '('), + hl('Register', '@b'), + }) + end) +end) -- cgit From 7c97f783935ec122fbf0d7d070c00804738abd6a Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 11 Sep 2017 01:27:46 +0300 Subject: klee: Start preparing for klee tests First stage: something compiling without klee, but with a buch of dirty hacks - done. Second stage: something running under klee, able to emit useful results, but still using dirty hacks - done. Third stage: make CMake care about clang argumnets - not done, may be omitted if proves to be too hard. Not that klee can be run on CI in any case. --- src/nvim/lib/ringbuf.h | 27 ++++ test/symbolic/klee/nvim/charset.c | 10 ++ test/symbolic/klee/nvim/garray.c | 195 +++++++++++++++++++++++++++ test/symbolic/klee/nvim/gettext.c | 4 + test/symbolic/klee/nvim/mbyte.c | 18 +++ test/symbolic/klee/nvim/memory.c | 90 +++++++++++++ test/symbolic/klee/run.sh | 69 ++++++++++ test/symbolic/klee/viml_expressions_lexer.c | 86 ++++++++++++ test/symbolic/klee/viml_expressions_parser.c | 102 ++++++++++++++ 9 files changed, 601 insertions(+) create mode 100644 test/symbolic/klee/nvim/charset.c create mode 100644 test/symbolic/klee/nvim/garray.c create mode 100644 test/symbolic/klee/nvim/gettext.c create mode 100644 test/symbolic/klee/nvim/mbyte.c create mode 100644 test/symbolic/klee/nvim/memory.c create mode 100755 test/symbolic/klee/run.sh create mode 100644 test/symbolic/klee/viml_expressions_lexer.c create mode 100644 test/symbolic/klee/viml_expressions_parser.c diff --git a/src/nvim/lib/ringbuf.h b/src/nvim/lib/ringbuf.h index 12b75ec65a..e63eae70b0 100644 --- a/src/nvim/lib/ringbuf.h +++ b/src/nvim/lib/ringbuf.h @@ -15,6 +15,7 @@ #ifndef NVIM_LIB_RINGBUF_H #define NVIM_LIB_RINGBUF_H +#include #include #include #include @@ -73,6 +74,32 @@ typedef struct { \ RBType *buf_end; \ } TypeName##RingBuffer; +/// Dummy item free macros, for use in RINGBUF_INIT +/// +/// This macros actually does nothing. +/// +/// @param[in] item Item to be freed. +#define RINGBUF_DUMMY_FREE(item) + +/// Static ring buffer +/// +/// @warning Ring buffers created with this macros must neither be freed nor +/// deallocated. +/// +/// @param scope Ring buffer scope. +/// @param TypeName Ring buffer type name. +/// @param RBType Type of the single ring buffer element. +/// @param varname Variable name. +/// @param rbsize Ring buffer size. +#define RINGBUF_STATIC(scope, TypeName, RBType, varname, rbsize) \ +static RBType _##varname##_buf[rbsize]; \ +scope TypeName##RingBuffer varname = { \ + .buf = _##varname##_buf, \ + .next = _##varname##_buf, \ + .first = NULL, \ + .buf_end = _##varname##_buf + rbsize - 1, \ +}; + /// Initialize a new ring buffer /// /// @param TypeName Ring buffer type name. Actual type name will be diff --git a/test/symbolic/klee/nvim/charset.c b/test/symbolic/klee/nvim/charset.c new file mode 100644 index 0000000000..a40488920e --- /dev/null +++ b/test/symbolic/klee/nvim/charset.c @@ -0,0 +1,10 @@ +#include + +#include "nvim/ascii.h" +#include "nvim/macros.h" +#include "nvim/charset.h" + +bool vim_isIDc(int c) +{ + return ASCII_ISALNUM(c); +} diff --git a/test/symbolic/klee/nvim/garray.c b/test/symbolic/klee/nvim/garray.c new file mode 100644 index 0000000000..260870c3c2 --- /dev/null +++ b/test/symbolic/klee/nvim/garray.c @@ -0,0 +1,195 @@ +// This is an open source non-commercial project. Dear PVS-Studio, please check +// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + +/// @file garray.c +/// +/// Functions for handling growing arrays. + +#include +#include + +#include "nvim/vim.h" +#include "nvim/ascii.h" +#include "nvim/log.h" +#include "nvim/memory.h" +#include "nvim/path.h" +#include "nvim/garray.h" +#include "nvim/strings.h" + +// #include "nvim/globals.h" +#include "nvim/memline.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "garray.c.generated.h" +#endif + +/// Clear an allocated growing array. +void ga_clear(garray_T *gap) +{ + xfree(gap->ga_data); + + // Initialize growing array without resetting itemsize or growsize + gap->ga_data = NULL; + gap->ga_maxlen = 0; + gap->ga_len = 0; +} + +/// Clear a growing array that contains a list of strings. +/// +/// @param gap +void ga_clear_strings(garray_T *gap) +{ + GA_DEEP_CLEAR_PTR(gap); +} + +/// Initialize a growing array. +/// +/// @param gap +/// @param itemsize +/// @param growsize +void ga_init(garray_T *gap, int itemsize, int growsize) +{ + gap->ga_data = NULL; + gap->ga_maxlen = 0; + gap->ga_len = 0; + gap->ga_itemsize = itemsize; + ga_set_growsize(gap, growsize); +} + +/// A setter for the growsize that guarantees it will be at least 1. +/// +/// @param gap +/// @param growsize +void ga_set_growsize(garray_T *gap, int growsize) +{ + if (growsize < 1) { + WLOG("trying to set an invalid ga_growsize: %d", growsize); + gap->ga_growsize = 1; + } else { + gap->ga_growsize = growsize; + } +} + +/// Make room in growing array "gap" for at least "n" items. +/// +/// @param gap +/// @param n +void ga_grow(garray_T *gap, int n) +{ + if (gap->ga_maxlen - gap->ga_len >= n) { + // the garray still has enough space, do nothing + return; + } + + if (gap->ga_growsize < 1) { + WLOG("ga_growsize(%d) is less than 1", gap->ga_growsize); + } + + // the garray grows by at least growsize + if (n < gap->ga_growsize) { + n = gap->ga_growsize; + } + int new_maxlen = gap->ga_len + n; + + size_t new_size = (size_t)(gap->ga_itemsize * new_maxlen); + size_t old_size = (size_t)(gap->ga_itemsize * gap->ga_maxlen); + + // reallocate and clear the new memory + char *pp = xrealloc(gap->ga_data, new_size); + memset(pp + old_size, 0, new_size - old_size); + + gap->ga_maxlen = new_maxlen; + gap->ga_data = pp; +} + +/// For a growing array that contains a list of strings: concatenate all the +/// strings with sep as separator. +/// +/// @param gap +/// @param sep +/// +/// @returns the concatenated strings +char_u *ga_concat_strings_sep(const garray_T *gap, const char *sep) + FUNC_ATTR_NONNULL_RET +{ + const size_t nelem = (size_t) gap->ga_len; + const char **strings = gap->ga_data; + + if (nelem == 0) { + return (char_u *) xstrdup(""); + } + + size_t len = 0; + for (size_t i = 0; i < nelem; i++) { + len += strlen(strings[i]); + } + + // add some space for the (num - 1) separators + len += (nelem - 1) * strlen(sep); + char *const ret = xmallocz(len); + + char *s = ret; + for (size_t i = 0; i < nelem - 1; i++) { + s = xstpcpy(s, strings[i]); + s = xstpcpy(s, sep); + } + strcpy(s, strings[nelem - 1]); + + return (char_u *) ret; +} + +/// For a growing array that contains a list of strings: concatenate all the +/// strings with a separating comma. +/// +/// @param gap +/// +/// @returns the concatenated strings +char_u* ga_concat_strings(const garray_T *gap) FUNC_ATTR_NONNULL_RET +{ + return ga_concat_strings_sep(gap, ","); +} + +/// Concatenate a string to a growarray which contains characters. +/// When "s" is NULL does not do anything. +/// +/// WARNING: +/// - Does NOT copy the NUL at the end! +/// - The parameter may not overlap with the growing array +/// +/// @param gap +/// @param s +void ga_concat(garray_T *gap, const char_u *restrict s) +{ + if (s == NULL) { + return; + } + + ga_concat_len(gap, (const char *restrict) s, strlen((char *) s)); +} + +/// Concatenate a string to a growarray which contains characters +/// +/// @param[out] gap Growarray to modify. +/// @param[in] s String to concatenate. +/// @param[in] len String length. +void ga_concat_len(garray_T *const gap, const char *restrict s, + const size_t len) + FUNC_ATTR_NONNULL_ALL +{ + if (len) { + ga_grow(gap, (int) len); + char *data = gap->ga_data; + memcpy(data + gap->ga_len, s, len); + gap->ga_len += (int) len; + } +} + +/// Append one byte to a growarray which contains bytes. +/// +/// @param gap +/// @param c +void ga_append(garray_T *gap, char c) +{ + GA_APPEND(char, gap, c); +} + diff --git a/test/symbolic/klee/nvim/gettext.c b/test/symbolic/klee/nvim/gettext.c new file mode 100644 index 0000000000..b9cc98d770 --- /dev/null +++ b/test/symbolic/klee/nvim/gettext.c @@ -0,0 +1,4 @@ +char *gettext(const char *s) +{ + return (char *)s; +} diff --git a/test/symbolic/klee/nvim/mbyte.c b/test/symbolic/klee/nvim/mbyte.c new file mode 100644 index 0000000000..394d17b700 --- /dev/null +++ b/test/symbolic/klee/nvim/mbyte.c @@ -0,0 +1,18 @@ +#include + +#include "nvim/types.h" +#include "nvim/mbyte.h" +#include "nvim/ascii.h" + +char_u *string_convert(const vimconv_T *conv, char_u *data, size_t *size) +{ + return NULL; +} + +int utfc_ptr2len_len(const char_u *p, int size) +{ + if (size < 1 || *p == NUL) { + return 0; + } + return 1; +} diff --git a/test/symbolic/klee/nvim/memory.c b/test/symbolic/klee/nvim/memory.c new file mode 100644 index 0000000000..7e924a410a --- /dev/null +++ b/test/symbolic/klee/nvim/memory.c @@ -0,0 +1,90 @@ +#include +#include + +#include "nvim/lib/ringbuf.h" + +enum { RB_SIZE = 1024 }; + +typedef struct { + void *ptr; + size_t size; +} AllocRecord; + +RINGBUF_TYPEDEF(AllocRecords, AllocRecord) +RINGBUF_INIT(AllocRecords, arecs, AllocRecord, RINGBUF_DUMMY_FREE) +RINGBUF_STATIC(static, AllocRecords, AllocRecord, arecs, RB_SIZE) + +size_t allocated_memory = 0; + +void *xmalloc(const size_t size) +{ + void *ret = malloc(size); + allocated_memory += size; + assert(arecs_rb_length(&arecs) < RB_SIZE); + arecs_rb_push(&arecs, (AllocRecord) { + .ptr = ret, + .size = size, + }); + return ret; +} + +void xfree(void *const p) +{ + RINGBUF_FORALL(&arecs, AllocRecord, arec) { + if (arec->ptr == p) { + allocated_memory -= arec->size; + arecs_rb_remove(&arecs, arecs_rb_find_idx(&arecs, arec)); + return; + } + } + assert(false); +} + +void *xrealloc(void *const p, size_t new_size) +{ + void *ret = realloc(p, new_size); + RINGBUF_FORALL(&arecs, AllocRecord, arec) { + if (arec->ptr == p) { + allocated_memory -= arec->size; + allocated_memory += new_size; + arec->ptr = ret; + arec->size = new_size; + return ret; + } + } + assert(false); + return (void *)(intptr_t)1; +} + +char *xstrdup(const char *str) + FUNC_ATTR_MALLOC FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_RET + FUNC_ATTR_NONNULL_ALL +{ + return xmemdupz(str, strlen(str)); +} + +void *xmallocz(size_t size) + FUNC_ATTR_MALLOC FUNC_ATTR_NONNULL_RET FUNC_ATTR_WARN_UNUSED_RESULT +{ + size_t total_size = size + 1; + assert(total_size > size); + + void *ret = xmalloc(total_size); + ((char *)ret)[size] = 0; + + return ret; +} + +char *xstpcpy(char *restrict dst, const char *restrict src) + FUNC_ATTR_NONNULL_RET FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL +{ + const size_t len = strlen(src); + return (char *)memcpy(dst, src, len + 1) + len; +} + +void *xmemdupz(const void *data, size_t len) + FUNC_ATTR_MALLOC FUNC_ATTR_NONNULL_RET FUNC_ATTR_WARN_UNUSED_RESULT + FUNC_ATTR_NONNULL_ALL +{ + return memcpy(xmallocz(len), data, len); +} diff --git a/test/symbolic/klee/run.sh b/test/symbolic/klee/run.sh new file mode 100755 index 0000000000..388903c234 --- /dev/null +++ b/test/symbolic/klee/run.sh @@ -0,0 +1,69 @@ +#!/bin/sh + +set -e +set -x +test -z "$POSH_VERSION" && set -u + +PROJECT_SOURCE_DIR=. +PROJECT_BINARY_DIR="$PROJECT_SOURCE_DIR/build" +KLEE_TEST_DIR="$PROJECT_SOURCE_DIR/test/symbolic/klee" +KLEE_BIN_DIR="$PROJECT_BINARY_DIR/klee" +KLEE_OUT_DIR="$KLEE_BIN_DIR/out" + +main() { + local compile= + if test "$1" = "-c" ; then + compile=1 + shift + fi + local test="$1" ; shift + + local includes= + includes="$includes -I$KLEE_TEST_DIR" + includes="$includes -I/home/klee/klee_src/include" + includes="$includes -I$PROJECT_SOURCE_DIR/src" + includes="$includes -I$PROJECT_BINARY_DIR/src/nvim/auto" + includes="$includes -I$PROJECT_BINARY_DIR/include" + includes="$includes -I$PROJECT_BINARY_DIR/config" + includes="$includes -I/host-includes" + + local defines= + defines="$defines -DMIN_LOG_LEVEL=9999" + defines="$defines -DINCLUDE_GENERATED_DECLARATIONS" + + test -z "$compile" && defines="$defines -DUSE_KLEE" + + test -d "$KLEE_BIN_DIR" || mkdir -p "$KLEE_BIN_DIR" + + if test -z "$compile" ; then + test -d "$KLEE_OUT_DIR" && rm -r "$KLEE_OUT_DIR" + local line1='cd /image' + line1="$line1 && $(echo clang \ + $includes $defines \ + -o "$KLEE_BIN_DIR/a.bc" -emit-llvm -g -c \ + "$KLEE_TEST_DIR/$test.c")" + line1="$line1 && klee --libc=uclibc --posix-runtime " + line1="$line1 '--output-dir=$KLEE_OUT_DIR' '$KLEE_BIN_DIR/a.bc'" + local line2="for t in '$KLEE_OUT_DIR'/*.err" + line2="$line2 ; do ktest-tool --write-ints" + line2="$line2 \"\$(printf '%s' \"\$t\" | sed -e 's@.[^/]*\$@.out@')\"" + line2="$line2 ; done" + printf '%s\n%s\n' "$line1" "$line2" | \ + docker run \ + --volume "$(cd "$PROJECT_SOURCE_DIR" && pwd)":/image \ + --volume "/usr/include":/host-includes \ + --interactive \ + --rm \ + --ulimit='stack=-1:-1' \ + klee/klee \ + /bin/sh -x + else + clang \ + $includes $defines \ + -o "$KLEE_BIN_DIR/$test" \ + -O0 -g \ + "$KLEE_TEST_DIR/$test.c" + fi +} + +main "$@" diff --git a/test/symbolic/klee/viml_expressions_lexer.c b/test/symbolic/klee/viml_expressions_lexer.c new file mode 100644 index 0000000000..e3b5aa80cc --- /dev/null +++ b/test/symbolic/klee/viml_expressions_lexer.c @@ -0,0 +1,86 @@ +#ifdef USE_KLEE +# include +#else +# include +#endif +#include +#include +#include + +#include "nvim/viml/parser/expressions.h" +#include "nvim/viml/parser/parser.h" +#include "nvim/mbyte.h" + +#include "nvim/memory.c" +#include "nvim/mbyte.c" +#include "nvim/charset.c" +#include "nvim/garray.c" +#include "nvim/gettext.c" +#include "nvim/viml/parser/expressions.c" + +#define INPUT_SIZE 7 + +uint8_t avoid_optimizing_out; + +void simple_get_line(void *cookie, ParserLine *ret_pline) +{ + ParserLine **plines_p = (ParserLine **)cookie; + *ret_pline = **plines_p; + (*plines_p)++; +} + +int main(const int argc, const char *const *const argv, + const char *const *const environ) +{ + char input[INPUT_SIZE]; + uint8_t shift; + const bool peek = false; + avoid_optimizing_out = argc; + +#ifdef USE_KLEE + klee_make_symbolic(input, sizeof(input), "input"); + klee_make_symbolic(&shift, sizeof(shift), "shift"); + klee_assume(shift < INPUT_SIZE); +#endif + + ParserLine plines[] = { + { +#ifdef USE_KLEE + .data = &input[shift], + .size = sizeof(input) - shift, +#else + .data = (const char *)&argv[1], + .size = strlen(argv[1]), +#endif + .allocated = false, + }, + { + .data = NULL, + .size = 0, + .allocated = false, + }, + }; +#ifdef USE_KLEE + assert(plines[0].size <= INPUT_SIZE); + assert((plines[0].data[0] != 5) | (plines[0].data[0] != argc)); +#endif + ParserLine *cur_pline = &plines[0]; + + ParserState pstate = { + .reader = { + .get_line = simple_get_line, + .cookie = &cur_pline, + .lines = KV_INITIAL_VALUE, + .conv.vc_type = CONV_NONE, + }, + .pos = { 0, 0 }, + .colors = NULL, + .can_continuate = false, + }; + kvi_init(pstate.reader.lines); + + LexExprToken token = viml_pexpr_next_token(&pstate, peek); + assert((pstate.pos.line == 0) + ? (pstate.pos.col > 0) + : (pstate.pos.line == 1 && pstate.pos.col == 0)); +} diff --git a/test/symbolic/klee/viml_expressions_parser.c b/test/symbolic/klee/viml_expressions_parser.c new file mode 100644 index 0000000000..2bad1adc53 --- /dev/null +++ b/test/symbolic/klee/viml_expressions_parser.c @@ -0,0 +1,102 @@ +#ifdef USE_KLEE +# error UNFINISHED +# include +#else +# include +#endif +#include +#include +#include + +#include "nvim/viml/parser/expressions.h" +#include "nvim/viml/parser/parser.h" +#include "nvim/mbyte.h" + +#include "nvim/memory.c" +#include "nvim/mbyte.c" +#include "nvim/charset.c" +#include "nvim/garray.c" +#include "nvim/gettext.c" +#include "nvim/viml/parser/expressions.c" + +#define INPUT_SIZE 50 + +uint8_t avoid_optimizing_out; + +void simple_get_line(void *cookie, ParserLine *ret_pline) +{ + ParserLine **plines_p = (ParserLine **)cookie; + *ret_pline = **plines_p; + (*plines_p)++; +} + +int main(const int argc, const char *const *const argv, + const char *const *const environ) +{ + char input[INPUT_SIZE]; + uint8_t shift; + int flags; + const bool peek = false; + avoid_optimizing_out = argc; + +#ifndef USE_KLEE + sscanf(argv[2], "%d", &flags); +#endif + +#ifdef USE_KLEE + klee_make_symbolic(input, sizeof(input), "input"); + klee_make_symbolic(&shift, sizeof(shift), "shift"); + klee_make_symbolic(&flags, sizeof{flags}, "flags"); + klee_assume(shift < INPUT_SIZE); + klee_assume( + flags <= kExprFlagsMulti|kExprFlagsDisallowEOC|kExprFlagsPrintError); +#endif + + ParserLine plines[] = { + { +#ifdef USE_KLEE + .data = &input[shift], + .size = sizeof(input) - shift, +#else + .data = argv[1], + .size = strlen(argv[1]), +#endif + .allocated = false, + }, + { + .data = NULL, + .size = 0, + .allocated = false, + }, + }; +#ifdef USE_KLEE + assert(plines[0].size <= INPUT_SIZE); + assert((plines[0].data[0] != 5) | (plines[0].data[0] != argc)); +#endif + ParserLine *cur_pline = &plines[0]; + + ParserState pstate = { + .reader = { + .get_line = simple_get_line, + .cookie = &cur_pline, + .lines = KV_INITIAL_VALUE, + .conv.vc_type = CONV_NONE, + }, + .pos = { 0, 0 }, + .colors = NULL, + .can_continuate = false, + }; + kvi_init(pstate.reader.lines); + + const ExprAST ast = viml_pexpr_parse(&pstate, flags); + assert(ast.root != NULL + || plines[0].size == 0); + assert(ast.root != NULL || !ast.correct); + assert(ast.correct + || (ast.err.msg != NULL + && ast.err.arg != NULL + && ast.err.arg >= plines[0].data + && ((size_t)(ast.err.arg - plines[0].data) + ast.err.arg_len + <= plines[0].size))); + // FIXME: free memory and assert no memory leaks +} -- cgit From 7980614650f0aedb39bf88466e5bd3ce90429cc1 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 17 Sep 2017 17:33:03 +0300 Subject: viml/parser/expressions: Add support for figure braces (three kinds) --- src/nvim/viml/parser/expressions.c | 644 ++++++++++++++++-- src/nvim/viml/parser/expressions.h | 35 + test/unit/viml/expressions/parser_spec.lua | 1018 ++++++++++++++++++++++++++-- 3 files changed, 1596 insertions(+), 101 deletions(-) diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index b54f2eb237..f4cfed3113 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -20,10 +20,22 @@ typedef kvec_withinit_t(ExprASTNode **, 16) ExprASTStack; +/// Which nodes may be wanted typedef enum { - kELvlOperator, ///< Operators: function call, subscripts, binary operators, … - kELvlValue, ///< Actual value: literals, variables, nested expressions. -} ExprASTLevel; + /// Operators: function call, subscripts, binary operators, … + /// + /// For unrestricted expressions. + kENodeOperator, + /// Values: literals, variables, nested expressions, unary operators. + /// + /// For unrestricted expressions as well, implies that top item in AST stack + /// points to NULL. + kENodeValue, + /// Argument: only allows simple argument names. + kENodeArgument, + /// Argument separator: only allows commas. + kENodeArgumentSeparator, +} ExprASTWantedNode; #ifdef INCLUDE_GENERATED_DECLARATIONS # include "viml/parser/expressions.c.generated.h" @@ -456,6 +468,8 @@ viml_pexpr_next_token_adv_return: // // Used highlighting groups and assumed linkage: // +// NVimInternalError -> highlight as fg:red/bg:red +// // NVimInvalid -> Error // NVimInvalidValue -> NVimInvalid // NVimInvalidOperator -> NVimInvalid @@ -469,11 +483,33 @@ viml_pexpr_next_token_adv_return: // // NVimParenthesis -> Delimiter // +// NVimComma -> Delimiter +// NVimArrow -> Delimiter +// +// NVimLambda -> Delimiter +// NVimDict -> Delimiter +// NVimCurly -> Delimiter +// +// NVimIdentifier -> Identifier +// NVimIdentifierScope -> NVimIdentifier +// NVimIdentifierScopeDelimiter -> NVimIdentifier +// +// NVimFigureBrace -> NVimInternalError +// +// NVimInvalidComma -> NVimInvalidDelimiter // NVimInvalidSpacing -> NVimInvalid // NVimInvalidTernaryOperator -> NVimInvalidOperator // NVimInvalidRegister -> NVimInvalidValue // NVimInvalidClosingBracket -> NVimInvalidDelimiter // NVimInvalidSpacing -> NVimInvalid +// NVimInvalidArrow -> NVimInvalidDelimiter +// NVimInvalidLambda -> NVimInvalidDelimiter +// NVimInvalidDict -> NVimInvalidDelimiter +// NVimInvalidCurly -> NVimInvalidDelimiter +// NVimInvalidFigureBrace -> NVimInvalidDelimiter +// NVimInvalidIdentifier -> NVimInvalidValue +// NVimInvalidIdentifierScope -> NVimInvalidValue +// NVimInvalidIdentifierScopeDelimiter -> NVimInvalidValue // // NVimUnaryPlus -> NVimUnaryOperator // NVimBinaryPlus -> NVimBinaryOperator @@ -498,6 +534,9 @@ static inline ExprASTNode *viml_pexpr_new_node(const ExprASTNodeType type) typedef enum { kEOpLvlInvalid = 0, kEOpLvlParens, + kEOpLvlArrow, + kEOpLvlComma, + kEOpLvlColon, kEOpLvlTernary, kEOpLvlOr, kEOpLvlAnd, @@ -506,6 +545,7 @@ typedef enum { kEOpLvlMultiplication, ///< Multiplication, division and modulo. kEOpLvlUnary, ///< Unary operations: not, minus, plus. kEOpLvlSubscript, ///< Subscripts. + kEOpLvlComplexIdentifier, ///< Plain identifier, curly braces name. kEOpLvlValue, ///< Values: literals, variables, nested expressions, … } ExprOpLvl; @@ -520,7 +560,16 @@ static const ExprOpLvl node_type_to_op_lvl[] = { [kExprNodeOpMissing] = kEOpLvlMultiplication, [kExprNodeNested] = kEOpLvlParens, - [kExprNodeComplexIdentifier] = kEOpLvlParens, + + [kExprNodeUnknownFigure] = kEOpLvlParens, + [kExprNodeLambda] = kEOpLvlParens, + [kExprNodeDictLiteral] = kEOpLvlParens, + + [kExprNodeArrow] = kEOpLvlArrow, + + [kExprNodeComma] = kEOpLvlComma, + + [kExprNodeColon] = kEOpLvlColon, [kExprNodeTernary] = kEOpLvlTernary, @@ -531,9 +580,12 @@ static const ExprOpLvl node_type_to_op_lvl[] = { [kExprNodeSubscript] = kEOpLvlSubscript, [kExprNodeCall] = kEOpLvlSubscript, + [kExprNodeComplexIdentifier] = kEOpLvlComplexIdentifier, + [kExprNodePlainIdentifier] = kEOpLvlComplexIdentifier, + [kExprNodeCurlyBracesIdentifier] = kEOpLvlComplexIdentifier, + [kExprNodeRegister] = kEOpLvlValue, [kExprNodeListLiteral] = kEOpLvlValue, - [kExprNodePlainIdentifier] = kEOpLvlValue, }; static const ExprOpAssociativity node_type_to_op_ass[] = { @@ -541,7 +593,24 @@ static const ExprOpAssociativity node_type_to_op_ass[] = { [kExprNodeOpMissing] = kEOpAssNo, [kExprNodeNested] = kEOpAssNo, - [kExprNodeComplexIdentifier] = kEOpAssLeft, + + [kExprNodeUnknownFigure] = kEOpAssLeft, + [kExprNodeLambda] = kEOpAssNo, + [kExprNodeDictLiteral] = kEOpAssNo, + + // Does not really matter. + [kExprNodeArrow] = kEOpAssNo, + + [kExprNodeColon] = kEOpAssNo, + + // Right associativity for comma because this means easier access to arguments + // list, etc: for "[a, b, c, d]" you can access "a" in one step if it is + // represented as "list(comma(a, comma(b, comma(c, d))))" then if it is + // "list(comma(comma(comma(a, b), c), d))" in which case you will need to + // traverse all three comma() structures. And with comma operator (including + // actual comma operator from C which is not present in VimL) nobody cares + // about associativity, only about order of execution. + [kExprNodeComma] = kEOpAssRight, [kExprNodeTernary] = kEOpAssNo, @@ -552,14 +621,31 @@ static const ExprOpAssociativity node_type_to_op_ass[] = { [kExprNodeSubscript] = kEOpAssLeft, [kExprNodeCall] = kEOpAssLeft, + [kExprNodePlainIdentifier] = kEOpAssLeft, + [kExprNodeComplexIdentifier] = kEOpAssLeft, + [kExprNodeCurlyBracesIdentifier] = kEOpAssLeft, + [kExprNodeRegister] = kEOpAssNo, [kExprNodeListLiteral] = kEOpAssNo, - [kExprNodePlainIdentifier] = kEOpAssNo, }; #ifdef UNIT_TESTING #include REAL_FATTR_UNUSED +static inline void viml_pexpr_debug_print_ast_node( + const ExprASTNode *const *const eastnode_p, + const char *const prefix) +{ + if (*eastnode_p == NULL) { + fprintf(stderr, "%s %p : NULL\n", prefix, (void *)eastnode_p); + } else { + fprintf(stderr, "%s %p : %p : %c : %zu:%zu:%zu\n", + prefix, (void *)eastnode_p, (void *)(*eastnode_p), + (*eastnode_p)->type, (*eastnode_p)->start.line, + (*eastnode_p)->start.col, (*eastnode_p)->len); + } +} +REAL_FATTR_UNUSED static inline void viml_pexpr_debug_print_ast_stack( const ExprASTStack *const ast_stack, const char *const msg) @@ -567,22 +653,17 @@ static inline void viml_pexpr_debug_print_ast_stack( { fprintf(stderr, "\n%sstack: %zu:\n", msg, kv_size(*ast_stack)); for (size_t i = 0; i < kv_size(*ast_stack); i++) { - const ExprASTNode *const *const eastnode_p = ( - (const ExprASTNode *const *)kv_A(*ast_stack, i)); - if (*eastnode_p == NULL) { - fprintf(stderr, "- %p : NULL\n", (void *)eastnode_p); - } else { - fprintf(stderr, "- %p : %p : %c : %zu:%zu:%zu\n", - (void *)eastnode_p, (void *)(*eastnode_p), (*eastnode_p)->type, - (*eastnode_p)->start.line, (*eastnode_p)->start.col, - (*eastnode_p)->len); - } + viml_pexpr_debug_print_ast_node( + (const ExprASTNode *const *)kv_A(*ast_stack, i), + "-"); } } #define PSTACK(msg) \ viml_pexpr_debug_print_ast_stack(&ast_stack, #msg) #define PSTACK_P(msg) \ viml_pexpr_debug_print_ast_stack(ast_stack, #msg) +#define PNODE_P(eastnode_p, msg) \ + viml_pexpr_debug_print_ast_node((const ExprASTNode *const *)ast_stack, #msg) #endif /// Handle binary operator @@ -590,7 +671,7 @@ static inline void viml_pexpr_debug_print_ast_stack( /// This function is responsible for handling priority levels as well. static void viml_pexpr_handle_bop(ExprASTStack *const ast_stack, ExprASTNode *const bop_node, - ExprASTLevel *const want_level_p) + ExprASTWantedNode *const want_node_p) FUNC_ATTR_NONNULL_ALL { ExprASTNode **top_node_p = NULL; @@ -599,6 +680,9 @@ static void viml_pexpr_handle_bop(ExprASTStack *const ast_stack, ExprOpAssociativity top_node_ass; assert(kv_size(*ast_stack)); const ExprOpLvl bop_node_lvl = node_type_to_op_lvl[bop_node->type]; +#ifndef NDEBUG + const ExprOpAssociativity bop_node_ass = node_type_to_op_ass[bop_node->type]; +#endif do { ExprASTNode **new_top_node_p = kv_last(*ast_stack); ExprASTNode *new_top_node = *new_top_node_p; @@ -606,6 +690,8 @@ static void viml_pexpr_handle_bop(ExprASTStack *const ast_stack, const ExprOpLvl new_top_node_lvl = node_type_to_op_lvl[new_top_node->type]; const ExprOpAssociativity new_top_node_ass = ( node_type_to_op_ass[new_top_node->type]); + assert(bop_node_lvl != new_top_node_lvl + || bop_node_ass == new_top_node_ass); if (top_node_p != NULL && ((bop_node_lvl > new_top_node_lvl || (bop_node_lvl == new_top_node_lvl @@ -617,14 +703,60 @@ static void viml_pexpr_handle_bop(ExprASTStack *const ast_stack, top_node = new_top_node; top_node_lvl = new_top_node_lvl; top_node_ass = new_top_node_ass; + if (bop_node_lvl == top_node_lvl && top_node_ass == kEOpAssRight) { + break; + } } while (kv_size(*ast_stack)); - // FIXME Handle right and no associativity correctly - *top_node_p = bop_node; - bop_node->children = top_node; - assert(bop_node->children->next == NULL); - kvi_push(*ast_stack, top_node_p); - kvi_push(*ast_stack, &bop_node->children->next); - *want_level_p = kELvlValue; + // FIXME: Handle no associativity + if (top_node_ass == kEOpAssLeft || top_node_lvl != bop_node_lvl) { + // outer(op(x,y)) -> outer(new_op(op(x,y),*)) + // + // Before: top_node_p = outer(*), points to op(x,y) + // Other stack elements unknown + // + // After: top_node_p = outer(*), points to new_op(op(x,y)) + // &bop_node->children->next = new_op(op(x,y),*), points to NULL + *top_node_p = bop_node; + bop_node->children = top_node; + assert(bop_node->children->next == NULL); + kvi_push(*ast_stack, top_node_p); + kvi_push(*ast_stack, &bop_node->children->next); + } else { + assert(top_node_lvl == bop_node_lvl && top_node_ass == kEOpAssRight); + assert(top_node->children != NULL && top_node->children->next != NULL); + // outer(op(x,y)) -> outer(op(x,new_op(y,*))) + // + // Before: top_node_p = outer(*), points to op(x,y) + // Other stack elements unknown + // + // After: top_node_p = outer(*), points to op(x,new_op(y)) + // &top_node->children->next = op(x,*), points to new_op(y) + // &bop_node->children->next = new_op(y,*), points to NULL + bop_node->children = top_node->children->next; + top_node->children->next = bop_node; + assert(bop_node->children->next == NULL); + kvi_push(*ast_stack, top_node_p); + kvi_push(*ast_stack, &top_node->children->next); + kvi_push(*ast_stack, &bop_node->children->next); + } + *want_node_p = (*want_node_p == kENodeArgumentSeparator + ? kENodeArgument + : kENodeValue); +} + +/// ParserPosition literal based on ParserPosition pos with columns shifted +/// +/// Function does not check whether remaining position is valid. +/// +/// @param[in] pos Position to shift. +/// @param[in] shift Number of bytes to shift. +/// +/// @return Shifted position. +static inline ParserPosition shifted_pos(const ParserPosition pos, + const size_t shift) + FUNC_ATTR_CONST FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_WARN_UNUSED_RESULT +{ + return (ParserPosition) { .line = pos.line, .col = pos.col + shift }; } /// Get highlight group name @@ -692,12 +824,25 @@ static void viml_pexpr_handle_bop(ExprASTStack *const ast_stack, ERROR_FROM_TOKEN_AND_MSG(cur_token, _("E15: Missing operator: %.*s")); \ NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeOpMissing); \ cur_node->len = 0; \ - viml_pexpr_handle_bop(&ast_stack, cur_node, &want_level); \ - is_invalid = true; \ + viml_pexpr_handle_bop(&ast_stack, cur_node, &want_node); \ goto viml_pexpr_parse_process_token; \ } \ } while (0) +/// Record missing value: for things like "* 5" +/// +/// @param[in] msg Error message. +#define ADD_VALUE_IF_MISSING(msg) \ + do { \ + if (want_node == kENodeValue) { \ + ERROR_FROM_TOKEN_AND_MSG(cur_token, (msg)); \ + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeMissing); \ + cur_node->len = 0; \ + *top_node_p = cur_node; \ + want_node = kENodeOperator; \ + } \ + } while (0) + /// Set AST error, unless AST already is not correct /// /// @param[out] ret_ast AST to set error in. @@ -721,14 +866,42 @@ static inline void east_set_error(ExprAST *const ret_ast, ret_ast->err.arg = pline.data + start.col; } -/// Set error from the given kExprLexInvalid token and given message +/// Set error from the given token and given message #define ERROR_FROM_TOKEN_AND_MSG(cur_token, msg) \ - east_set_error(&ast, pstate, msg, cur_token.start) + do { \ + is_invalid = true; \ + east_set_error(&ast, pstate, msg, cur_token.start); \ + } while (0) + +/// Like #ERROR_FROM_TOKEN_AND_MSG, but gets position from a node +#define ERROR_FROM_NODE_AND_MSG(node, msg) \ + do { \ + is_invalid = true; \ + east_set_error(&ast, pstate, msg, node->start); \ + } while (0) /// Set error from the given kExprLexInvalid token #define ERROR_FROM_TOKEN(cur_token) \ ERROR_FROM_TOKEN_AND_MSG(cur_token, cur_token.data.err.msg) +/// Select figure brace type, altering highlighting as well if needed +/// +/// @param[out] node Node to modify type. +/// @param[in] new_type New type, one of ExprASTNodeType values without +/// kExprNode prefix. +/// @param[in] hl Corresponding highlighting, passed as an argument to #HL. +#define SELECT_FIGURE_BRACE_TYPE(node, new_type, hl) \ + do { \ + ExprASTNode *const node_ = (node); \ + assert(node_->type == kExprNodeUnknownFigure \ + || node_->type == kExprNode##new_type); \ + node_->type = kExprNode##new_type; \ + if (pstate->colors) { \ + kv_A(*pstate->colors, node_->data.fig.opening_hl_idx).group = \ + HL(hl); \ + } \ + } while (0) + /// Parse one VimL expression /// /// @param pstate Parser state. @@ -751,13 +924,15 @@ ExprAST viml_pexpr_parse(ParserState *const pstate, const int flags) kvi_init(ast_stack); kvi_push(ast_stack, &ast.root); // Expressions stack: - // 1. *last is NULL if want_level is kExprLexValue. Indicates where expression + // 1. *last is NULL if want_node is kExprLexValue. Indicates where expression // is to be put. // 2. *last is not NULL otherwise, indicates current expression to be used as // an operator argument. - ExprASTLevel want_level = kELvlValue; + ExprASTWantedNode want_node = kENodeValue; LexExprToken prev_token = { .type = kExprLexMissing }; bool highlighted_prev_spacing = false; + // Lambda node, valid when parsing lambda arguments only. + ExprASTNode *lambda_node = NULL; do { LexExprToken cur_token = viml_pexpr_next_token(pstate, true); if (cur_token.type == kExprLexEOC) { @@ -789,8 +964,7 @@ ExprAST viml_pexpr_parse(ParserState *const pstate, const int flags) viml_pexpr_parse_process_token: if (tok_type == kExprLexSpacing) { if (is_invalid) { - viml_parser_highlight(pstate, cur_token.start, cur_token.len, - HL(Spacing)); + HL_CUR_TOKEN(Spacing); } else { // Do not do anything: let regular spacing be highlighted as normal. // This also allows later to highlight spacing as invalid. @@ -803,11 +977,44 @@ viml_pexpr_parse_process_token: is_invalid = false; highlighted_prev_spacing = true; } + const ParserLine pline = pstate->reader.lines.items[cur_token.start.line]; ExprASTNode **const top_node_p = kv_last(ast_stack); ExprASTNode *cur_node = NULL; - // Keep these two asserts separate for debugging purposes. - assert(want_level == kELvlValue || *top_node_p != NULL); - assert(want_level != kELvlValue || *top_node_p == NULL); + assert((want_node == kENodeValue || want_node == kENodeArgument) + == (*top_node_p == NULL)); + if ((want_node == kENodeArgumentSeparator + && tok_type != kExprLexComma + && tok_type != kExprLexArrow) + || (want_node == kENodeArgument + && !(tok_type == kExprLexPlainIdentifier + && cur_token.data.var.scope == 0 + && !cur_token.data.var.autoload) + && tok_type != kExprLexArrow)) { + lambda_node->data.fig.type_guesses.allow_lambda = false; + if (lambda_node->children != NULL + && lambda_node->children->type == kExprNodeComma) { + // If lambda has comma child this means that parser has already seen at + // least "{arg1,", so node cannot possibly be anything, but lambda. + + // Vim may give E121 or E720 in this case, but it does not look right to + // have either because both are results of reevaluation possibly-lambda + // node as a dictionary and here this is not going to happen. + ERROR_FROM_TOKEN_AND_MSG( + cur_token, _("E15: Expected lambda arguments list or arrow: %.*s")); + } else { + // Else it may appear that possibly-lambda node is actually a dictionary + // or curly-braces-name identifier. + lambda_node = NULL; + if (want_node == kENodeArgumentSeparator) { + want_node = kENodeOperator; + } else { + want_node = kENodeValue; + } + } + } + assert(lambda_node == NULL + || want_node == kENodeArgumentSeparator + || want_node == kENodeArgument); switch (tok_type) { case kExprLexEOC: { assert(false); @@ -818,13 +1025,12 @@ viml_pexpr_parse_process_token: goto viml_pexpr_parse_process_token; } case kExprLexRegister: { - if (want_level == kELvlValue) { + if (want_node == kENodeValue) { NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeRegister); cur_node->data.reg.name = cur_token.data.reg.name; *top_node_p = cur_node; - want_level = kELvlOperator; - viml_parser_highlight(pstate, cur_token.start, cur_token.len, - HL(Register)); + want_node = kENodeOperator; + HL_CUR_TOKEN(Register); } else { // Register in operator position: e.g. @a @a OP_MISSING; @@ -832,23 +1038,343 @@ viml_pexpr_parse_process_token: break; } case kExprLexPlus: { - if (want_level == kELvlValue) { + if (want_node == kENodeValue) { // Value level: assume unary plus NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeUnaryPlus); *top_node_p = cur_node; kvi_push(ast_stack, &cur_node->children); HL_CUR_TOKEN(UnaryPlus); - } else if (want_level < kELvlValue) { + } else { NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeBinaryPlus); - viml_pexpr_handle_bop(&ast_stack, cur_node, &want_level); + viml_pexpr_handle_bop(&ast_stack, cur_node, &want_node); HL_CUR_TOKEN(BinaryPlus); } - want_level = kELvlValue; + want_node = kENodeValue; + break; + } + case kExprLexComma: { + assert(want_node != kENodeArgument); + if (want_node == kENodeValue) { + // Value level: comma appearing here is not valid. + // Note: in Vim string(,x) will give E116, this is not the case here. + ERROR_FROM_TOKEN_AND_MSG( + cur_token, _("E15: Expected value, got comma: %.*s")); + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeMissing); + cur_node->len = 0; + *top_node_p = cur_node; + want_node = (want_node == kENodeArgument + ? kENodeArgumentSeparator + : kENodeOperator); + } + if (want_node == kENodeArgumentSeparator) { + assert(lambda_node->data.fig.type_guesses.allow_lambda); + assert(lambda_node != NULL); + SELECT_FIGURE_BRACE_TYPE(lambda_node, Lambda, Lambda); + } + if (kv_size(ast_stack) < 2) { + goto viml_pexpr_parse_invalid_comma; + } + for (size_t i = 1; i < kv_size(ast_stack); i++) { + const ExprASTNode *const *const eastnode_p = + (const ExprASTNode *const *)kv_Z(ast_stack, i); + if (!((*eastnode_p)->type == kExprNodeComma + || ((*eastnode_p)->type == kExprNodeColon + && i == 1)) + || i == kv_size(ast_stack) - 1) { + switch ((*eastnode_p)->type) { + case kExprNodeLambda: { + assert(want_node == kENodeArgumentSeparator); + break; + } + case kExprNodeDictLiteral: + case kExprNodeListLiteral: + case kExprNodeCall: { + break; + } + default: { +viml_pexpr_parse_invalid_comma: + ERROR_FROM_TOKEN_AND_MSG( + cur_token, + _("E15: Comma outside of call, lambda or literal: %.*s")); + break; + } + } + break; + } + } + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeComma); + viml_pexpr_handle_bop(&ast_stack, cur_node, &want_node); + HL_CUR_TOKEN(Comma); + break; + } + case kExprLexColon: { + ADD_VALUE_IF_MISSING(_("E15: Expected value, got colon: %.*s")); + if (kv_size(ast_stack) < 2) { + goto viml_pexpr_parse_invalid_colon; + } + for (size_t i = 1; i < kv_size(ast_stack); i++) { + ExprASTNode *const *const eastnode_p = + (ExprASTNode *const *)kv_Z(ast_stack, i); + if ((*eastnode_p)->type != kExprNodeColon + || i == kv_size(ast_stack) - 1) { + switch ((*eastnode_p)->type) { + case kExprNodeUnknownFigure: { + SELECT_FIGURE_BRACE_TYPE((*eastnode_p), DictLiteral, Dict); + break; + } + case kExprNodeComma: + case kExprNodeDictLiteral: + case kExprNodeTernary: { + break; + } + default: { +viml_pexpr_parse_invalid_colon: + ERROR_FROM_TOKEN_AND_MSG( + cur_token, + _("E15: Colon outside of dictionary or ternary operator: " + "%.*s")); + break; + } + } + break; + } + } + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeColon); + viml_pexpr_handle_bop(&ast_stack, cur_node, &want_node); + // FIXME: Handle ternary operator. + HL_CUR_TOKEN(Colon); + want_node = kENodeValue; + break; + } + case kExprLexFigureBrace: { + if (cur_token.data.brc.closing) { + ExprASTNode **new_top_node_p = NULL; + // Always drop the topmost value: + // + // 1. When want_node != kENodeValue topmost item on stack is + // a *finished* left operand, which may as well be "{@a}" which + // needs not be finished again. + // 2. Otherwise it is pointing to NULL what nobody wants. + kv_drop(ast_stack, 1); + if (!kv_size(ast_stack)) { + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeUnknownFigure); + cur_node->data.fig.type_guesses.allow_lambda = false; + cur_node->data.fig.type_guesses.allow_dict = false; + cur_node->data.fig.type_guesses.allow_ident = false; + cur_node->len = 0; + if (want_node != kENodeValue) { + cur_node->children = *top_node_p; + } + *top_node_p = cur_node; + goto viml_pexpr_parse_figure_brace_closing_error; + } + if (want_node == kENodeValue) { + if ((*kv_last(ast_stack))->type != kExprNodeUnknownFigure + && (*kv_last(ast_stack))->type != kExprNodeComma) { + // kv_last being UnknownFigure may occur for empty dictionary + // literal, while Comma is expected in case of non-empty one. + ERROR_FROM_TOKEN_AND_MSG( + cur_token, + _("E15: Expected value, got closing figure brace: %.*s")); + } + } else { + if (!kv_size(ast_stack)) { + new_top_node_p = top_node_p; + goto viml_pexpr_parse_figure_brace_closing_error; + } + } + do { + new_top_node_p = kv_pop(ast_stack); + } while (kv_size(ast_stack) + && (new_top_node_p == NULL + || ((*new_top_node_p)->type != kExprNodeUnknownFigure + && (*new_top_node_p)->type != kExprNodeDictLiteral + && ((*new_top_node_p)->type + != kExprNodeCurlyBracesIdentifier) + && (*new_top_node_p)->type != kExprNodeLambda))); + ExprASTNode *new_top_node = *new_top_node_p; + switch (new_top_node->type) { + case kExprNodeUnknownFigure: { + if (new_top_node->children == NULL) { + // No children of curly braces node indicates empty dictionary. + + // Should actually be kENodeArgument, but that was changed + // earlier. + assert(want_node == kENodeValue); + assert(new_top_node->data.fig.type_guesses.allow_dict); + SELECT_FIGURE_BRACE_TYPE(new_top_node, DictLiteral, Dict); + HL_CUR_TOKEN(Dict); + } else if (new_top_node->data.fig.type_guesses.allow_ident) { + SELECT_FIGURE_BRACE_TYPE(new_top_node, CurlyBracesIdentifier, + Curly); + HL_CUR_TOKEN(Curly); + } else { + // If by this time type of the node has not already been + // guessed, but it definitely is not a curly braces name then + // it is invalid for sure. + ERROR_FROM_NODE_AND_MSG( + new_top_node, + _("E15: Don't know what figure brace means: %.*s")); + if (pstate->colors) { + // Will reset to NVimInvalidFigureBrace. + kv_A(*pstate->colors, + new_top_node->data.fig.opening_hl_idx).group = ( + HL(FigureBrace)); + } + HL_CUR_TOKEN(FigureBrace); + } + break; + } + case kExprNodeDictLiteral: { + HL_CUR_TOKEN(Dict); + break; + } + case kExprNodeCurlyBracesIdentifier: { + HL_CUR_TOKEN(Curly); + break; + } + case kExprNodeLambda: { + HL_CUR_TOKEN(Lambda); + break; + } + default: { +viml_pexpr_parse_figure_brace_closing_error: + assert(!kv_size(ast_stack)); + ERROR_FROM_TOKEN_AND_MSG( + cur_token, _("E15: Unexpected closing figure brace: %.*s")); + HL_CUR_TOKEN(FigureBrace); + break; + } + } + kvi_push(ast_stack, new_top_node_p); + want_node = kENodeOperator; + } else { + if (want_node == kENodeValue) { + HL_CUR_TOKEN(FigureBrace); + // Value: may be any of lambda, dictionary literal and curly braces + // name. + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeUnknownFigure); + cur_node->data.fig.type_guesses.allow_lambda = true; + cur_node->data.fig.type_guesses.allow_dict = true; + cur_node->data.fig.type_guesses.allow_ident = true; + if (pstate->colors) { + cur_node->data.fig.opening_hl_idx = kv_size(*pstate->colors) - 1; + } + *top_node_p = cur_node; + kvi_push(ast_stack, &cur_node->children); + want_node = kENodeArgument; + lambda_node = cur_node; + } else { + // Operator: may only be curly braces name, but only under certain + // conditions. + + // First condition is that there is no space before {. + if (prev_token.type == kExprLexSpacing) { + OP_MISSING; + } + switch ((*top_node_p)->type) { + // Second is that previous node is one of the identifiers: + // complex, plain, curly braces. + + // TODO(ZyX-I): Extend syntax to allow ${expr}. This is needed to + // handle environment variables like those bash uses for + // `export -f`: their names consist not only of alphanumeric + // characetrs. + case kExprNodeComplexIdentifier: + case kExprNodePlainIdentifier: + case kExprNodeCurlyBracesIdentifier: { + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeComplexIdentifier); + cur_node->len = 0; + viml_pexpr_handle_bop(&ast_stack, cur_node, &want_node); + ExprASTNode *const new_top_node = *kv_last(ast_stack); + assert(new_top_node->next == NULL); + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeCurlyBracesIdentifier); + new_top_node->next = cur_node; + kvi_push(ast_stack, &cur_node->children); + HL_CUR_TOKEN(Curly); + break; + } + default: { + OP_MISSING; + break; + } + } + } + } + break; + } + case kExprLexArrow: { + if (want_node == kENodeArgumentSeparator + || want_node == kENodeArgument) { + if (want_node == kENodeArgument) { + kv_drop(ast_stack, 1); + } + assert(kv_size(ast_stack) >= 1); + while ((*kv_last(ast_stack))->type != kExprNodeLambda + && (*kv_last(ast_stack))->type != kExprNodeUnknownFigure) { + kv_drop(ast_stack, 1); + } + assert((*kv_last(ast_stack)) == lambda_node); + SELECT_FIGURE_BRACE_TYPE(lambda_node, Lambda, Lambda); + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeArrow); + if (lambda_node->children == NULL) { + assert(want_node == kENodeArgument); + lambda_node->children = cur_node; + kvi_push(ast_stack, &lambda_node->children); + } else { + assert(lambda_node->children->next == NULL); + lambda_node->children->next = cur_node; + kvi_push(ast_stack, &lambda_node->children->next); + } + kvi_push(ast_stack, &cur_node->children); + lambda_node = NULL; + } else { + // Only first branch is valid. + is_invalid = true; + ADD_VALUE_IF_MISSING(_("E15: Unexpected arrow: %.*s")); + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeArrow); + viml_pexpr_handle_bop(&ast_stack, cur_node, &want_node); + } + want_node = kENodeValue; + HL_CUR_TOKEN(Arrow); + break; + } + case kExprLexPlainIdentifier: { + if (want_node == kENodeValue || want_node == kENodeArgument) { + want_node = (want_node == kENodeArgument + ? kENodeArgumentSeparator + : kENodeOperator); + // FIXME: It is not valid to have scope inside complex identifier, + // check that. + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodePlainIdentifier); + cur_node->data.var.scope = cur_token.data.var.scope; + const size_t scope_shift = (cur_token.data.var.scope == 0 + ? 0 + : 2); + cur_node->data.var.ident = (pline.data + cur_token.start.col + + scope_shift); + cur_node->data.var.ident_len = cur_token.len - scope_shift; + *top_node_p = cur_node; + if (scope_shift) { + viml_parser_highlight(pstate, cur_token.start, 1, + HL(IdentifierScope)); + viml_parser_highlight(pstate, shifted_pos(cur_token.start, 1), 1, + HL(IdentifierScopeDelimiter)); + } + if (scope_shift < cur_token.len) { + viml_parser_highlight(pstate, shifted_pos(cur_token.start, + scope_shift), + cur_token.len - scope_shift, + HL(Identifier)); + } + } else { + OP_MISSING; + } break; } case kExprLexParenthesis: { if (cur_token.data.brc.closing) { - if (want_level == kELvlValue) { + if (want_node == kENodeValue) { if (kv_size(ast_stack) > 1) { const ExprASTNode *const prev_top_node = *kv_Z(ast_stack, 1); if (prev_top_node->type == kExprNodeCall) { @@ -858,15 +1384,15 @@ viml_pexpr_parse_process_token: goto viml_pexpr_parse_no_paren_closing_error; } } - is_invalid = true; - ERROR_FROM_TOKEN_AND_MSG(cur_token, _("E15: Expected value: %.*s")); + ERROR_FROM_TOKEN_AND_MSG( + cur_token, _("E15: Expected value, got parenthesis: %.*s")); NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeMissing); cur_node->len = 0; *top_node_p = cur_node; } else { - // Always drop the topmost value: when want_level != kELvlValue + // Always drop the topmost value: when want_node != kENodeValue // topmost item on stack is a *finished* left operand, which may as - // well be "(@a)" which needs not be finished. + // well be "(@a)" which needs not be finished again. kv_drop(ast_stack, 1); } viml_pexpr_parse_no_paren_closing_error: {} @@ -892,10 +1418,9 @@ viml_pexpr_parse_no_paren_closing_error: {} if (new_top_node_p == NULL) { new_top_node_p = top_node_p; } - is_invalid = true; - HL_CUR_TOKEN(NestingParenthesis); ERROR_FROM_TOKEN_AND_MSG( cur_token, _("E15: Unexpected closing parenthesis: %.*s")); + HL_CUR_TOKEN(NestingParenthesis); cur_node = NEW_NODE(kExprNodeNested); cur_node->start = cur_token.start; cur_node->len = 0; @@ -906,14 +1431,14 @@ viml_pexpr_parse_no_paren_closing_error: {} assert(cur_node->next == NULL); } kvi_push(ast_stack, new_top_node_p); - want_level = kELvlOperator; + want_node = kENodeOperator; } else { - if (want_level == kELvlValue) { + if (want_node == kENodeValue) { NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeNested); *top_node_p = cur_node; kvi_push(ast_stack, &cur_node->children); HL_CUR_TOKEN(NestingParenthesis); - } else if (want_level == kELvlOperator) { + } else if (want_node == kENodeOperator) { if (prev_token.type == kExprLexSpacing) { // For some reason "function (args)" is a function call, but // "(funcref) (args)" is not. AFAIR this somehow involves @@ -926,13 +1451,13 @@ viml_pexpr_parse_no_paren_closing_error: {} } } NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeCall); - viml_pexpr_handle_bop(&ast_stack, cur_node, &want_level); + viml_pexpr_handle_bop(&ast_stack, cur_node, &want_node); HL_CUR_TOKEN(CallingParenthesis); } else { // Currently it is impossible to reach this. assert(false); } - want_level = kELvlValue; + want_node = kENodeValue; } break; } @@ -943,8 +1468,9 @@ viml_pexpr_parse_cycle_end: viml_parser_advance(pstate, cur_token.len); } while (true); viml_pexpr_parse_end: - if (want_level == kELvlValue) { - east_set_error(&ast, pstate, _("E15: Expected value: %.*s"), pstate->pos); + if (want_node == kENodeValue) { + east_set_error(&ast, pstate, _("E15: Expected value, got EOC: %.*s"), + pstate->pos); } else if (kv_size(ast_stack) != 1) { // Something may be wrong, check whether it really is. @@ -956,7 +1482,7 @@ viml_pexpr_parse_end: kv_drop(ast_stack, 1); while (ast.correct && kv_size(ast_stack)) { const ExprASTNode *const cur_node = (*kv_pop(ast_stack)); - // This should only happen when want_level == kELvlValue. + // This should only happen when want_node == kENodeValue. assert(cur_node != NULL); switch (cur_node->type) { case kExprNodeOpMissing: diff --git a/src/nvim/viml/parser/expressions.h b/src/nvim/viml/parser/expressions.h index 13888562df..13640ec137 100644 --- a/src/nvim/viml/parser/expressions.h +++ b/src/nvim/viml/parser/expressions.h @@ -2,6 +2,7 @@ #define NVIM_VIML_PARSER_EXPRESSIONS_H #include +#include #include #include "nvim/types.h" @@ -130,6 +131,17 @@ typedef enum { kExprNodePlainIdentifier = 'i', /// Complex identifier: variable/function name with curly braces kExprNodeComplexIdentifier = 'I', + /// Figure brace expression which is not yet known + /// + /// May resolve to any of kExprNodeDictLiteral, kExprNodeLambda or + /// kExprNodeCurlyBracesIdentifier. + kExprNodeUnknownFigure = '{', + kExprNodeLambda = '\\', ///< Lambda. + kExprNodeDictLiteral = 'd', ///< Dictionary literal. + kExprNodeCurlyBracesIdentifier= '}', ///< Part of the curly braces name. + kExprNodeComma = ',', ///< Comma “operator”. + kExprNodeColon = ':', ///< Colon “operator”. + kExprNodeArrow = '>', ///< Arrow “operator”. } ExprASTNodeType; typedef struct expr_ast_node ExprASTNode; @@ -149,6 +161,27 @@ struct expr_ast_node { struct { int name; ///< Register name, may be -1 if name not present. } reg; ///< For kExprNodeRegister. + struct { + /// Which nodes UnknownFigure can’t possibly represent. + struct { + /// True if UnknownFigure may actually represent dictionary literal. + bool allow_dict; + /// True if UnknownFigure may actually represent lambda. + bool allow_lambda; + /// True if UnknownFigure may actually be part of curly braces name. + bool allow_ident; + } type_guesses; + /// Highlight chunk index, used for rehighlighting if needed + size_t opening_hl_idx; + } fig; ///< For kExprNodeUnknownFigure. + struct { + int scope; ///< Scope character or 0 if not present. + /// Actual identifier without scope. + /// + /// Points to inside parser reader state. + const char *ident; + size_t ident_len; ///< Actual identifier length. + } var; } data; }; @@ -166,6 +199,8 @@ enum { /// /// Without the flag they are only taken into account when parsing. kExprFlagsPrintError = (1 << 2), + // WARNING: whenever you add a new flag, alter klee_assume() statement in + // viml_expressions_parser.c. } ExprParserFlags; /// Structure representing complety AST for one expression diff --git a/test/unit/viml/expressions/parser_spec.lua b/test/unit/viml/expressions/parser_spec.lua index c4bb067391..63b8baa8a8 100644 --- a/test/unit/viml/expressions/parser_spec.lua +++ b/test/unit/viml/expressions/parser_spec.lua @@ -31,6 +31,13 @@ make_enum_conv_tab(lib, { 'kExprNodeCall', 'kExprNodePlainIdentifier', 'kExprNodeComplexIdentifier', + 'kExprNodeUnknownFigure', + 'kExprNodeLambda', + 'kExprNodeDictLiteral', + 'kExprNodeCurlyBracesIdentifier', + 'kExprNodeComma', + 'kExprNodeColon', + 'kExprNodeArrow', }, 'kExprNode', function(ret) east_node_type_tab = ret end) local function conv_east_node_type(typ) @@ -59,6 +66,16 @@ local function eastnode2lua(pstate, eastnode, checked_nodes) if typ == 'Register' then typ = typ .. ('(name=%s)'):format( tostring(intchar2lua(eastnode.data.reg.name))) + elseif typ == 'PlainIdentifier' then + typ = typ .. ('(scope=%s,ident=%s)'):format( + tostring(intchar2lua(eastnode.data.var.scope)), + ffi.string(eastnode.data.var.ident, eastnode.data.var.ident_len)) + elseif (typ == 'UnknownFigure' or typ == 'DictLiteral' + or typ == 'CurlyBracesIdentifier' or typ == 'Lambda') then + typ = typ .. ('(%s)'):format( + (eastnode.data.fig.type_guesses.allow_lambda and '\\' or '-') + .. (eastnode.data.fig.type_guesses.allow_dict and 'd' or '-') + .. (eastnode.data.fig.type_guesses.allow_ident and 'i' or '-')) end ret_str = typ .. ':' .. ret_str local can_simplify = true @@ -90,8 +107,7 @@ local function east2lua(pstate, east) return { err = (not east.correct) and { msg = ffi.string(east.err.msg), - arg = ('%u:%s'):format( - tonumber(east.err.arg_len), + arg = ('%s'):format( ffi.string(east.err.arg, east.err.arg_len)), } or nil, ast = eastnodelist2lua(pstate, east.root, checked_nodes), @@ -121,31 +137,31 @@ child_call_once(function() end) describe('Expressions parser', function() - itp('works', function() - local function check_parsing(str, flags, exp_ast, exp_highlighting_fs) - local pstate = new_pstate({str}) - local east = lib.viml_pexpr_parse(pstate, flags) - local ast = east2lua(pstate, east) - eq(exp_ast, ast) - if exp_highlighting_fs then - local exp_highlighting = {} - local next_col = 0 - for i, h in ipairs(exp_highlighting_fs) do - exp_highlighting[i], next_col = h(next_col) - end - eq(exp_highlighting, phl2lua(pstate)) + local function check_parsing(str, flags, exp_ast, exp_highlighting_fs) + local pstate = new_pstate({str}) + local east = lib.viml_pexpr_parse(pstate, flags) + local ast = east2lua(pstate, east) + eq(exp_ast, ast) + if exp_highlighting_fs then + local exp_highlighting = {} + local next_col = 0 + for i, h in ipairs(exp_highlighting_fs) do + exp_highlighting[i], next_col = h(next_col) end + eq(exp_highlighting, phl2lua(pstate)) end - local function hl(group, str, shift) - return function(next_col) - local col = next_col + (shift or 0) - return (('%s:%u:%u:%s'):format( - 'NVim' .. group, - 0, - col, - str)), (col + #str) - end + end + local function hl(group, str, shift) + return function(next_col) + local col = next_col + (shift or 0) + return (('%s:%u:%u:%s'):format( + 'NVim' .. group, + 0, + col, + str)), (col + #str) end + end + itp('works with + and @a', function() check_parsing('@a', 0, { ast = { 'Register(name=a):0:0:@a', @@ -263,7 +279,7 @@ describe('Expressions parser', function() }, }, err = { - arg = '2:@b', + arg = '@b', msg = 'E15: Missing operator: %.*s', }, }, { @@ -281,7 +297,7 @@ describe('Expressions parser', function() }, }, err = { - arg = '2:@b', + arg = '@b', msg = 'E15: Missing operator: %.*s', }, }, { @@ -294,8 +310,8 @@ describe('Expressions parser', function() 'UnaryPlus:0:0:+', }, err = { - arg = '0:', - msg = 'E15: Expected value: %.*s', + arg = '', + msg = 'E15: Expected value, got EOC: %.*s', }, }, { hl('UnaryPlus', '+'), @@ -305,8 +321,8 @@ describe('Expressions parser', function() 'UnaryPlus:0:0: +', }, err = { - arg = '0:', - msg = 'E15: Expected value: %.*s', + arg = '', + msg = 'E15: Expected value, got EOC: %.*s', }, }, { hl('UnaryPlus', '+', 1), @@ -321,13 +337,15 @@ describe('Expressions parser', function() }, }, err = { - arg = '0:', - msg = 'E15: Expected value: %.*s', + arg = '', + msg = 'E15: Expected value, got EOC: %.*s', }, }, { hl('Register', '@a'), hl('BinaryPlus', '+'), }) + end) + itp('works with @a, + and parenthesis', function() check_parsing('(@a)', 0, { ast = { { @@ -352,8 +370,8 @@ describe('Expressions parser', function() }, }, err = { - arg = '1:)', - msg = 'E15: Expected value: %.*s', + arg = ')', + msg = 'E15: Expected value, got parenthesis: %.*s', }, }, { hl('NestingParenthesis', '('), @@ -369,8 +387,8 @@ describe('Expressions parser', function() }, }, err = { - arg = '1:)', - msg = 'E15: Expected value: %.*s', + arg = ')', + msg = 'E15: Expected value, got parenthesis: %.*s', }, }, { hl('InvalidNestingParenthesis', ')'), @@ -390,8 +408,8 @@ describe('Expressions parser', function() }, }, err = { - arg = '1:)', - msg = 'E15: Expected value: %.*s', + arg = ')', + msg = 'E15: Expected value, got parenthesis: %.*s', }, }, { hl('UnaryPlus', '+'), @@ -473,7 +491,7 @@ describe('Expressions parser', function() }, }, err = { - arg = '2:()', + arg = '()', msg = 'E15: Missing operator: %.*s', }, }, { @@ -838,7 +856,7 @@ describe('Expressions parser', function() }, }, err = { - arg = '1:)', + arg = ')', msg = 'E15: Unexpected closing parenthesis: %.*s', }, }, { @@ -856,7 +874,7 @@ describe('Expressions parser', function() }, }, err = { - arg = '3:(@a', + arg = '(@a', msg = 'E110: Missing closing parenthesis for nested expression: %.*s', }, }, { @@ -875,7 +893,7 @@ describe('Expressions parser', function() }, }, err = { - arg = '3:(@b', + arg = '(@b', msg = 'E116: Missing closing parenthesis for function call: %.*s', }, }, { @@ -884,4 +902,920 @@ describe('Expressions parser', function() hl('Register', '@b'), }) end) + itp('works with identifiers', function() + check_parsing('var', 0, { + ast = { + 'PlainIdentifier(scope=0,ident=var):0:0:var', + }, + }, { + hl('Identifier', 'var'), + }) + check_parsing('g:var', 0, { + ast = { + 'PlainIdentifier(scope=g,ident=var):0:0:g:var', + }, + }, { + hl('IdentifierScope', 'g'), + hl('IdentifierScopeDelimiter', ':'), + hl('Identifier', 'var'), + }) + check_parsing('g:', 0, { + ast = { + 'PlainIdentifier(scope=g,ident=):0:0:g:', + }, + }, { + hl('IdentifierScope', 'g'), + hl('IdentifierScopeDelimiter', ':'), + }) + end) + itp('works with curly braces', function() + check_parsing('{}', 0, { + ast = { + 'DictLiteral(-di):0:0:{', + }, + }, { + hl('Dict', '{'), + hl('Dict', '}'), + }) + check_parsing('{a}', 0, { + -- 012 + ast = { + { + 'CurlyBracesIdentifier(-di):0:0:{', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + }, + }, + }, + }, { + hl('Curly', '{'), + hl('Identifier', 'a'), + hl('Curly', '}'), + }) + check_parsing('{a:b}', 0, { + -- 012 + ast = { + { + 'CurlyBracesIdentifier(-di):0:0:{', + children = { + 'PlainIdentifier(scope=a,ident=b):0:1:a:b', + }, + }, + }, + }, { + hl('Curly', '{'), + hl('IdentifierScope', 'a'), + hl('IdentifierScopeDelimiter', ':'), + hl('Identifier', 'b'), + hl('Curly', '}'), + }) + check_parsing('{a:@b}', 0, { + -- 012345 + ast = { + { + 'CurlyBracesIdentifier(-di):0:0:{', + children = { + { + 'OpMissing:0:3:', + children={ + 'PlainIdentifier(scope=a,ident=):0:1:a:', + 'Register(name=b):0:3:@b', + }, + }, + }, + }, + }, + err = { + arg = '@b}', + msg = 'E15: Missing operator: %.*s', + }, + }, { + hl('Curly', '{'), + hl('IdentifierScope', 'a'), + hl('IdentifierScopeDelimiter', ':'), + hl('InvalidRegister', '@b'), + hl('Curly', '}'), + }) + check_parsing('{@a}', 0, { + ast = { + { + 'CurlyBracesIdentifier(-di):0:0:{', + children = { + 'Register(name=a):0:1:@a', + }, + }, + }, + }, { + hl('Curly', '{'), + hl('Register', '@a'), + hl('Curly', '}'), + }) + check_parsing('{->@a}', 0, { + ast = { + { + 'Lambda(\\di):0:0:{', + children = { + { + 'Arrow:0:1:->', + children = { + 'Register(name=a):0:3:@a', + }, + }, + }, + }, + }, + }, { + hl('Lambda', '{'), + hl('Arrow', '->'), + hl('Register', '@a'), + hl('Lambda', '}'), + }) + check_parsing('{->@a+@b}', 0, { + -- 012345678 + ast = { + { + 'Lambda(\\di):0:0:{', + children = { + { + 'Arrow:0:1:->', + children = { + { + 'BinaryPlus:0:5:+', + children = { + 'Register(name=a):0:3:@a', + 'Register(name=b):0:6:@b', + }, + }, + }, + }, + }, + }, + }, + }, { + hl('Lambda', '{'), + hl('Arrow', '->'), + hl('Register', '@a'), + hl('BinaryPlus', '+'), + hl('Register', '@b'), + hl('Lambda', '}'), + }) + check_parsing('{a->@a}', 0, { + -- 012345678 + ast = { + { + 'Lambda(\\di):0:0:{', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + { + 'Arrow:0:2:->', + children = { + 'Register(name=a):0:4:@a', + }, + }, + }, + }, + }, + }, { + hl('Lambda', '{'), + hl('Identifier', 'a'), + hl('Arrow', '->'), + hl('Register', '@a'), + hl('Lambda', '}'), + }) + check_parsing('{a,b->@a}', 0, { + -- 012345678 + ast = { + { + 'Lambda(\\di):0:0:{', + children = { + { + 'Comma:0:2:,', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + 'PlainIdentifier(scope=0,ident=b):0:3:b', + }, + }, + { + 'Arrow:0:4:->', + children = { + 'Register(name=a):0:6:@a', + }, + }, + }, + }, + }, + }, { + hl('Lambda', '{'), + hl('Identifier', 'a'), + hl('Comma', ','), + hl('Identifier', 'b'), + hl('Arrow', '->'), + hl('Register', '@a'), + hl('Lambda', '}'), + }) + check_parsing('{a,b,c->@a}', 0, { + -- 01234567890 + ast = { + { + 'Lambda(\\di):0:0:{', + children = { + { + 'Comma:0:2:,', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + { + 'Comma:0:4:,', + children = { + 'PlainIdentifier(scope=0,ident=b):0:3:b', + 'PlainIdentifier(scope=0,ident=c):0:5:c', + }, + }, + }, + }, + { + 'Arrow:0:6:->', + children = { + 'Register(name=a):0:8:@a', + }, + }, + }, + }, + }, + }, { + hl('Lambda', '{'), + hl('Identifier', 'a'), + hl('Comma', ','), + hl('Identifier', 'b'), + hl('Comma', ','), + hl('Identifier', 'c'), + hl('Arrow', '->'), + hl('Register', '@a'), + hl('Lambda', '}'), + }) + check_parsing('{a,b,c,d->@a}', 0, { + -- 0123456789012 + ast = { + { + 'Lambda(\\di):0:0:{', + children = { + { + 'Comma:0:2:,', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + { + 'Comma:0:4:,', + children = { + 'PlainIdentifier(scope=0,ident=b):0:3:b', + { + 'Comma:0:6:,', + children = { + 'PlainIdentifier(scope=0,ident=c):0:5:c', + 'PlainIdentifier(scope=0,ident=d):0:7:d', + }, + }, + }, + }, + }, + }, + { + 'Arrow:0:8:->', + children = { + 'Register(name=a):0:10:@a', + }, + }, + }, + }, + }, + }, { + hl('Lambda', '{'), + hl('Identifier', 'a'), + hl('Comma', ','), + hl('Identifier', 'b'), + hl('Comma', ','), + hl('Identifier', 'c'), + hl('Comma', ','), + hl('Identifier', 'd'), + hl('Arrow', '->'), + hl('Register', '@a'), + hl('Lambda', '}'), + }) + check_parsing('{a,b,c,d,->@a}', 0, { + -- 01234567890123 + ast = { + { + 'Lambda(\\di):0:0:{', + children = { + { + 'Comma:0:2:,', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + { + 'Comma:0:4:,', + children = { + 'PlainIdentifier(scope=0,ident=b):0:3:b', + { + 'Comma:0:6:,', + children = { + 'PlainIdentifier(scope=0,ident=c):0:5:c', + { + 'Comma:0:8:,', + children = { + 'PlainIdentifier(scope=0,ident=d):0:7:d', + }, + }, + }, + }, + }, + }, + }, + }, + { + 'Arrow:0:9:->', + children = { + 'Register(name=a):0:11:@a', + }, + }, + }, + }, + }, + }, { + hl('Lambda', '{'), + hl('Identifier', 'a'), + hl('Comma', ','), + hl('Identifier', 'b'), + hl('Comma', ','), + hl('Identifier', 'c'), + hl('Comma', ','), + hl('Identifier', 'd'), + hl('Comma', ','), + hl('Arrow', '->'), + hl('Register', '@a'), + hl('Lambda', '}'), + }) + check_parsing('{a,b->{c,d->{e,f->@a}}}', 0, { + -- 01234567890123456789012 + -- 0 1 2 + ast = { + { + 'Lambda(\\di):0:0:{', + children = { + { + 'Comma:0:2:,', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + 'PlainIdentifier(scope=0,ident=b):0:3:b', + }, + }, + { + 'Arrow:0:4:->', + children = { + { + 'Lambda(\\di):0:6:{', + children = { + { + 'Comma:0:8:,', + children = { + 'PlainIdentifier(scope=0,ident=c):0:7:c', + 'PlainIdentifier(scope=0,ident=d):0:9:d', + }, + }, + { + 'Arrow:0:10:->', + children = { + { + 'Lambda(\\di):0:12:{', + children = { + { + 'Comma:0:14:,', + children = { + 'PlainIdentifier(scope=0,ident=e):0:13:e', + 'PlainIdentifier(scope=0,ident=f):0:15:f', + }, + }, + { + 'Arrow:0:16:->', + children = { + 'Register(name=a):0:18:@a', + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, { + hl('Lambda', '{'), + hl('Identifier', 'a'), + hl('Comma', ','), + hl('Identifier', 'b'), + hl('Arrow', '->'), + hl('Lambda', '{'), + hl('Identifier', 'c'), + hl('Comma', ','), + hl('Identifier', 'd'), + hl('Arrow', '->'), + hl('Lambda', '{'), + hl('Identifier', 'e'), + hl('Comma', ','), + hl('Identifier', 'f'), + hl('Arrow', '->'), + hl('Register', '@a'), + hl('Lambda', '}'), + hl('Lambda', '}'), + hl('Lambda', '}'), + }) + check_parsing('{a,b->c,d}', 0, { + -- 0123456789 + ast = { + { + 'Lambda(\\di):0:0:{', + children = { + { + 'Comma:0:2:,', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + 'PlainIdentifier(scope=0,ident=b):0:3:b', + }, + }, + { + 'Arrow:0:4:->', + children = { + { + 'Comma:0:7:,', + children = { + 'PlainIdentifier(scope=0,ident=c):0:6:c', + 'PlainIdentifier(scope=0,ident=d):0:8:d', + }, + }, + }, + }, + }, + }, + }, + err = { + arg = ',d}', + msg = 'E15: Comma outside of call, lambda or literal: %.*s', + }, + }, { + hl('Lambda', '{'), + hl('Identifier', 'a'), + hl('Comma', ','), + hl('Identifier', 'b'), + hl('Arrow', '->'), + hl('Identifier', 'c'), + hl('InvalidComma', ','), + hl('Identifier', 'd'), + hl('Lambda', '}'), + }) + check_parsing('a,b,c,d', 0, { + -- 0123456789 + ast = { + { + 'Comma:0:1:,', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'Comma:0:3:,', + children = { + 'PlainIdentifier(scope=0,ident=b):0:2:b', + { + 'Comma:0:5:,', + children = { + 'PlainIdentifier(scope=0,ident=c):0:4:c', + 'PlainIdentifier(scope=0,ident=d):0:6:d', + }, + }, + }, + }, + }, + }, + }, + err = { + arg = ',b,c,d', + msg = 'E15: Comma outside of call, lambda or literal: %.*s', + }, + }, { + hl('Identifier', 'a'), + hl('InvalidComma', ','), + hl('Identifier', 'b'), + hl('InvalidComma', ','), + hl('Identifier', 'c'), + hl('InvalidComma', ','), + hl('Identifier', 'd'), + }) + check_parsing('a,b,c,d,', 0, { + -- 0123456789 + ast = { + { + 'Comma:0:1:,', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'Comma:0:3:,', + children = { + 'PlainIdentifier(scope=0,ident=b):0:2:b', + { + 'Comma:0:5:,', + children = { + 'PlainIdentifier(scope=0,ident=c):0:4:c', + { + 'Comma:0:7:,', + children = { + 'PlainIdentifier(scope=0,ident=d):0:6:d', + }, + }, + }, + }, + }, + }, + }, + }, + }, + err = { + arg = ',b,c,d,', + msg = 'E15: Comma outside of call, lambda or literal: %.*s', + }, + }, { + hl('Identifier', 'a'), + hl('InvalidComma', ','), + hl('Identifier', 'b'), + hl('InvalidComma', ','), + hl('Identifier', 'c'), + hl('InvalidComma', ','), + hl('Identifier', 'd'), + hl('InvalidComma', ','), + }) + check_parsing(',', 0, { + -- 0123456789 + ast = { + { + 'Comma:0:0:,', + children = { + 'Missing:0:0:', + }, + }, + }, + err = { + arg = ',', + msg = 'E15: Expected value, got comma: %.*s', + }, + }, { + hl('InvalidComma', ','), + }) + check_parsing('{,a->@a}', 0, { + -- 0123456789 + ast = { + { + 'CurlyBracesIdentifier(-di):0:0:{', + children = { + { + 'Arrow:0:3:->', + children = { + { + 'Comma:0:1:,', + children = { + 'Missing:0:1:', + 'PlainIdentifier(scope=0,ident=a):0:2:a', + }, + }, + 'Register(name=a):0:5:@a', + }, + }, + }, + }, + }, + err = { + arg = ',a->@a}', + msg = 'E15: Expected value, got comma: %.*s', + }, + }, { + hl('Curly', '{'), + hl('InvalidComma', ','), + hl('Identifier', 'a'), + hl('InvalidArrow', '->'), + hl('Register', '@a'), + hl('Curly', '}'), + }) + check_parsing('}', 0, { + -- 0123456789 + ast = { + 'UnknownFigure(---):0:0:', + }, + err = { + arg = '}', + msg = 'E15: Unexpected closing figure brace: %.*s', + }, + }, { + hl('InvalidFigureBrace', '}'), + }) + check_parsing('{->}', 0, { + -- 0123456789 + ast = { + { + 'Lambda(\\di):0:0:{', + children = { + 'Arrow:0:1:->', + }, + }, + }, + err = { + arg = '}', + msg = 'E15: Expected value, got closing figure brace: %.*s', + }, + }, { + hl('Lambda', '{'), + hl('Arrow', '->'), + hl('InvalidLambda', '}'), + }) + check_parsing('{a,b}', 0, { + -- 0123456789 + ast = { + { + 'Lambda(-di):0:0:{', + children = { + { + 'Comma:0:2:,', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + 'PlainIdentifier(scope=0,ident=b):0:3:b', + }, + }, + }, + }, + }, + err = { + arg = '}', + msg = 'E15: Expected lambda arguments list or arrow: %.*s', + }, + }, { + hl('Lambda', '{'), + hl('Identifier', 'a'), + hl('Comma', ','), + hl('Identifier', 'b'), + hl('InvalidLambda', '}'), + }) + check_parsing('{a,}', 0, { + -- 0123456789 + ast = { + { + 'Lambda(-di):0:0:{', + children = { + { + 'Comma:0:2:,', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + }, + }, + }, + }, + }, + err = { + arg = '}', + msg = 'E15: Expected lambda arguments list or arrow: %.*s', + }, + }, { + hl('Lambda', '{'), + hl('Identifier', 'a'), + hl('Comma', ','), + hl('InvalidLambda', '}'), + }) + check_parsing('{@a:@b}', 0, { + -- 0123456789 + ast = { + { + 'DictLiteral(-di):0:0:{', + children = { + { + 'Colon:0:3::', + children = { + 'Register(name=a):0:1:@a', + 'Register(name=b):0:4:@b', + }, + }, + }, + }, + }, + }, { + hl('Dict', '{'), + hl('Register', '@a'), + hl('Colon', ':'), + hl('Register', '@b'), + hl('Dict', '}'), + }) + check_parsing('{@a:@b,@c:@d}', 0, { + -- 0123456789012 + -- 0 1 + ast = { + { + 'DictLiteral(-di):0:0:{', + children = { + { + 'Comma:0:6:,', + children = { + { + 'Colon:0:3::', + children = { + 'Register(name=a):0:1:@a', + 'Register(name=b):0:4:@b', + }, + }, + { + 'Colon:0:9::', + children = { + 'Register(name=c):0:7:@c', + 'Register(name=d):0:10:@d', + }, + }, + }, + }, + }, + }, + }, + }, { + hl('Dict', '{'), + hl('Register', '@a'), + hl('Colon', ':'), + hl('Register', '@b'), + hl('Comma', ','), + hl('Register', '@c'), + hl('Colon', ':'), + hl('Register', '@d'), + hl('Dict', '}'), + }) + check_parsing('{@a:@b,@c:@d,@e:@f,}', 0, { + -- 01234567890123456789 + -- 0 1 + ast = { + { + 'DictLiteral(-di):0:0:{', + children = { + { + 'Comma:0:6:,', + children = { + { + 'Colon:0:3::', + children = { + 'Register(name=a):0:1:@a', + 'Register(name=b):0:4:@b', + }, + }, + { + 'Comma:0:12:,', + children = { + { + 'Colon:0:9::', + children = { + 'Register(name=c):0:7:@c', + 'Register(name=d):0:10:@d', + }, + }, + { + 'Comma:0:18:,', + children = { + { + 'Colon:0:15::', + children = { + 'Register(name=e):0:13:@e', + 'Register(name=f):0:16:@f', + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, { + hl('Dict', '{'), + hl('Register', '@a'), + hl('Colon', ':'), + hl('Register', '@b'), + hl('Comma', ','), + hl('Register', '@c'), + hl('Colon', ':'), + hl('Register', '@d'), + hl('Comma', ','), + hl('Register', '@e'), + hl('Colon', ':'), + hl('Register', '@f'), + hl('Comma', ','), + hl('Dict', '}'), + }) + check_parsing('{@a:@b,@c:@d,@e:@f,@g:}', 0, { + -- 01234567890123456789012 + -- 0 1 2 + ast = { + { + 'DictLiteral(-di):0:0:{', + children = { + { + 'Comma:0:6:,', + children = { + { + 'Colon:0:3::', + children = { + 'Register(name=a):0:1:@a', + 'Register(name=b):0:4:@b', + }, + }, + { + 'Comma:0:12:,', + children = { + { + 'Colon:0:9::', + children = { + 'Register(name=c):0:7:@c', + 'Register(name=d):0:10:@d', + }, + }, + { + 'Comma:0:18:,', + children = { + { + 'Colon:0:15::', + children = { + 'Register(name=e):0:13:@e', + 'Register(name=f):0:16:@f', + }, + }, + { + 'Colon:0:21::', + children = { + 'Register(name=g):0:19:@g', + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + err = { + arg = '}', + msg = 'E15: Expected value, got closing figure brace: %.*s', + }, + }, { + hl('Dict', '{'), + hl('Register', '@a'), + hl('Colon', ':'), + hl('Register', '@b'), + hl('Comma', ','), + hl('Register', '@c'), + hl('Colon', ':'), + hl('Register', '@d'), + hl('Comma', ','), + hl('Register', '@e'), + hl('Colon', ':'), + hl('Register', '@f'), + hl('Comma', ','), + hl('Register', '@g'), + hl('Colon', ':'), + hl('InvalidDict', '}'), + }) + check_parsing('{@a:@b,}', 0, { + -- 01234567890123 + -- 0 1 + ast = { + { + 'DictLiteral(-di):0:0:{', + children = { + { + 'Comma:0:6:,', + children = { + { + 'Colon:0:3::', + children = { + 'Register(name=a):0:1:@a', + 'Register(name=b):0:4:@b', + }, + }, + }, + }, + }, + }, + }, + }, { + hl('Dict', '{'), + hl('Register', '@a'), + hl('Colon', ':'), + hl('Register', '@b'), + hl('Comma', ','), + hl('Dict', '}'), + }) + end) + -- FIXME: Test sequence of arrows inside and outside lambdas. + -- FIXME: Test multiple arguments calling. + -- FIXME: Test autoload character and scope in lambda arguments. end) -- cgit From d4782fb1ca05e76095086bdcbc8dcea47f532d00 Mon Sep 17 00:00:00 2001 From: ZyX Date: Tue, 26 Sep 2017 00:28:34 +0300 Subject: viml/parser/expressions: Make commas actually work when calling --- src/nvim/viml/parser/expressions.c | 99 +++++++++++++++++------------- test/unit/viml/expressions/parser_spec.lua | 42 +++++++++++++ 2 files changed, 97 insertions(+), 44 deletions(-) diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index f4cfed3113..b9abf4a067 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -395,6 +395,43 @@ viml_pexpr_next_token_adv_return: return ret; } +#ifdef UNIT_TESTING +#include +REAL_FATTR_UNUSED +static inline void viml_pexpr_debug_print_ast_node( + const ExprASTNode *const *const eastnode_p, + const char *const prefix) +{ + if (*eastnode_p == NULL) { + fprintf(stderr, "%s %p : NULL\n", prefix, (void *)eastnode_p); + } else { + fprintf(stderr, "%s %p : %p : %c : %zu:%zu:%zu\n", + prefix, (void *)eastnode_p, (void *)(*eastnode_p), + (*eastnode_p)->type, (*eastnode_p)->start.line, + (*eastnode_p)->start.col, (*eastnode_p)->len); + } +} +REAL_FATTR_UNUSED +static inline void viml_pexpr_debug_print_ast_stack( + const ExprASTStack *const ast_stack, + const char *const msg) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_ALWAYS_INLINE +{ + fprintf(stderr, "\n%sstack: %zu:\n", msg, kv_size(*ast_stack)); + for (size_t i = 0; i < kv_size(*ast_stack); i++) { + viml_pexpr_debug_print_ast_node( + (const ExprASTNode *const *)kv_A(*ast_stack, i), + "-"); + } +} +#define PSTACK(msg) \ + viml_pexpr_debug_print_ast_stack(&ast_stack, #msg) +#define PSTACK_P(msg) \ + viml_pexpr_debug_print_ast_stack(ast_stack, #msg) +#define PNODE_P(eastnode_p, msg) \ + viml_pexpr_debug_print_ast_node((const ExprASTNode *const *)ast_stack, #msg) +#endif + // start = s ternary_expr s EOC // ternary_expr = binop_expr // ( s Question s ternary_expr s Colon s ternary_expr s )? @@ -560,6 +597,9 @@ static const ExprOpLvl node_type_to_op_lvl[] = { [kExprNodeOpMissing] = kEOpLvlMultiplication, [kExprNodeNested] = kEOpLvlParens, + // Note: it is kEOpLvlSubscript for “binary operator” itself, but + // kEOpLvlParens when it comes to inside the parenthesis. + [kExprNodeCall] = kEOpLvlParens, [kExprNodeUnknownFigure] = kEOpLvlParens, [kExprNodeLambda] = kEOpLvlParens, @@ -578,7 +618,6 @@ static const ExprOpLvl node_type_to_op_lvl[] = { [kExprNodeUnaryPlus] = kEOpLvlUnary, [kExprNodeSubscript] = kEOpLvlSubscript, - [kExprNodeCall] = kEOpLvlSubscript, [kExprNodeComplexIdentifier] = kEOpLvlComplexIdentifier, [kExprNodePlainIdentifier] = kEOpLvlComplexIdentifier, @@ -593,6 +632,7 @@ static const ExprOpAssociativity node_type_to_op_ass[] = { [kExprNodeOpMissing] = kEOpAssNo, [kExprNodeNested] = kEOpAssNo, + [kExprNodeCall] = kEOpAssNo, [kExprNodeUnknownFigure] = kEOpAssLeft, [kExprNodeLambda] = kEOpAssNo, @@ -619,7 +659,6 @@ static const ExprOpAssociativity node_type_to_op_ass[] = { [kExprNodeUnaryPlus] = kEOpAssNo, [kExprNodeSubscript] = kEOpAssLeft, - [kExprNodeCall] = kEOpAssLeft, [kExprNodePlainIdentifier] = kEOpAssLeft, [kExprNodeComplexIdentifier] = kEOpAssLeft, @@ -629,43 +668,6 @@ static const ExprOpAssociativity node_type_to_op_ass[] = { [kExprNodeListLiteral] = kEOpAssNo, }; -#ifdef UNIT_TESTING -#include -REAL_FATTR_UNUSED -static inline void viml_pexpr_debug_print_ast_node( - const ExprASTNode *const *const eastnode_p, - const char *const prefix) -{ - if (*eastnode_p == NULL) { - fprintf(stderr, "%s %p : NULL\n", prefix, (void *)eastnode_p); - } else { - fprintf(stderr, "%s %p : %p : %c : %zu:%zu:%zu\n", - prefix, (void *)eastnode_p, (void *)(*eastnode_p), - (*eastnode_p)->type, (*eastnode_p)->start.line, - (*eastnode_p)->start.col, (*eastnode_p)->len); - } -} -REAL_FATTR_UNUSED -static inline void viml_pexpr_debug_print_ast_stack( - const ExprASTStack *const ast_stack, - const char *const msg) - FUNC_ATTR_NONNULL_ALL FUNC_ATTR_ALWAYS_INLINE -{ - fprintf(stderr, "\n%sstack: %zu:\n", msg, kv_size(*ast_stack)); - for (size_t i = 0; i < kv_size(*ast_stack); i++) { - viml_pexpr_debug_print_ast_node( - (const ExprASTNode *const *)kv_A(*ast_stack, i), - "-"); - } -} -#define PSTACK(msg) \ - viml_pexpr_debug_print_ast_stack(&ast_stack, #msg) -#define PSTACK_P(msg) \ - viml_pexpr_debug_print_ast_stack(ast_stack, #msg) -#define PNODE_P(eastnode_p, msg) \ - viml_pexpr_debug_print_ast_node((const ExprASTNode *const *)ast_stack, #msg) -#endif - /// Handle binary operator /// /// This function is responsible for handling priority levels as well. @@ -679,17 +681,24 @@ static void viml_pexpr_handle_bop(ExprASTStack *const ast_stack, ExprOpLvl top_node_lvl; ExprOpAssociativity top_node_ass; assert(kv_size(*ast_stack)); - const ExprOpLvl bop_node_lvl = node_type_to_op_lvl[bop_node->type]; +#define NODE_LVL(typ) \ + (bop_node->type == kExprNodeCall && typ == kExprNodeCall \ + ? kEOpLvlSubscript \ + : node_type_to_op_lvl[typ]) +#define NODE_ASS(typ) \ + (bop_node->type == kExprNodeCall && typ == kExprNodeCall \ + ? kEOpAssLeft \ + : node_type_to_op_ass[typ]) + const ExprOpLvl bop_node_lvl = NODE_LVL(bop_node->type); #ifndef NDEBUG - const ExprOpAssociativity bop_node_ass = node_type_to_op_ass[bop_node->type]; + const ExprOpAssociativity bop_node_ass = NODE_ASS(bop_node->type); #endif do { ExprASTNode **new_top_node_p = kv_last(*ast_stack); ExprASTNode *new_top_node = *new_top_node_p; assert(new_top_node != NULL); - const ExprOpLvl new_top_node_lvl = node_type_to_op_lvl[new_top_node->type]; - const ExprOpAssociativity new_top_node_ass = ( - node_type_to_op_ass[new_top_node->type]); + const ExprOpLvl new_top_node_lvl = NODE_LVL(new_top_node->type); + const ExprOpAssociativity new_top_node_ass = NODE_ASS(new_top_node->type); assert(bop_node_lvl != new_top_node_lvl || bop_node_ass == new_top_node_ass); if (top_node_p != NULL @@ -742,6 +751,8 @@ static void viml_pexpr_handle_bop(ExprASTStack *const ast_stack, *want_node_p = (*want_node_p == kENodeArgumentSeparator ? kENodeArgument : kENodeValue); +#undef NODE_ASS +#undef NODE_LVL } /// ParserPosition literal based on ParserPosition pos with columns shifted diff --git a/test/unit/viml/expressions/parser_spec.lua b/test/unit/viml/expressions/parser_spec.lua index 63b8baa8a8..4d4a5f1007 100644 --- a/test/unit/viml/expressions/parser_spec.lua +++ b/test/unit/viml/expressions/parser_spec.lua @@ -901,6 +901,48 @@ describe('Expressions parser', function() hl('CallingParenthesis', '('), hl('Register', '@b'), }) + check_parsing('@a(@b, @c, @d, @e)', 0, { + -- 012345678901234567 + -- 0 1 + ast = { + { + 'Call:0:2:(', + children = { + 'Register(name=a):0:0:@a', + { + 'Comma:0:5:,', + children = { + 'Register(name=b):0:3:@b', + { + 'Comma:0:9:,', + children = { + 'Register(name=c):0:6: @c', + { + 'Comma:0:13:,', + children = { + 'Register(name=d):0:10: @d', + 'Register(name=e):0:14: @e', + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, { + hl('Register', '@a'), + hl('CallingParenthesis', '('), + hl('Register', '@b'), + hl('Comma', ','), + hl('Register', '@c', 1), + hl('Comma', ','), + hl('Register', '@d', 1), + hl('Comma', ','), + hl('Register', '@e', 1), + hl('CallingParenthesis', ')'), + }) end) itp('works with identifiers', function() check_parsing('var', 0, { -- cgit From 3cc65ac054976ef7520f0247b430ebef2f9537b7 Mon Sep 17 00:00:00 2001 From: ZyX Date: Tue, 26 Sep 2017 00:52:40 +0300 Subject: viml/parser/expressions: Make commas actually work when calling --- src/nvim/viml/parser/expressions.c | 24 +++--- test/unit/viml/expressions/parser_spec.lua | 115 +++++++++++++++++++++++++++++ 2 files changed, 125 insertions(+), 14 deletions(-) diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index b9abf4a067..7bee779c49 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -681,24 +681,22 @@ static void viml_pexpr_handle_bop(ExprASTStack *const ast_stack, ExprOpLvl top_node_lvl; ExprOpAssociativity top_node_ass; assert(kv_size(*ast_stack)); -#define NODE_LVL(typ) \ - (bop_node->type == kExprNodeCall && typ == kExprNodeCall \ - ? kEOpLvlSubscript \ - : node_type_to_op_lvl[typ]) -#define NODE_ASS(typ) \ - (bop_node->type == kExprNodeCall && typ == kExprNodeCall \ - ? kEOpAssLeft \ - : node_type_to_op_ass[typ]) - const ExprOpLvl bop_node_lvl = NODE_LVL(bop_node->type); + const ExprOpLvl bop_node_lvl = (bop_node->type == kExprNodeCall + ? kEOpLvlSubscript + : node_type_to_op_lvl[bop_node->type]); #ifndef NDEBUG - const ExprOpAssociativity bop_node_ass = NODE_ASS(bop_node->type); + const ExprOpAssociativity bop_node_ass = ( + bop_node->type == kExprNodeCall + ? kEOpAssLeft + : node_type_to_op_ass[bop_node->type]); #endif do { ExprASTNode **new_top_node_p = kv_last(*ast_stack); ExprASTNode *new_top_node = *new_top_node_p; assert(new_top_node != NULL); - const ExprOpLvl new_top_node_lvl = NODE_LVL(new_top_node->type); - const ExprOpAssociativity new_top_node_ass = NODE_ASS(new_top_node->type); + const ExprOpLvl new_top_node_lvl = node_type_to_op_lvl[new_top_node->type]; + const ExprOpAssociativity new_top_node_ass = ( + node_type_to_op_ass[new_top_node->type]); assert(bop_node_lvl != new_top_node_lvl || bop_node_ass == new_top_node_ass); if (top_node_p != NULL @@ -751,8 +749,6 @@ static void viml_pexpr_handle_bop(ExprASTStack *const ast_stack, *want_node_p = (*want_node_p == kENodeArgumentSeparator ? kENodeArgument : kENodeValue); -#undef NODE_ASS -#undef NODE_LVL } /// ParserPosition literal based on ParserPosition pos with columns shifted diff --git a/test/unit/viml/expressions/parser_spec.lua b/test/unit/viml/expressions/parser_spec.lua index 4d4a5f1007..b747a40e27 100644 --- a/test/unit/viml/expressions/parser_spec.lua +++ b/test/unit/viml/expressions/parser_spec.lua @@ -943,6 +943,121 @@ describe('Expressions parser', function() hl('Register', '@e', 1), hl('CallingParenthesis', ')'), }) + check_parsing('@a(@b(@c))', 0, { + -- 01234567890123456789012345678901234567 + -- 0 1 2 3 + ast = { + { + 'Call:0:2:(', + children = { + 'Register(name=a):0:0:@a', + { + 'Call:0:5:(', + children = { + 'Register(name=b):0:3:@b', + 'Register(name=c):0:6:@c', + }, + }, + }, + }, + }, + }, { + hl('Register', '@a'), + hl('CallingParenthesis', '('), + hl('Register', '@b'), + hl('CallingParenthesis', '('), + hl('Register', '@c'), + hl('CallingParenthesis', ')'), + hl('CallingParenthesis', ')'), + }) + check_parsing('@a(@b(@c(@d(@e), @f(@g(@h), @i(@j)))))', 0, { + -- 01234567890123456789012345678901234567 + -- 0 1 2 3 + ast = { + { + 'Call:0:2:(', + children = { + 'Register(name=a):0:0:@a', + { + 'Call:0:5:(', + children = { + 'Register(name=b):0:3:@b', + { + 'Call:0:8:(', + children = { + 'Register(name=c):0:6:@c', + { + 'Comma:0:15:,', + children = { + { + 'Call:0:11:(', + children = { + 'Register(name=d):0:9:@d', + 'Register(name=e):0:12:@e', + }, + }, + { + 'Call:0:19:(', + children = { + 'Register(name=f):0:16: @f', + { + 'Comma:0:26:,', + children = { + { + 'Call:0:22:(', + children = { + 'Register(name=g):0:20:@g', + 'Register(name=h):0:23:@h', + }, + }, + { + 'Call:0:30:(', + children = { + 'Register(name=i):0:27: @i', + 'Register(name=j):0:31:@j', + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, { + hl('Register', '@a'), + hl('CallingParenthesis', '('), + hl('Register', '@b'), + hl('CallingParenthesis', '('), + hl('Register', '@c'), + hl('CallingParenthesis', '('), + hl('Register', '@d'), + hl('CallingParenthesis', '('), + hl('Register', '@e'), + hl('CallingParenthesis', ')'), + hl('Comma', ','), + hl('Register', '@f', 1), + hl('CallingParenthesis', '('), + hl('Register', '@g'), + hl('CallingParenthesis', '('), + hl('Register', '@h'), + hl('CallingParenthesis', ')'), + hl('Comma', ','), + hl('Register', '@i', 1), + hl('CallingParenthesis', '('), + hl('Register', '@j'), + hl('CallingParenthesis', ')'), + hl('CallingParenthesis', ')'), + hl('CallingParenthesis', ')'), + hl('CallingParenthesis', ')'), + hl('CallingParenthesis', ')'), + }) end) itp('works with identifiers', function() check_parsing('var', 0, { -- cgit From 0987d3b10f36202e9f0289b50298e69aaf2fa4d2 Mon Sep 17 00:00:00 2001 From: ZyX Date: Tue, 26 Sep 2017 01:22:13 +0300 Subject: viml/parser/expressions: Make curly braces name actually work --- src/nvim/viml/parser/expressions.c | 116 ++++++--- test/unit/viml/expressions/parser_spec.lua | 384 ++++++++++++++++++++++++++++- 2 files changed, 450 insertions(+), 50 deletions(-) diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index 7bee779c49..cabf2dac58 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -909,6 +909,55 @@ static inline void east_set_error(ExprAST *const ret_ast, } \ } while (0) +/// Add identifier which should constitute complex identifier node +/// +/// This one is to be called only in case want_node is kENodeOperator. +/// +/// @param new_ident_node_code Code used to create a new identifier node and +/// update want_node and ast_stack, without +/// a trailing semicolon. +/// @param hl Highlighting name to use, passed as an argument to #HL. +#define ADD_IDENT(new_ident_node_code, hl) \ + do { \ + assert(want_node == kENodeOperator); \ + /* Operator: may only be curly braces name, but only under certain */ \ + /* conditions. */ \ +\ + /* First condition is that there is no space before a part of complex */ \ + /* identifier. */ \ + if (prev_token.type == kExprLexSpacing) { \ + OP_MISSING; \ + } \ + switch ((*top_node_p)->type) { \ + /* Second is that previous node is one of the identifiers: */ \ + /* complex, plain, curly braces. */ \ +\ + /* TODO(ZyX-I): Extend syntax to allow ${expr}. This is needed to */ \ + /* handle environment variables like those bash uses for */ \ + /* `export -f`: their names consist not only of alphanumeric */ \ + /* characetrs. */ \ + case kExprNodeComplexIdentifier: \ + case kExprNodePlainIdentifier: \ + case kExprNodeCurlyBracesIdentifier: { \ + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeComplexIdentifier); \ + cur_node->len = 0; \ + cur_node->children = *top_node_p; \ + *top_node_p = cur_node; \ + kvi_push(ast_stack, &cur_node->children->next); \ + ExprASTNode **const new_top_node_p = kv_last(ast_stack); \ + assert(*new_top_node_p == NULL); \ + new_ident_node_code; \ + *new_top_node_p = cur_node; \ + HL_CUR_TOKEN(hl); \ + break; \ + } \ + default: { \ + OP_MISSING; \ + break; \ + } \ + } \ + } while (0) + /// Parse one VimL expression /// /// @param pstate Parser state. @@ -1272,40 +1321,18 @@ viml_pexpr_parse_figure_brace_closing_error: want_node = kENodeArgument; lambda_node = cur_node; } else { - // Operator: may only be curly braces name, but only under certain - // conditions. - - // First condition is that there is no space before {. - if (prev_token.type == kExprLexSpacing) { - OP_MISSING; - } - switch ((*top_node_p)->type) { - // Second is that previous node is one of the identifiers: - // complex, plain, curly braces. - - // TODO(ZyX-I): Extend syntax to allow ${expr}. This is needed to - // handle environment variables like those bash uses for - // `export -f`: their names consist not only of alphanumeric - // characetrs. - case kExprNodeComplexIdentifier: - case kExprNodePlainIdentifier: - case kExprNodeCurlyBracesIdentifier: { - NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeComplexIdentifier); - cur_node->len = 0; - viml_pexpr_handle_bop(&ast_stack, cur_node, &want_node); - ExprASTNode *const new_top_node = *kv_last(ast_stack); - assert(new_top_node->next == NULL); - NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeCurlyBracesIdentifier); - new_top_node->next = cur_node; - kvi_push(ast_stack, &cur_node->children); - HL_CUR_TOKEN(Curly); - break; - } - default: { - OP_MISSING; - break; - } - } + ADD_IDENT( + do { + NEW_NODE_WITH_CUR_POS(cur_node, + kExprNodeCurlyBracesIdentifier); + cur_node->data.fig.opening_hl_idx = kv_size(*pstate->colors); + cur_node->data.fig.type_guesses.allow_lambda = false; + cur_node->data.fig.type_guesses.allow_dict = false; + cur_node->data.fig.type_guesses.allow_ident = true; + kvi_push(ast_stack, &cur_node->children); + want_node = kENodeValue; + } while (0), + Curly); } } break; @@ -1351,8 +1378,6 @@ viml_pexpr_parse_figure_brace_closing_error: want_node = (want_node == kENodeArgument ? kENodeArgumentSeparator : kENodeOperator); - // FIXME: It is not valid to have scope inside complex identifier, - // check that. NEW_NODE_WITH_CUR_POS(cur_node, kExprNodePlainIdentifier); cur_node->data.var.scope = cur_token.data.var.scope; const size_t scope_shift = (cur_token.data.var.scope == 0 @@ -1374,8 +1399,22 @@ viml_pexpr_parse_figure_brace_closing_error: cur_token.len - scope_shift, HL(Identifier)); } + // FIXME: Actually, g{foo}g:foo is valid: "1?g{foo}g:foo" is like + // "g{foo}g" and not an error. } else { - OP_MISSING; + if (cur_token.data.var.scope == 0) { + ADD_IDENT( + do { + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodePlainIdentifier); + cur_node->data.var.scope = cur_token.data.var.scope; + cur_node->data.var.ident = pline.data + cur_token.start.col; + cur_node->data.var.ident_len = cur_token.len; + want_node = kENodeOperator; + } while (0), + Identifier); + } else { + OP_MISSING; + } } break; } @@ -1453,7 +1492,8 @@ viml_pexpr_parse_no_paren_closing_error: {} // intentionally inconsistent and he is not very happy with the // situation himself. if ((*top_node_p)->type != kExprNodePlainIdentifier - && (*top_node_p)->type != kExprNodeComplexIdentifier) { + && (*top_node_p)->type != kExprNodeComplexIdentifier + && (*top_node_p)->type != kExprNodeCurlyBracesIdentifier) { OP_MISSING; } } diff --git a/test/unit/viml/expressions/parser_spec.lua b/test/unit/viml/expressions/parser_spec.lua index b747a40e27..eec4cb5bd9 100644 --- a/test/unit/viml/expressions/parser_spec.lua +++ b/test/unit/viml/expressions/parser_spec.lua @@ -1059,7 +1059,7 @@ describe('Expressions parser', function() hl('CallingParenthesis', ')'), }) end) - itp('works with identifiers', function() + itp('works with variable names, including curly braces ones', function() check_parsing('var', 0, { ast = { 'PlainIdentifier(scope=0,ident=var):0:0:var', @@ -1084,16 +1084,6 @@ describe('Expressions parser', function() hl('IdentifierScope', 'g'), hl('IdentifierScopeDelimiter', ':'), }) - end) - itp('works with curly braces', function() - check_parsing('{}', 0, { - ast = { - 'DictLiteral(-di):0:0:{', - }, - }, { - hl('Dict', '{'), - hl('Dict', '}'), - }) check_parsing('{a}', 0, { -- 012 ast = { @@ -1167,6 +1157,209 @@ describe('Expressions parser', function() hl('Register', '@a'), hl('Curly', '}'), }) + check_parsing('{@a}{@b}', 0, { + -- 01234567 + ast = { + { + 'ComplexIdentifier:0:4:', + children = { + { + 'CurlyBracesIdentifier(-di):0:0:{', + children = { + 'Register(name=a):0:1:@a', + }, + }, + { + 'CurlyBracesIdentifier(--i):0:4:{', + children = { + 'Register(name=b):0:5:@b', + }, + }, + }, + }, + }, + }, { + hl('Curly', '{'), + hl('Register', '@a'), + hl('Curly', '}'), + hl('Curly', '{'), + hl('Register', '@b'), + hl('Curly', '}'), + }) + check_parsing('g:{@a}', 0, { + -- 01234567 + ast = { + { + 'ComplexIdentifier:0:2:', + children = { + 'PlainIdentifier(scope=g,ident=):0:0:g:', + { + 'CurlyBracesIdentifier(--i):0:2:{', + children = { + 'Register(name=a):0:3:@a', + }, + }, + }, + }, + }, + }, { + hl('IdentifierScope', 'g'), + hl('IdentifierScopeDelimiter', ':'), + hl('Curly', '{'), + hl('Register', '@a'), + hl('Curly', '}'), + }) + check_parsing('{@a}_test', 0, { + -- 012345678 + ast = { + { + 'ComplexIdentifier:0:4:', + children = { + { + 'CurlyBracesIdentifier(-di):0:0:{', + children = { + 'Register(name=a):0:1:@a', + }, + }, + 'PlainIdentifier(scope=0,ident=_test):0:4:_test', + }, + }, + }, + }, { + hl('Curly', '{'), + hl('Register', '@a'), + hl('Curly', '}'), + hl('Identifier', '_test'), + }) + check_parsing('g:{@a}_test', 0, { + -- 01234567890 + ast = { + { + 'ComplexIdentifier:0:2:', + children = { + 'PlainIdentifier(scope=g,ident=):0:0:g:', + { + 'ComplexIdentifier:0:6:', + children = { + { + 'CurlyBracesIdentifier(--i):0:2:{', + children = { + 'Register(name=a):0:3:@a', + }, + }, + 'PlainIdentifier(scope=0,ident=_test):0:6:_test', + }, + }, + }, + }, + }, + }, { + hl('IdentifierScope', 'g'), + hl('IdentifierScopeDelimiter', ':'), + hl('Curly', '{'), + hl('Register', '@a'), + hl('Curly', '}'), + hl('Identifier', '_test'), + }) + check_parsing('g:{@a}_test()', 0, { + -- 0123456789012 + ast = { + { + 'Call:0:11:(', + children = { + { + 'ComplexIdentifier:0:2:', + children = { + 'PlainIdentifier(scope=g,ident=):0:0:g:', + { + 'ComplexIdentifier:0:6:', + children = { + { + 'CurlyBracesIdentifier(--i):0:2:{', + children = { + 'Register(name=a):0:3:@a', + }, + }, + 'PlainIdentifier(scope=0,ident=_test):0:6:_test', + }, + }, + }, + }, + }, + }, + }, + }, { + hl('IdentifierScope', 'g'), + hl('IdentifierScopeDelimiter', ':'), + hl('Curly', '{'), + hl('Register', '@a'), + hl('Curly', '}'), + hl('Identifier', '_test'), + hl('CallingParenthesis', '('), + hl('CallingParenthesis', ')'), + }) + check_parsing('{@a} ()', 0, { + -- 0123456789012 + ast = { + { + 'Call:0:4: (', + children = { + { + 'CurlyBracesIdentifier(-di):0:0:{', + children = { + 'Register(name=a):0:1:@a', + }, + }, + }, + }, + }, + }, { + hl('Curly', '{'), + hl('Register', '@a'), + hl('Curly', '}'), + hl('CallingParenthesis', '(', 1), + hl('CallingParenthesis', ')'), + }) + check_parsing('g:{@a} ()', 0, { + -- 0123456789012 + ast = { + { + 'Call:0:6: (', + children = { + { + 'ComplexIdentifier:0:2:', + children = { + 'PlainIdentifier(scope=g,ident=):0:0:g:', + { + 'CurlyBracesIdentifier(--i):0:2:{', + children = { + 'Register(name=a):0:3:@a', + }, + }, + }, + }, + }, + }, + }, + }, { + hl('IdentifierScope', 'g'), + hl('IdentifierScopeDelimiter', ':'), + hl('Curly', '{'), + hl('Register', '@a'), + hl('Curly', '}'), + hl('CallingParenthesis', '(', 1), + hl('CallingParenthesis', ')'), + }) + end) + itp('works with lambdas and dictionaries', function() + check_parsing('{}', 0, { + ast = { + 'DictLiteral(-di):0:0:{', + }, + }, { + hl('Dict', '{'), + hl('Dict', '}'), + }) check_parsing('{->@a}', 0, { ast = { { @@ -1971,8 +2164,175 @@ describe('Expressions parser', function() hl('Comma', ','), hl('Dict', '}'), }) + check_parsing('{({f -> g})(@h)(@i)}', 0, { + -- 01234567890123456789 + -- 0 1 + ast = { + { + 'CurlyBracesIdentifier(-di):0:0:{', + children = { + { + 'Call:0:15:(', + children = { + { + 'Call:0:11:(', + children = { + { + 'Nested:0:1:(', + children = { + { + 'Lambda(\\di):0:2:{', + children = { + 'PlainIdentifier(scope=0,ident=f):0:3:f', + { + 'Arrow:0:4: ->', + children = { + 'PlainIdentifier(scope=0,ident=g):0:7: g', + }, + }, + }, + }, + }, + }, + 'Register(name=h):0:12:@h', + }, + }, + 'Register(name=i):0:16:@i', + }, + }, + }, + }, + }, + }, { + hl('Curly', '{'), + hl('NestingParenthesis', '('), + hl('Lambda', '{'), + hl('Identifier', 'f'), + hl('Arrow', '->', 1), + hl('Identifier', 'g', 1), + hl('Lambda', '}'), + hl('NestingParenthesis', ')'), + hl('CallingParenthesis', '('), + hl('Register', '@h'), + hl('CallingParenthesis', ')'), + hl('CallingParenthesis', '('), + hl('Register', '@i'), + hl('CallingParenthesis', ')'), + hl('Curly', '}'), + }) + -- FIXME the below should not crash + check_parsing('a:{{b, c -> @d + @e + ({f -> g})(@h)}(@i)}j', 0, { + -- 01234567890123456789012345678901234567890123456 + -- 0 1 2 3 4 + ast = { + { + 'ComplexIdentifier:0:2:', + children = { + 'PlainIdentifier(scope=a,ident=):0:0:g:', + { + 'ComplexIdentifier:0:6:', + children = { + { + 'CurlyBracesIdentifier(--i):0:2:{', + children = { + { + 'Call:0:37:(', + children = { + { + 'Lambda(\\di):0:3:{', + children = { + { + 'Comma:0:5:,', + children = { + 'PlainIdentifier(scope=0,ident=b):0:4:b', + 'PlainIdentifier(scope=0,ident=c):0:6: c', + }, + }, + { + 'Arrow:0:8: ->', + children = { + { + 'BinaryPlus:0:19: +', + children = { + { + 'BinaryPlus:0:14: +', + children = { + 'Register(name=d):0:11: @d', + 'Register(name=e):0:16: @e', + }, + }, + { + 'Call:0:32:(', + children = { + { + 'NestingParenthesis:0:21: (', + children = { + { + 'Lambda(\\di):0:23:{', + children = { + 'PlainIdentifier(scope=0,ident=f):0:24:f', + { + 'Arrow:0:25: ->', + children = { + 'PlainIdentifier(scope=0,ident=g):0:28: g', + }, + }, + }, + }, + }, + }, + 'Register(name=h):0:33:@h', + }, + }, + }, + }, + }, + }, + }, + }, + 'Register(name=i):0:38:@i', + }, + }, + 'PlainIdentifier(scope=0,ident=j):0:42:j', + }, + }, + 'PlainIdentifier(scope=0,ident=_test):0:42:_test', + }, + }, + }, + }, + }, + }, { + hl('IdentifierScope', 'a'), + hl('IdentifierScopeDelimiter', ':'), + hl('Curly', '{'), + hl('Lambda', '{'), + hl('Identifier', 'b'), + hl('Comma', ','), + hl('Identifier', 'c', 1), + hl('Arrow', '->', 1), + hl('Register', '@d', 1), + hl('BinaryPlus', '+', 1), + hl('Register', '@e', 1), + hl('BinaryPlus', '+', 1), + hl('NestingParenthesis', '('), + hl('Lambda', '{'), + hl('Identifier', 'f'), + hl('Arrow', '->', 1), + hl('Identifier', 'g', 1), + hl('Lambda', '}'), + hl('NestingParenthesis', ')'), + hl('CallingParenthesis', '('), + hl('Register', '@h'), + hl('CallingParenthesis', ')'), + hl('Lambda', '}'), + hl('CallingParenthesis', '('), + hl('Register', '@i'), + hl('CallingParenthesis', ')'), + hl('Curly', '}'), + hl('Identifier', 'j'), + }) end) -- FIXME: Test sequence of arrows inside and outside lambdas. - -- FIXME: Test multiple arguments calling. -- FIXME: Test autoload character and scope in lambda arguments. end) -- cgit From 9fa8f7fc0a24371f7956450d840bdae8a2fc9a51 Mon Sep 17 00:00:00 2001 From: ZyX Date: Thu, 28 Sep 2017 00:40:25 +0300 Subject: viml/parser/expressions: Add a way to adjust lexer It also adds support for kExprLexOr which for some reason was forgotten. It was only made sure that KLEE test compiles in non-KLEE mode, not that something works or that KLEE is able to run tests. --- src/nvim/viml/parser/expressions.c | 105 ++++++++---- src/nvim/viml/parser/expressions.h | 28 ++++ test/symbolic/klee/nvim/memory.c | 8 + test/symbolic/klee/viml_expressions_lexer.c | 24 ++- test/unit/viml/expressions/lexer_spec.lua | 247 ++++++++++++++++++++++------ 5 files changed, 323 insertions(+), 89 deletions(-) diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index cabf2dac58..3027c0046b 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -47,10 +47,10 @@ typedef enum { /// Get next token for the VimL expression input /// /// @param pstate Parser state. -/// @param[in] peek If true, do not advance pstate cursor. +/// @param[in] flags Flags, @see LexExprFlags. /// /// @return Next token. -LexExprToken viml_pexpr_next_token(ParserState *const pstate, const bool peek) +LexExprToken viml_pexpr_next_token(ParserState *const pstate, const int flags) FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { LexExprToken ret = { @@ -153,12 +153,33 @@ LexExprToken viml_pexpr_next_token(ParserState *const pstate, const bool peek) } // Number. - // Note: determining whether dot is (not) a part of a float needs more - // context, so lexer does not do this. - // FIXME: Resolve ambiguity by additional argument. case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { + ret.data.num.is_float = false; CHARREG(kExprLexNumber, ascii_isdigit); + if (flags & kELFlagAllowFloat) { + if (pline.size > ret.len + 1 + && pline.data[ret.len] == '.' + && ascii_isdigit(pline.data[ret.len + 1])) { + ret.len++; + ret.data.num.is_float = true; + CHARREG(kExprLexNumber, ascii_isdigit); + if (pline.size > ret.len + 1 + && (pline.data[ret.len] == 'e' + || pline.data[ret.len] == 'E') + && ((pline.size > ret.len + 2 + && (pline.data[ret.len + 1] == '+' + || pline.data[ret.len + 1] == '-') + && ascii_isdigit(pline.data[ret.len + 2])) + || ascii_isdigit(pline.data[ret.len + 1]))) { + ret.len++; + if (pline.data[ret.len] == '+' || pline.data[ret.len] == '-') { + ret.len++; + } + CHARREG(kExprLexNumber, ascii_isdigit); + } + } + } break; } @@ -187,8 +208,9 @@ LexExprToken viml_pexpr_next_token(ParserState *const pstate, const bool peek) ret.data.var.autoload = false; CHARREG(kExprLexPlainIdentifier, ISWORD); // "is" and "isnot" operators. - if ((ret.len == 2 && memcmp(pline.data, "is", 2) == 0) - || (ret.len == 5 && memcmp(pline.data, "isnot", 5) == 0)) { + if (!(flags & kELFlagIsNotCmp) + && ((ret.len == 2 && memcmp(pline.data, "is", 2) == 0) + || (ret.len == 5 && memcmp(pline.data, "isnot", 5) == 0))) { ret.type = kExprLexComparison; ret.data.cmp.type = kExprLexCmpIdentical; ret.data.cmp.inv = (ret.len == 5); @@ -197,14 +219,14 @@ LexExprToken viml_pexpr_next_token(ParserState *const pstate, const bool peek) } else if (ret.len == 1 && pline.size > 1 && strchr("sgvbwtla", schar) != NULL - && pline.data[ret.len] == ':') { + && pline.data[ret.len] == ':' + && !(flags & kELFlagForbidScope)) { ret.len++; ret.data.var.scope = schar; CHARREG(kExprLexPlainIdentifier, ISWORD_OR_AUTOLOAD); ret.data.var.autoload = ( memchr(pline.data + 2, AUTOLOAD_CHAR, ret.len - 2) != NULL); - // FIXME: Resolve ambiguity with an argument to the lexer function. // Previous CHARREG stopped at autoload character in order to make it // possible to detect `is#`. Continue now with autoload characters // included. @@ -373,7 +395,30 @@ viml_pexpr_next_token_invalid_comparison: // Expression end because Ex command ended. case NUL: case NL: { - ret.type = kExprLexEOC; + if (flags & kELFlagForbidEOC) { + ret.type = kExprLexInvalid; + ret.data.err.msg = _("E15: Unexpected EOC character: %.*s"); + ret.data.err.type = kExprLexSpacing; + } else { + ret.type = kExprLexEOC; + } + break; + } + + case '|': { + if (pline.size >= 2 && pline.data[ret.len] == '|') { + // "||" is or. + ret.len++; + ret.type = kExprLexOr; + } else if (flags & kELFlagForbidEOC) { + // Note: `=1 | 2` actually yields 1 in Vim without any + // errors. This will be changed here. + ret.type = kExprLexInvalid; + ret.data.err.msg = _("E15: Unexpected EOC character: %.*s"); + ret.data.err.type = kExprLexOr; + } else { + ret.type = kExprLexEOC; + } break; } @@ -389,7 +434,7 @@ viml_pexpr_next_token_invalid_comparison: } #undef GET_CCS viml_pexpr_next_token_adv_return: - if (!peek) { + if (!(flags & kELFlagPeek)) { viml_parser_advance(pstate, ret.len); } return ret; @@ -990,34 +1035,28 @@ ExprAST viml_pexpr_parse(ParserState *const pstate, const int flags) // Lambda node, valid when parsing lambda arguments only. ExprASTNode *lambda_node = NULL; do { - LexExprToken cur_token = viml_pexpr_next_token(pstate, true); + const int want_node_to_lexer_flags[] = { + [kENodeValue] = kELFlagIsNotCmp, + [kENodeOperator] = kELFlagForbidScope, + [kENodeArgument] = kELFlagIsNotCmp, + [kENodeArgumentSeparator] = kELFlagForbidScope, + }; + // FIXME Determine when (not) to allow floating-point numbers. + const int lexer_additional_flags = ( + kELFlagPeek + | ((flags & kExprFlagsDisallowEOC) ? kELFlagForbidEOC : 0)); + LexExprToken cur_token = viml_pexpr_next_token( + pstate, want_node_to_lexer_flags[want_node] | lexer_additional_flags); if (cur_token.type == kExprLexEOC) { - if (flags & kExprFlagsDisallowEOC) { - if (cur_token.len == 0) { - // It is end of string, break. - break; - } else { - // It is NL, NUL or bar. - // - // Note: `=1 | 2` actually yields 1 in Vim without any - // errors. This will be changed here. - cur_token.type = kExprLexInvalid; - cur_token.data.err.msg = _("E15: Unexpected EOC character: %.*s"); - const ParserLine pline = ( - pstate->reader.lines.items[cur_token.start.line]); - const char eoc_char = pline.data[cur_token.start.col]; - cur_token.data.err.type = ((eoc_char == NUL || eoc_char == NL) - ? kExprLexSpacing - : kExprLexOr); - } - } else { - break; - } + break; } LexExprTokenType tok_type = cur_token.type; const bool token_invalid = (tok_type == kExprLexInvalid); bool is_invalid = token_invalid; viml_pexpr_parse_process_token: + // May use different flags this time. + cur_token = viml_pexpr_next_token( + pstate, want_node_to_lexer_flags[want_node] | lexer_additional_flags); if (tok_type == kExprLexSpacing) { if (is_invalid) { HL_CUR_TOKEN(Spacing); diff --git a/src/nvim/viml/parser/expressions.h b/src/nvim/viml/parser/expressions.h index 13640ec137..64abab9e41 100644 --- a/src/nvim/viml/parser/expressions.h +++ b/src/nvim/viml/parser/expressions.h @@ -109,9 +109,37 @@ typedef struct { LexExprTokenType type; ///< Suggested type for parsing incorrect code. const char *msg; ///< Error message. } err; ///< For kExprLexInvalid + + struct { + bool is_float; ///< True if number is a floating-point. + } num; ///< For kExprLexNumber } data; ///< Additional data, if needed. } LexExprToken; +typedef enum { + /// If set, “pointer” to the current byte in pstate will not be shifted + kELFlagPeek = (1 << 0), + /// Determines whether scope is allowed to come before the identifier + kELFlagForbidScope = (1 << 1), + /// Determines whether floating-point numbers are allowed + /// + /// I.e. whether dot is a decimal point separator or is not a part of + /// a number at all. + kELFlagAllowFloat = (1 << 2), + /// Determines whether `is` and `isnot` are seen as comparison operators + /// + /// If set they are supposed to be just regular identifiers. + kELFlagIsNotCmp = (1 << 3), + /// Determines whether EOC tokens are allowed + /// + /// If set then it will yield Invalid token with E15 in place of EOC one if + /// “EOC” is something like "|". It is fine with emitting EOC at the end of + /// string still, with or without this flag set. + kELFlagForbidEOC = (1 << 4), + // WARNING: whenever you add a new flag, alter klee_assume() statement in + // viml_expressions_lexer.c. +} LexExprFlags; + /// Expression AST node type typedef enum { kExprNodeMissing = 'X', diff --git a/test/symbolic/klee/nvim/memory.c b/test/symbolic/klee/nvim/memory.c index 7e924a410a..1f9cdce6c0 100644 --- a/test/symbolic/klee/nvim/memory.c +++ b/test/symbolic/klee/nvim/memory.c @@ -15,11 +15,16 @@ RINGBUF_INIT(AllocRecords, arecs, AllocRecord, RINGBUF_DUMMY_FREE) RINGBUF_STATIC(static, AllocRecords, AllocRecord, arecs, RB_SIZE) size_t allocated_memory = 0; +size_t ever_allocated_memory = 0; + +size_t allocated_memory_limit = SIZE_MAX; void *xmalloc(const size_t size) { void *ret = malloc(size); allocated_memory += size; + ever_allocated_memory += size; + assert(allocated_memory <= allocated_memory_limit); assert(arecs_rb_length(&arecs) < RB_SIZE); arecs_rb_push(&arecs, (AllocRecord) { .ptr = ret, @@ -47,6 +52,9 @@ void *xrealloc(void *const p, size_t new_size) if (arec->ptr == p) { allocated_memory -= arec->size; allocated_memory += new_size; + if (new_size > arec->size) { + ever_allocated_memory += (new_size - arec->size); + } arec->ptr = ret; arec->size = new_size; return ret; diff --git a/test/symbolic/klee/viml_expressions_lexer.c b/test/symbolic/klee/viml_expressions_lexer.c index e3b5aa80cc..67f3eb7faa 100644 --- a/test/symbolic/klee/viml_expressions_lexer.c +++ b/test/symbolic/klee/viml_expressions_lexer.c @@ -34,13 +34,20 @@ int main(const int argc, const char *const *const argv, { char input[INPUT_SIZE]; uint8_t shift; - const bool peek = false; + int flags; avoid_optimizing_out = argc; +#ifndef USE_KLEE + sscanf(argv[2], "%d", &flags); +#endif + #ifdef USE_KLEE klee_make_symbolic(input, sizeof(input), "input"); klee_make_symbolic(&shift, sizeof(shift), "shift"); + klee_make_symbolic(&flags, sizeof(flags), "flags"); klee_assume(shift < INPUT_SIZE); + klee_assume(flags <= (kELFlagPeek|kELFlagAllowFloat|kELFlagForbidEOC + |kELFlagForbidScope|kELFlagIsNotCmp)); #endif ParserLine plines[] = { @@ -79,8 +86,15 @@ int main(const int argc, const char *const *const argv, }; kvi_init(pstate.reader.lines); - LexExprToken token = viml_pexpr_next_token(&pstate, peek); - assert((pstate.pos.line == 0) - ? (pstate.pos.col > 0) - : (pstate.pos.line == 1 && pstate.pos.col == 0)); + allocated_memory_limit = 0; + LexExprToken token = viml_pexpr_next_token(&pstate, flags); + if (flags & kELFlagPeek) { + assert(pstate.pos.line == 0 && pstate.pos.col == 0); + } else { + assert((pstate.pos.line == 0) + ? (pstate.pos.col > 0) + : (pstate.pos.line == 1 && pstate.pos.col == 0)); + } + assert(allocated_memory == 0); + assert(ever_allocated_memory == 0); } diff --git a/test/unit/viml/expressions/lexer_spec.lua b/test/unit/viml/expressions/lexer_spec.lua index 32182f650d..972478c2e5 100644 --- a/test/unit/viml/expressions/lexer_spec.lua +++ b/test/unit/viml/expressions/lexer_spec.lua @@ -1,5 +1,6 @@ local helpers = require('test.unit.helpers')(after_each) local viml_helpers = require('test.unit.viml.helpers') +local global_helpers = require('test.helpers') local itp = helpers.gen_itp(it) local child_call_once = helpers.child_call_once @@ -13,6 +14,8 @@ local new_pstate = viml_helpers.new_pstate local intchar2lua = viml_helpers.intchar2lua local pstate_set_str = viml_helpers.pstate_set_str +local shallowcopy = global_helpers.shallowcopy + local lib = cimport('./src/nvim/viml/parser/expressions.h') local eltkn_type_tab, eltkn_cmp_type_tab, ccs_tab, eltkn_mul_type_tab @@ -121,37 +124,81 @@ local function eltkn2lua(pstate, tkn) scope = intchar2lua(tkn.data.var.scope), autoload = (not not tkn.data.var.autoload), } + elseif ret.type == 'Number' then + ret.data = { + is_float = (not not tkn.data.num.is_float), + } elseif ret.type == 'Invalid' then ret.data = { error = ffi.string(tkn.data.err.msg) } end return ret, tkn end -local function next_eltkn(pstate) - return eltkn2lua(pstate, lib.viml_pexpr_next_token(pstate, false)) +local function next_eltkn(pstate, flags) + return eltkn2lua(pstate, lib.viml_pexpr_next_token(pstate, flags)) end describe('Expressions lexer', function() - itp('works (single tokens)', function() - local function singl_eltkn_test(typ, str, data) - local pstate = new_pstate({str}) - eq({data=data, len=#str, start={col=0, line=0}, str=str, type=typ}, - next_eltkn(pstate)) - if not ( - typ == 'Spacing' - or (typ == 'Register' and str == '@') - or ((typ == 'SingleQuotedString' or typ == 'DoubleQuotedString') - and not data.closed) - ) then - pstate = new_pstate({str .. ' '}) - eq({data=data, len=#str, start={col=0, line=0}, str=str, type=typ}, - next_eltkn(pstate)) + local flags = 0 + local should_advance = true + local function check_advance(pstate, bytes_to_advance, initial_col) + local tgt = initial_col + bytes_to_advance + if should_advance then + if pstate.reader.lines.items[0].size == tgt then + eq(1, pstate.pos.line) + eq(0, pstate.pos.col) + else + eq(0, pstate.pos.line) + eq(tgt, pstate.pos.col) end - pstate = new_pstate({'x' .. str}) - pstate.pos.col = 1 - eq({data=data, len=#str, start={col=1, line=0}, str=str, type=typ}, - next_eltkn(pstate)) + else + eq(0, pstate.pos.line) + eq(initial_col, pstate.pos.col) end + end + local function singl_eltkn_test(typ, str, data) + local pstate = new_pstate({str}) + eq({data=data, len=#str, start={col=0, line=0}, str=str, type=typ}, + next_eltkn(pstate, flags)) + check_advance(pstate, #str, 0) + if not ( + typ == 'Spacing' + or (typ == 'Register' and str == '@') + or ((typ == 'SingleQuotedString' or typ == 'DoubleQuotedString') + and not data.closed) + ) then + pstate = new_pstate({str .. ' '}) + eq({data=data, len=#str, start={col=0, line=0}, str=str, type=typ}, + next_eltkn(pstate, flags)) + check_advance(pstate, #str, 0) + end + pstate = new_pstate({'x' .. str}) + pstate.pos.col = 1 + eq({data=data, len=#str, start={col=1, line=0}, str=str, type=typ}, + next_eltkn(pstate, flags)) + check_advance(pstate, #str, 1) + end + local function scope_test(scope) + singl_eltkn_test('PlainIdentifier', scope .. ':test#var', {autoload=true, scope=scope}) + singl_eltkn_test('PlainIdentifier', scope .. ':', {autoload=false, scope=scope}) + end + local function comparison_test(op, inv_op, cmp_type) + singl_eltkn_test('Comparison', op, {type=cmp_type, inv=false, ccs='UseOption'}) + singl_eltkn_test('Comparison', inv_op, {type=cmp_type, inv=true, ccs='UseOption'}) + singl_eltkn_test('Comparison', op .. '#', {type=cmp_type, inv=false, ccs='MatchCase'}) + singl_eltkn_test('Comparison', inv_op .. '#', {type=cmp_type, inv=true, ccs='MatchCase'}) + singl_eltkn_test('Comparison', op .. '?', {type=cmp_type, inv=false, ccs='IgnoreCase'}) + singl_eltkn_test('Comparison', inv_op .. '?', {type=cmp_type, inv=true, ccs='IgnoreCase'}) + end + local function simple_test(pstate_arg, exp_type, exp_len, exp) + local pstate = new_pstate(pstate_arg) + local exp = shallowcopy(exp) + exp.type = exp_type + exp.len = exp_len or #(pstate_arg[0]) + exp.start = { col = 0, line = 0 } + eq(exp, next_eltkn(pstate, flags)) + end + local function stable_tests() singl_eltkn_test('Parenthesis', '(', {closing=false}) singl_eltkn_test('Parenthesis', ')', {closing=true}) singl_eltkn_test('Bracket', '[', {closing=false}) @@ -170,9 +217,9 @@ describe('Expressions lexer', function() singl_eltkn_test('Spacing', ' ') singl_eltkn_test('Spacing', '\t') singl_eltkn_test('Invalid', '\x01\x02\x03', {error='E15: Invalid control character present in input: %.*s'}) - singl_eltkn_test('Number', '0123') - singl_eltkn_test('Number', '0') - singl_eltkn_test('Number', '9') + singl_eltkn_test('Number', '0123', {is_float=false}) + singl_eltkn_test('Number', '0', {is_float=false}) + singl_eltkn_test('Number', '9', {is_float=false}) singl_eltkn_test('Env', '$abc') singl_eltkn_test('Env', '$') singl_eltkn_test('PlainIdentifier', 'test', {autoload=false, scope=0}) @@ -184,28 +231,8 @@ describe('Expressions lexer', function() singl_eltkn_test('PlainIdentifier', 'test#var', {autoload=true, scope=0}) singl_eltkn_test('PlainIdentifier', 'test#var#val###', {autoload=true, scope=0}) singl_eltkn_test('PlainIdentifier', 't#####', {autoload=true, scope=0}) - local function scope_test(scope) - singl_eltkn_test('PlainIdentifier', scope .. ':test#var', {autoload=true, scope=scope}) - singl_eltkn_test('PlainIdentifier', scope .. ':', {autoload=false, scope=scope}) - end - scope_test('s') - scope_test('g') - scope_test('v') - scope_test('b') - scope_test('w') - scope_test('t') - scope_test('l') - scope_test('a') - local function comparison_test(op, inv_op, cmp_type) - singl_eltkn_test('Comparison', op, {type=cmp_type, inv=false, ccs='UseOption'}) - singl_eltkn_test('Comparison', inv_op, {type=cmp_type, inv=true, ccs='UseOption'}) - singl_eltkn_test('Comparison', op .. '#', {type=cmp_type, inv=false, ccs='MatchCase'}) - singl_eltkn_test('Comparison', inv_op .. '#', {type=cmp_type, inv=true, ccs='MatchCase'}) - singl_eltkn_test('Comparison', op .. '?', {type=cmp_type, inv=false, ccs='IgnoreCase'}) - singl_eltkn_test('Comparison', inv_op .. '?', {type=cmp_type, inv=true, ccs='IgnoreCase'}) - end - comparison_test('is', 'isnot', 'Identical') singl_eltkn_test('And', '&&') + singl_eltkn_test('Or', '||') singl_eltkn_test('Invalid', '&', {error='E112: Option name missing: %.*s'}) singl_eltkn_test('Option', '&opt', {scope='Unspecified', name='opt'}) singl_eltkn_test('Option', '&t_xx', {scope='Unspecified', name='t_xx'}) @@ -245,16 +272,134 @@ describe('Expressions lexer', function() comparison_test('>=', '<', 'GreaterOrEqual') singl_eltkn_test('Minus', '-') singl_eltkn_test('Arrow', '->') + singl_eltkn_test('Invalid', '~', {error='E15: Unidentified character: %.*s'}) + simple_test({{data=nil, size=0}}, 'EOC', 0, {error='start.col >= #pstr'}) + simple_test({''}, 'EOC', 0, {error='start.col >= #pstr'}) + simple_test({'2.'}, 'Number', 1, {data={is_float=false}, str='2'}) + simple_test({'2.x'}, 'Number', 1, {data={is_float=false}, str='2'}) + end + + local function regular_scope_tests() + scope_test('s') + scope_test('g') + scope_test('v') + scope_test('b') + scope_test('w') + scope_test('t') + scope_test('l') + scope_test('a') + + simple_test({'g:'}, 'PlainIdentifier', 2, {data={scope='g', autoload=false}, str='g:'}) + simple_test({'g:is#foo'}, 'PlainIdentifier', 8, {data={scope='g', autoload=true}, str='g:is#foo'}) + simple_test({'g:isnot#foo'}, 'PlainIdentifier', 11, {data={scope='g', autoload=true}, str='g:isnot#foo'}) + end + + local function regular_is_tests() + comparison_test('is', 'isnot', 'Identical') + + simple_test({'is'}, 'Comparison', 2, {data={type='Identical', inv=false, ccs='UseOption'}, str='is'}) + simple_test({'isnot'}, 'Comparison', 5, {data={type='Identical', inv=true, ccs='UseOption'}, str='isnot'}) + simple_test({'is?'}, 'Comparison', 3, {data={type='Identical', inv=false, ccs='IgnoreCase'}, str='is?'}) + simple_test({'isnot?'}, 'Comparison', 6, {data={type='Identical', inv=true, ccs='IgnoreCase'}, str='isnot?'}) + simple_test({'is#'}, 'Comparison', 3, {data={type='Identical', inv=false, ccs='MatchCase'}, str='is#'}) + simple_test({'isnot#'}, 'Comparison', 6, {data={type='Identical', inv=true, ccs='MatchCase'}, str='isnot#'}) + simple_test({'is#foo'}, 'Comparison', 3, {data={type='Identical', inv=false, ccs='MatchCase'}, str='is#'}) + simple_test({'isnot#foo'}, 'Comparison', 6, {data={type='Identical', inv=true, ccs='MatchCase'}, str='isnot#'}) + end + + local function regular_number_tests() + simple_test({'2.0'}, 'Number', 1, {data={is_float=false}, str='2'}) + simple_test({'2.0x'}, 'Number', 1, {data={is_float=false}, str='2'}) + simple_test({'2.0e'}, 'Number', 1, {data={is_float=false}, str='2'}) + simple_test({'2.0e+'}, 'Number', 1, {data={is_float=false}, str='2'}) + simple_test({'2.0e-'}, 'Number', 1, {data={is_float=false}, str='2'}) + simple_test({'2.0e+x'}, 'Number', 1, {data={is_float=false}, str='2'}) + simple_test({'2.0e-x'}, 'Number', 1, {data={is_float=false}, str='2'}) + simple_test({'2.0e5'}, 'Number', 1, {data={is_float=false}, str='2'}) + simple_test({'2.0e+5'}, 'Number', 1, {data={is_float=false}, str='2'}) + simple_test({'2.0e-5'}, 'Number', 1, {data={is_float=false}, str='2'}) + end + + local function regular_eoc_tests() + singl_eltkn_test('EOC', '|') singl_eltkn_test('EOC', '\0') singl_eltkn_test('EOC', '\n') - singl_eltkn_test('Invalid', '~', {error='E15: Unidentified character: %.*s'}) + end + + itp('works (single tokens, zero flags)', function() + stable_tests() + + regular_eoc_tests() + regular_scope_tests() + regular_is_tests() + regular_number_tests() + end) + itp('peeks', function() + flags = tonumber(lib.kELFlagPeek) + should_advance = false + stable_tests() + + regular_eoc_tests() + regular_scope_tests() + regular_is_tests() + regular_number_tests() + end) + itp('forbids scope', function() + flags = tonumber(lib.kELFlagForbidScope) + stable_tests() + + regular_eoc_tests() + regular_is_tests() + regular_number_tests() + + simple_test({'g:'}, 'PlainIdentifier', 1, {data={scope=0, autoload=false}, str='g'}) + end) + itp('allows floats', function() + flags = tonumber(lib.kELFlagAllowFloat) + stable_tests() + + regular_eoc_tests() + regular_scope_tests() + regular_is_tests() + + simple_test({'2.0'}, 'Number', 3, {data={is_float=true}, str='2.0'}) + simple_test({'2.0x'}, 'Number', 3, {data={is_float=true}, str='2.0'}) + simple_test({'2.0e'}, 'Number', 3, {data={is_float=true}, str='2.0'}) + simple_test({'2.0e+'}, 'Number', 3, {data={is_float=true}, str='2.0'}) + simple_test({'2.0e-'}, 'Number', 3, {data={is_float=true}, str='2.0'}) + simple_test({'2.0e+x'}, 'Number', 3, {data={is_float=true}, str='2.0'}) + simple_test({'2.0e-x'}, 'Number', 3, {data={is_float=true}, str='2.0'}) + simple_test({'2.0e5'}, 'Number', 5, {data={is_float=true}, str='2.0e5'}) + simple_test({'2.0e+5'}, 'Number', 6, {data={is_float=true}, str='2.0e+5'}) + simple_test({'2.0e-5'}, 'Number', 6, {data={is_float=true}, str='2.0e-5'}) + end) + itp('treats `is` as an identifier', function() + flags = tonumber(lib.kELFlagIsNotCmp) + stable_tests() + + regular_eoc_tests() + regular_scope_tests() + regular_number_tests() + + simple_test({'is'}, 'PlainIdentifier', 2, {data={scope=0, autoload=false}, str='is'}) + simple_test({'isnot'}, 'PlainIdentifier', 5, {data={scope=0, autoload=false}, str='isnot'}) + simple_test({'is?'}, 'PlainIdentifier', 2, {data={scope=0, autoload=false}, str='is'}) + simple_test({'isnot?'}, 'PlainIdentifier', 5, {data={scope=0, autoload=false}, str='isnot'}) + simple_test({'is#'}, 'PlainIdentifier', 3, {data={scope=0, autoload=true}, str='is#'}) + simple_test({'isnot#'}, 'PlainIdentifier', 6, {data={scope=0, autoload=true}, str='isnot#'}) + simple_test({'is#foo'}, 'PlainIdentifier', 6, {data={scope=0, autoload=true}, str='is#foo'}) + simple_test({'isnot#foo'}, 'PlainIdentifier', 9, {data={scope=0, autoload=true}, str='isnot#foo'}) + end) + itp('forbids EOC', function() + flags = tonumber(lib.kELFlagForbidEOC) + stable_tests() - local pstate = new_pstate({{data=nil, size=0}}) - eq({len=0, error='start.col >= #pstr', start={col=0, line=0}, type='EOC'}, - next_eltkn(pstate)) + regular_scope_tests() + regular_is_tests() + regular_number_tests() - local pstate = new_pstate({''}) - eq({len=0, error='start.col >= #pstr', start={col=0, line=0}, type='EOC'}, - next_eltkn(pstate)) + singl_eltkn_test('Invalid', '|', {error='E15: Unexpected EOC character: %.*s'}) + singl_eltkn_test('Invalid', '\0', {error='E15: Unexpected EOC character: %.*s'}) + singl_eltkn_test('Invalid', '\n', {error='E15: Unexpected EOC character: %.*s'}) end) end) -- cgit From f2650660819ddeae97c26114a97aff9608bd226e Mon Sep 17 00:00:00 2001 From: ZyX Date: Fri, 29 Sep 2017 01:13:35 +0300 Subject: unittests: Add support for dumping “expected” state MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Purpose is similar to that of `screen:snapshot_util()`, but in different domain. --- test/unit/viml/expressions/parser_spec.lua | 53 ++++++++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/test/unit/viml/expressions/parser_spec.lua b/test/unit/viml/expressions/parser_spec.lua index eec4cb5bd9..51db2dd2d9 100644 --- a/test/unit/viml/expressions/parser_spec.lua +++ b/test/unit/viml/expressions/parser_spec.lua @@ -1,5 +1,6 @@ local helpers = require('test.unit.helpers')(after_each) local viml_helpers = require('test.unit.viml.helpers') +local global_helpers = require('test.helpers') local itp = helpers.gen_itp(it) local make_enum_conv_tab = helpers.make_enum_conv_tab @@ -15,8 +16,51 @@ local new_pstate = viml_helpers.new_pstate local intchar2lua = viml_helpers.intchar2lua local pstate_set_str = viml_helpers.pstate_set_str +local format_string = global_helpers.format_string +local format_luav = global_helpers.format_luav + local lib = cimport('./src/nvim/viml/parser/expressions.h') +local function format_check(expr, flags, ast, hls) + -- That forces specific order. + print( format_string('\ncheck_parsing(%r, %u, {', expr, flags)) + local digits = ' -- ' + local digits2 = ' -- ' + for i = 0, #expr - 1 do + if i % 10 == 0 then + digits2 = ('%s%10u'):format(digits2, i / 10) + end + digits = ('%s%u'):format(digits, i % 10) + end + print(digits) + if #expr > 10 then + print(digits2) + end + print(' ast = ' .. format_luav(ast.ast, ' ') .. ',') + if ast.err then + print(' err = {') + print(' arg = ' .. format_luav(ast.err.arg) .. ',') + print(' msg = ' .. format_luav(ast.err.msg) .. ',') + print(' },') + end + print('}, {') + local next_col = 0 + for _, v in ipairs(hls) do + local group, line, col, str = v:match('NVim([a-zA-Z]+):(%d+):(%d+):(.*)') + col = tonumber(col) + line = tonumber(line) + assert(line == 0) + local col_shift = col - next_col + assert(col_shift >= 0) + next_col = col + #str + print(format_string(' hl(%r, %r%s),', + group, + str, + (col_shift == 0 and '' or (', %u'):format(col_shift)))) + end + print('})') +end + local east_node_type_tab make_enum_conv_tab(lib, { 'kExprNodeMissing', @@ -137,10 +181,15 @@ child_call_once(function() end) describe('Expressions parser', function() - local function check_parsing(str, flags, exp_ast, exp_highlighting_fs) + local function check_parsing(str, flags, exp_ast, exp_highlighting_fs, + print_exp) local pstate = new_pstate({str}) local east = lib.viml_pexpr_parse(pstate, flags) local ast = east2lua(pstate, east) + local hls = phl2lua(pstate) + if print_exp then + format_check(str, flags, ast, hls) + end eq(exp_ast, ast) if exp_highlighting_fs then local exp_highlighting = {} @@ -148,7 +197,7 @@ describe('Expressions parser', function() for i, h in ipairs(exp_highlighting_fs) do exp_highlighting[i], next_col = h(next_col) end - eq(exp_highlighting, phl2lua(pstate)) + eq(exp_highlighting, hls) end end local function hl(group, str, shift) -- cgit From f33543377e39fc62ab063ca57c716984fb07aea1 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sat, 30 Sep 2017 21:34:34 +0300 Subject: viml/parser/expressions: Add a way to represent tokens from C code --- src/nvim/viml/parser/expressions.c | 179 +++++++++++++++++++++++++++++++++++++ 1 file changed, 179 insertions(+) diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index 3027c0046b..fc64fee140 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -440,6 +440,176 @@ viml_pexpr_next_token_adv_return: return ret; } +#ifdef UNIT_TESTING +static const char *const eltkn_type_tab[] = { + [kExprLexInvalid] = "Invalid", + [kExprLexMissing] = "Missing", + [kExprLexSpacing] = "Spacing", + [kExprLexEOC] = "EOC", + + [kExprLexQuestion] = "Question", + [kExprLexColon] = "Colon", + [kExprLexOr] = "Or", + [kExprLexAnd] = "And", + [kExprLexComparison] = "Comparison", + [kExprLexPlus] = "Plus", + [kExprLexMinus] = "Minus", + [kExprLexDot] = "Dot", + [kExprLexMultiplication] = "Multiplication", + + [kExprLexNot] = "Not", + + [kExprLexNumber] = "Number", + [kExprLexSingleQuotedString] = "SingleQuotedString", + [kExprLexDoubleQuotedString] = "DoubleQuotedString", + [kExprLexOption] = "Option", + [kExprLexRegister] = "Register", + [kExprLexEnv] = "Env", + [kExprLexPlainIdentifier] = "PlainIdentifier", + + [kExprLexBracket] = "Bracket", + [kExprLexFigureBrace] = "FigureBrace", + [kExprLexParenthesis] = "Parenthesis", + [kExprLexComma] = "Comma", + [kExprLexArrow] = "Arrow", +}; + +static const char *const eltkn_cmp_type_tab[] = { + [kExprLexCmpEqual] = "Equal", + [kExprLexCmpMatches] = "Matches", + [kExprLexCmpGreater] = "Greater", + [kExprLexCmpGreaterOrEqual] = "GreaterOrEqual", + [kExprLexCmpIdentical] = "Identical", +}; + +static const char *const ccs_tab[] = { + [kCCStrategyUseOption] = "UseOption", + [kCCStrategyMatchCase] = "MatchCase", + [kCCStrategyIgnoreCase] = "IgnoreCase", +}; + +static const char *const eltkn_mul_type_tab[] = { + [kExprLexMulMul] = "Mul", + [kExprLexMulDiv] = "Div", + [kExprLexMulMod] = "Mod", +}; + +static const char *const eltkn_opt_scope_tab[] = { + [kExprLexOptUnspecified] = "Unspecified", + [kExprLexOptGlobal] = "Global", + [kExprLexOptLocal] = "Local", +}; + +/// Represent `int` character as a string +/// +/// Converts +/// - ASCII digits into '{digit}' +/// - ASCII printable characters into a single-character strings +/// - everything else to numbers. +/// +/// @param[in] ch Character to convert. +/// +/// @return Converted string, stored in a static buffer (overriden after each +/// call). +static const char *intchar2str(const int ch) + FUNC_ATTR_WARN_UNUSED_RESULT +{ + static char buf[sizeof(int) * 3 + 1]; + if (' ' <= ch && ch < 0x7f) { + if (ascii_isdigit(ch)) { + buf[0] = '\''; + buf[1] = (char)ch; + buf[2] = '\''; + buf[3] = NUL; + } else { + buf[0] = (char)ch; + buf[1] = NUL; + } + } else { + snprintf(buf, sizeof(buf), "%i", ch); + } + return buf; +} + +/// Represent token as a string +/// +/// Intended for testing and debugging purposes. +/// +/// @param[in] pstate Parser state, needed to get token string from it. May be +/// NULL, in which case in place of obtaining part of the +/// string represented by token only token length is +/// returned. +/// @param[in] token Token to represent. +/// @param[out] ret_size Return string size, for cases like NULs inside +/// a string. May be NULL. +/// +/// @return Token represented in a string form, in a static buffer (overwritten +/// on each call). +const char *viml_pexpr_repr_token(const ParserState *const pstate, + const LexExprToken token, + size_t *const ret_size) + FUNC_ATTR_WARN_UNUSED_RESULT +{ + static char ret[1024]; + char *p = ret; + const char *const e = &ret[1024] - 1; +#define ADDSTR(...) \ + do { \ + p += snprintf(p, (size_t)(sizeof(ret) - (size_t)(p - ret)), __VA_ARGS__); \ + if (p >= e) { \ + goto viml_pexpr_repr_token_end; \ + } \ + } while (0) + ADDSTR("%zu:%zu:%s", token.start.line, token.start.col, + eltkn_type_tab[token.type]); + switch (token.type) { +#define TKNARGS(tkn_type, ...) \ + case tkn_type: { \ + ADDSTR(__VA_ARGS__); \ + break; \ + } + TKNARGS(kExprLexComparison, "(type=%s,ccs=%s,inv=%i)", + eltkn_cmp_type_tab[token.data.cmp.type], + ccs_tab[token.data.cmp.ccs], + (int)token.data.cmp.inv) + TKNARGS(kExprLexMultiplication, "(type=%s)", + eltkn_mul_type_tab[token.data.mul.type]) + TKNARGS(kExprLexRegister, "(name=%s)", intchar2str(token.data.reg.name)) + case kExprLexDoubleQuotedString: + TKNARGS(kExprLexSingleQuotedString, "(closed=%i)", + (int)token.data.str.closed) + TKNARGS(kExprLexOption, "(scope=%s,name=%.*s)", + eltkn_opt_scope_tab[token.data.opt.scope], + (int)token.data.opt.len, token.data.opt.name) + TKNARGS(kExprLexPlainIdentifier, "(scope=%s,autoload=%i)", + intchar2str(token.data.var.scope), (int)token.data.var.autoload) + TKNARGS(kExprLexNumber, "(is_float=%i)", (int)token.data.num.is_float) + TKNARGS(kExprLexInvalid, "(msg=%s)", token.data.err.msg) + default: { + // No additional arguments. + break; + } +#undef TKNARGS + } + if (pstate == NULL) { + ADDSTR("::%zu", token.len); + } else { + *p++ = ':'; + memmove( + p, &pstate->reader.lines.items[token.start.line].data[token.start.col], + token.len); + p += token.len; + *p = NUL; + } +#undef ADDSTR +viml_pexpr_repr_token_end: + if (ret_size != NULL) { + *ret_size = (size_t)(p - ret); + } + return ret; +} +#endif + #ifdef UNIT_TESTING #include REAL_FATTR_UNUSED @@ -469,12 +639,21 @@ static inline void viml_pexpr_debug_print_ast_stack( "-"); } } + +static inline void viml_pexpr_debug_print_token( + const ParserState *const pstate, const LexExprToken token) + FUNC_ATTR_ALWAYS_INLINE +{ + fprintf(stderr, "\ntkn: %s\n", viml_pexpr_repr_token(pstate, token, NULL)); +} #define PSTACK(msg) \ viml_pexpr_debug_print_ast_stack(&ast_stack, #msg) #define PSTACK_P(msg) \ viml_pexpr_debug_print_ast_stack(ast_stack, #msg) #define PNODE_P(eastnode_p, msg) \ viml_pexpr_debug_print_ast_node((const ExprASTNode *const *)ast_stack, #msg) +#define PTOKEN(tkn) \ + viml_pexpr_debug_print_token(pstate, tkn) #endif // start = s ternary_expr s EOC -- cgit From 3735537a508c5690c4622ebe450e6f3f15706670 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 1 Oct 2017 15:54:46 +0300 Subject: viml/parser/expressions: Fix call inside nested parenthesis MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It may have incorrectly tried to call everything because of essentially “value” nodes being treated as not such. --- src/nvim/viml/parser/expressions.c | 13 +++++--- test/unit/viml/expressions/parser_spec.lua | 49 +++++++++++++++++++++++++----- 2 files changed, 50 insertions(+), 12 deletions(-) diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index fc64fee140..1713d0c89f 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -612,6 +612,7 @@ viml_pexpr_repr_token_end: #ifdef UNIT_TESTING #include + REAL_FATTR_UNUSED static inline void viml_pexpr_debug_print_ast_node( const ExprASTNode *const *const eastnode_p, @@ -626,6 +627,7 @@ static inline void viml_pexpr_debug_print_ast_node( (*eastnode_p)->start.col, (*eastnode_p)->len); } } + REAL_FATTR_UNUSED static inline void viml_pexpr_debug_print_ast_stack( const ExprASTStack *const ast_stack, @@ -640,6 +642,7 @@ static inline void viml_pexpr_debug_print_ast_stack( } } +REAL_FATTR_UNUSED static inline void viml_pexpr_debug_print_token( const ParserState *const pstate, const LexExprToken token) FUNC_ATTR_ALWAYS_INLINE @@ -794,6 +797,7 @@ static inline ExprASTNode *viml_pexpr_new_node(const ExprASTNodeType type) typedef enum { kEOpLvlInvalid = 0, + kEOpLvlComplexIdentifier, kEOpLvlParens, kEOpLvlArrow, kEOpLvlComma, @@ -806,7 +810,6 @@ typedef enum { kEOpLvlMultiplication, ///< Multiplication, division and modulo. kEOpLvlUnary, ///< Unary operations: not, minus, plus. kEOpLvlSubscript, ///< Subscripts. - kEOpLvlComplexIdentifier, ///< Plain identifier, curly braces name. kEOpLvlValue, ///< Values: literals, variables, nested expressions, … } ExprOpLvl; @@ -843,10 +846,10 @@ static const ExprOpLvl node_type_to_op_lvl[] = { [kExprNodeSubscript] = kEOpLvlSubscript, - [kExprNodeComplexIdentifier] = kEOpLvlComplexIdentifier, - [kExprNodePlainIdentifier] = kEOpLvlComplexIdentifier, [kExprNodeCurlyBracesIdentifier] = kEOpLvlComplexIdentifier, + [kExprNodeComplexIdentifier] = kEOpLvlValue, + [kExprNodePlainIdentifier] = kEOpLvlValue, [kExprNodeRegister] = kEOpLvlValue, [kExprNodeListLiteral] = kEOpLvlValue, }; @@ -884,10 +887,10 @@ static const ExprOpAssociativity node_type_to_op_ass[] = { [kExprNodeSubscript] = kEOpAssLeft, - [kExprNodePlainIdentifier] = kEOpAssLeft, - [kExprNodeComplexIdentifier] = kEOpAssLeft, [kExprNodeCurlyBracesIdentifier] = kEOpAssLeft, + [kExprNodeComplexIdentifier] = kEOpAssLeft, + [kExprNodePlainIdentifier] = kEOpAssNo, [kExprNodeRegister] = kEOpAssNo, [kExprNodeListLiteral] = kEOpAssNo, }; diff --git a/test/unit/viml/expressions/parser_spec.lua b/test/unit/viml/expressions/parser_spec.lua index 51db2dd2d9..a2b76ccf8d 100644 --- a/test/unit/viml/expressions/parser_spec.lua +++ b/test/unit/viml/expressions/parser_spec.lua @@ -2269,7 +2269,43 @@ describe('Expressions parser', function() hl('CallingParenthesis', ')'), hl('Curly', '}'), }) - -- FIXME the below should not crash + check_parsing('a:{b()}c', 0, { + -- 01234567 + ast = { + { + 'ComplexIdentifier:0:2:', + children = { + 'PlainIdentifier(scope=a,ident=):0:0:a:', + { + 'ComplexIdentifier:0:7:', + children = { + { + 'CurlyBracesIdentifier(--i):0:2:{', + children = { + { + 'Call:0:4:(', + children = { + 'PlainIdentifier(scope=0,ident=b):0:3:b', + }, + }, + }, + }, + 'PlainIdentifier(scope=0,ident=c):0:7:c', + }, + }, + }, + }, + }, + }, { + hl('IdentifierScope', 'a'), + hl('IdentifierScopeDelimiter', ':'), + hl('Curly', '{'), + hl('Identifier', 'b'), + hl('CallingParenthesis', '('), + hl('CallingParenthesis', ')'), + hl('Curly', '}'), + hl('Identifier', 'c'), + }) check_parsing('a:{{b, c -> @d + @e + ({f -> g})(@h)}(@i)}j', 0, { -- 01234567890123456789012345678901234567890123456 -- 0 1 2 3 4 @@ -2277,9 +2313,9 @@ describe('Expressions parser', function() { 'ComplexIdentifier:0:2:', children = { - 'PlainIdentifier(scope=a,ident=):0:0:g:', + 'PlainIdentifier(scope=a,ident=):0:0:a:', { - 'ComplexIdentifier:0:6:', + 'ComplexIdentifier:0:42:', children = { { 'CurlyBracesIdentifier(--i):0:2:{', @@ -2314,7 +2350,7 @@ describe('Expressions parser', function() 'Call:0:32:(', children = { { - 'NestingParenthesis:0:21: (', + 'Nested:0:21: (', children = { { 'Lambda(\\di):0:23:{', @@ -2342,10 +2378,9 @@ describe('Expressions parser', function() 'Register(name=i):0:38:@i', }, }, - 'PlainIdentifier(scope=0,ident=j):0:42:j', }, }, - 'PlainIdentifier(scope=0,ident=_test):0:42:_test', + 'PlainIdentifier(scope=0,ident=j):0:42:j', }, }, }, @@ -2364,7 +2399,7 @@ describe('Expressions parser', function() hl('BinaryPlus', '+', 1), hl('Register', '@e', 1), hl('BinaryPlus', '+', 1), - hl('NestingParenthesis', '('), + hl('NestingParenthesis', '(', 1), hl('Lambda', '{'), hl('Identifier', 'f'), hl('Arrow', '->', 1), -- cgit From 9e721031d597bfa435da03597939191970f7a918 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 1 Oct 2017 16:50:46 +0300 Subject: viml/parser/expressions: Fix determining invalid commas/colons --- src/nvim/viml/parser/expressions.c | 163 ++++++++++++++++++++--------- src/nvim/viml/parser/expressions.h | 8 +- test/unit/viml/expressions/parser_spec.lua | 78 +++++++++++++- 3 files changed, 192 insertions(+), 57 deletions(-) diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index 1713d0c89f..c283241cb4 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -13,6 +13,7 @@ #include "nvim/types.h" #include "nvim/charset.h" #include "nvim/ascii.h" +#include "nvim/assert.h" #include "nvim/lib/kvec.h" #include "nvim/viml/parser/expressions.h" @@ -37,6 +38,32 @@ typedef enum { kENodeArgumentSeparator, } ExprASTWantedNode; +/// Operator priority level +typedef enum { + kEOpLvlInvalid = 0, + kEOpLvlComplexIdentifier, + kEOpLvlParens, + kEOpLvlArrow, + kEOpLvlComma, + kEOpLvlColon, + kEOpLvlTernary, + kEOpLvlOr, + kEOpLvlAnd, + kEOpLvlComparison, + kEOpLvlAddition, ///< Addition, subtraction and concatenation. + kEOpLvlMultiplication, ///< Multiplication, division and modulo. + kEOpLvlUnary, ///< Unary operations: not, minus, plus. + kEOpLvlSubscript, ///< Subscripts. + kEOpLvlValue, ///< Values: literals, variables, nested expressions, … +} ExprOpLvl; + +/// Operator associativity +typedef enum { + kEOpAssNo= 'n', ///< Not associative / not applicable. + kEOpAssLeft = 'l', ///< Left associativity. + kEOpAssRight = 'r', ///< Right associativity. +} ExprOpAssociativity; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "viml/parser/expressions.c.generated.h" #endif @@ -747,6 +774,7 @@ static inline void viml_pexpr_debug_print_token( // // NVimParenthesis -> Delimiter // +// NVimColon -> Delimiter // NVimComma -> Delimiter // NVimArrow -> Delimiter // @@ -895,6 +923,32 @@ static const ExprOpAssociativity node_type_to_op_ass[] = { [kExprNodeListLiteral] = kEOpAssNo, }; +/// Get AST node priority level +/// +/// Used primary to reduce line length, so keep the name short. +/// +/// @param[in] node Node to get priority for. +/// +/// @return Node priority level. +static inline ExprOpLvl node_lvl(const ExprASTNode node) + FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_CONST FUNC_ATTR_WARN_UNUSED_RESULT +{ + return node_type_to_op_lvl[node.type]; +} + +/// Get AST node associativity, to be used for operator nodes primary +/// +/// Used primary to reduce line length, so keep the name short. +/// +/// @param[in] node Node to get priority for. +/// +/// @return Node associativity. +static inline ExprOpAssociativity node_ass(const ExprASTNode node) + FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_CONST FUNC_ATTR_WARN_UNUSED_RESULT +{ + return node_type_to_op_ass[node.type]; +} + /// Handle binary operator /// /// This function is responsible for handling priority levels as well. @@ -910,20 +964,19 @@ static void viml_pexpr_handle_bop(ExprASTStack *const ast_stack, assert(kv_size(*ast_stack)); const ExprOpLvl bop_node_lvl = (bop_node->type == kExprNodeCall ? kEOpLvlSubscript - : node_type_to_op_lvl[bop_node->type]); + : node_lvl(*bop_node)); #ifndef NDEBUG const ExprOpAssociativity bop_node_ass = ( bop_node->type == kExprNodeCall ? kEOpAssLeft - : node_type_to_op_ass[bop_node->type]); + : node_ass(*bop_node)); #endif do { ExprASTNode **new_top_node_p = kv_last(*ast_stack); ExprASTNode *new_top_node = *new_top_node_p; assert(new_top_node != NULL); - const ExprOpLvl new_top_node_lvl = node_type_to_op_lvl[new_top_node->type]; - const ExprOpAssociativity new_top_node_ass = ( - node_type_to_op_ass[new_top_node->type]); + const ExprOpLvl new_top_node_lvl = node_lvl(*new_top_node); + const ExprOpAssociativity new_top_node_ass = node_ass(*new_top_node); assert(bop_node_lvl != new_top_node_lvl || bop_node_ass == new_top_node_ass); if (top_node_p != NULL @@ -1352,32 +1405,31 @@ viml_pexpr_parse_process_token: goto viml_pexpr_parse_invalid_comma; } for (size_t i = 1; i < kv_size(ast_stack); i++) { - const ExprASTNode *const *const eastnode_p = - (const ExprASTNode *const *)kv_Z(ast_stack, i); - if (!((*eastnode_p)->type == kExprNodeComma - || ((*eastnode_p)->type == kExprNodeColon - && i == 1)) - || i == kv_size(ast_stack) - 1) { - switch ((*eastnode_p)->type) { - case kExprNodeLambda: { - assert(want_node == kENodeArgumentSeparator); - break; - } - case kExprNodeDictLiteral: - case kExprNodeListLiteral: - case kExprNodeCall: { - break; - } - default: { + ExprASTNode *const *const eastnode_p = + (ExprASTNode *const *)kv_Z(ast_stack, i); + const ExprASTNodeType eastnode_type = (*eastnode_p)->type; + const ExprOpLvl eastnode_lvl = node_lvl(**eastnode_p); + if (eastnode_type == kExprNodeLambda) { + assert(want_node == kENodeArgumentSeparator); + break; + } else if (eastnode_type == kExprNodeDictLiteral + || eastnode_type == kExprNodeListLiteral + || eastnode_type == kExprNodeCall) { + break; + } else if (eastnode_type == kExprNodeComma + || eastnode_type == kExprNodeColon + || eastnode_lvl > kEOpLvlComma) { + // Do nothing + } else { viml_pexpr_parse_invalid_comma: - ERROR_FROM_TOKEN_AND_MSG( - cur_token, - _("E15: Comma outside of call, lambda or literal: %.*s")); - break; - } - } + ERROR_FROM_TOKEN_AND_MSG( + cur_token, + _("E15: Comma outside of call, lambda or literal: %.*s")); break; } + if (i == kv_size(ast_stack) - 1) { + goto viml_pexpr_parse_invalid_comma; + } } NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeComma); viml_pexpr_handle_bop(&ast_stack, cur_node, &want_node); @@ -1389,37 +1441,48 @@ viml_pexpr_parse_invalid_comma: if (kv_size(ast_stack) < 2) { goto viml_pexpr_parse_invalid_colon; } + bool is_ternary = false; + bool can_be_ternary = true; for (size_t i = 1; i < kv_size(ast_stack); i++) { ExprASTNode *const *const eastnode_p = (ExprASTNode *const *)kv_Z(ast_stack, i); - if ((*eastnode_p)->type != kExprNodeColon - || i == kv_size(ast_stack) - 1) { - switch ((*eastnode_p)->type) { - case kExprNodeUnknownFigure: { - SELECT_FIGURE_BRACE_TYPE((*eastnode_p), DictLiteral, Dict); - break; - } - case kExprNodeComma: - case kExprNodeDictLiteral: - case kExprNodeTernary: { - break; - } - default: { + const ExprASTNodeType eastnode_type = (*eastnode_p)->type; + const ExprOpLvl eastnode_lvl = node_lvl(**eastnode_p); + STATIC_ASSERT(kEOpLvlTernary > kEOpLvlComma, + "Unexpected operator priorities"); + if (can_be_ternary && eastnode_lvl == kEOpLvlTernary) { + assert(eastnode_type == kExprNodeTernary); + is_ternary = true; + break; + } else if (eastnode_type == kExprNodeUnknownFigure) { + SELECT_FIGURE_BRACE_TYPE(*eastnode_p, DictLiteral, Dict); + break; + } else if (eastnode_type == kExprNodeDictLiteral + || eastnode_type == kExprNodeComma) { + break; + } else if (eastnode_lvl > kEOpLvlTernary) { + // Do nothing + } else if (eastnode_lvl > kEOpLvlComma) { + can_be_ternary = false; + } else { viml_pexpr_parse_invalid_colon: - ERROR_FROM_TOKEN_AND_MSG( - cur_token, - _("E15: Colon outside of dictionary or ternary operator: " - "%.*s")); - break; - } - } + ERROR_FROM_TOKEN_AND_MSG( + cur_token, + _("E15: Colon outside of dictionary or ternary operator: " + "%.*s")); break; } + if (i == kv_size(ast_stack) - 1) { + goto viml_pexpr_parse_invalid_colon; + } } NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeColon); viml_pexpr_handle_bop(&ast_stack, cur_node, &want_node); - // FIXME: Handle ternary operator. - HL_CUR_TOKEN(Colon); + if (is_ternary) { + HL_CUR_TOKEN(TernaryColon); + } else { + HL_CUR_TOKEN(Colon); + } want_node = kENodeValue; break; } diff --git a/src/nvim/viml/parser/expressions.h b/src/nvim/viml/parser/expressions.h index 64abab9e41..01a51e4eda 100644 --- a/src/nvim/viml/parser/expressions.h +++ b/src/nvim/viml/parser/expressions.h @@ -144,10 +144,10 @@ typedef enum { typedef enum { kExprNodeMissing = 'X', kExprNodeOpMissing = '_', - kExprNodeTernary = '?', ///< Ternary operator, valid one has three children. - kExprNodeRegister = '@', ///< Register, no children. - kExprNodeSubscript = 's', ///< Subscript, should have two or three children. - kExprNodeListLiteral = 'l', ///< List literal, any number of children. + kExprNodeTernary = '?', ///< Ternary operator. + kExprNodeRegister = '@', ///< Register. + kExprNodeSubscript = 's', ///< Subscript. + kExprNodeListLiteral = 'l', ///< List literal. kExprNodeUnaryPlus = 'p', kExprNodeBinaryPlus = '+', kExprNodeNested = 'e', ///< Nested parenthesised expression. diff --git a/test/unit/viml/expressions/parser_spec.lua b/test/unit/viml/expressions/parser_spec.lua index a2b76ccf8d..1f734c3c2a 100644 --- a/test/unit/viml/expressions/parser_spec.lua +++ b/test/unit/viml/expressions/parser_spec.lua @@ -181,14 +181,14 @@ child_call_once(function() end) describe('Expressions parser', function() - local function check_parsing(str, flags, exp_ast, exp_highlighting_fs, - print_exp) + local function check_parsing(str, flags, exp_ast, exp_highlighting_fs) local pstate = new_pstate({str}) local east = lib.viml_pexpr_parse(pstate, flags) local ast = east2lua(pstate, east) local hls = phl2lua(pstate) - if print_exp then + if exp_ast == nil then format_check(str, flags, ast, hls) + return end eq(exp_ast, ast) if exp_highlighting_fs then @@ -2416,6 +2416,78 @@ describe('Expressions parser', function() hl('Curly', '}'), hl('Identifier', 'j'), }) + check_parsing('{@a + @b : @c + @d, @e + @f : @g + @i}', 0, { + -- 01234567890123456789012345678901234567 + -- 0 1 2 3 + ast = { + { + 'DictLiteral(-di):0:0:{', + children = { + { + 'Comma:0:18:,', + children = { + { + 'Colon:0:8: :', + children = { + { + 'BinaryPlus:0:3: +', + children = { + 'Register(name=a):0:1:@a', + 'Register(name=b):0:5: @b', + }, + }, + { + 'BinaryPlus:0:13: +', + children = { + 'Register(name=c):0:10: @c', + 'Register(name=d):0:15: @d', + }, + }, + }, + }, + { + 'Colon:0:27: :', + children = { + { + 'BinaryPlus:0:22: +', + children = { + 'Register(name=e):0:19: @e', + 'Register(name=f):0:24: @f', + }, + }, + { + 'BinaryPlus:0:32: +', + children = { + 'Register(name=g):0:29: @g', + 'Register(name=i):0:34: @i', + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, { + hl('Dict', '{'), + hl('Register', '@a'), + hl('BinaryPlus', '+', 1), + hl('Register', '@b', 1), + hl('Colon', ':', 1), + hl('Register', '@c', 1), + hl('BinaryPlus', '+', 1), + hl('Register', '@d', 1), + hl('Comma', ','), + hl('Register', '@e', 1), + hl('BinaryPlus', '+', 1), + hl('Register', '@f', 1), + hl('Colon', ':', 1), + hl('Register', '@g', 1), + hl('BinaryPlus', '+', 1), + hl('Register', '@i', 1), + hl('Dict', '}'), + }) end) -- FIXME: Test sequence of arrows inside and outside lambdas. -- FIXME: Test autoload character and scope in lambda arguments. -- cgit From 6144e26eb920a90b0db22bd7afcac0b9e0734ed6 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 1 Oct 2017 22:35:41 +0300 Subject: viml/parser/expressions: Add support for ternary operator --- src/nvim/viml/parser/expressions.c | 87 ++-- src/nvim/viml/parser/expressions.h | 6 +- test/unit/viml/expressions/parser_spec.lua | 651 ++++++++++++++++++++++++++++- 3 files changed, 705 insertions(+), 39 deletions(-) diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index c283241cb4..41c77c5c88 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -46,6 +46,7 @@ typedef enum { kEOpLvlArrow, kEOpLvlComma, kEOpLvlColon, + kEOpLvlTernaryValue, kEOpLvlTernary, kEOpLvlOr, kEOpLvlAnd, @@ -770,7 +771,8 @@ static inline void viml_pexpr_debug_print_token( // NVimUnaryOperator -> NVimOperator // NVimBinaryOperator -> NVimOperator // NVimComparisonOperator -> NVimOperator -// NVimTernaryOperator -> NVimOperator +// NVimTernary -> NVimOperator +// NVimTernaryColon -> NVimTernary // // NVimParenthesis -> Delimiter // @@ -790,7 +792,8 @@ static inline void viml_pexpr_debug_print_token( // // NVimInvalidComma -> NVimInvalidDelimiter // NVimInvalidSpacing -> NVimInvalid -// NVimInvalidTernaryOperator -> NVimInvalidOperator +// NVimInvalidTernary -> NVimInvalidOperator +// NVimInvalidTernaryColon -> NVimInvalidTernary // NVimInvalidRegister -> NVimInvalidValue // NVimInvalidClosingBracket -> NVimInvalidDelimiter // NVimInvalidSpacing -> NVimInvalid @@ -823,30 +826,6 @@ static inline ExprASTNode *viml_pexpr_new_node(const ExprASTNodeType type) return ret; } -typedef enum { - kEOpLvlInvalid = 0, - kEOpLvlComplexIdentifier, - kEOpLvlParens, - kEOpLvlArrow, - kEOpLvlComma, - kEOpLvlColon, - kEOpLvlTernary, - kEOpLvlOr, - kEOpLvlAnd, - kEOpLvlComparison, - kEOpLvlAddition, ///< Addition, subtraction and concatenation. - kEOpLvlMultiplication, ///< Multiplication, division and modulo. - kEOpLvlUnary, ///< Unary operations: not, minus, plus. - kEOpLvlSubscript, ///< Subscripts. - kEOpLvlValue, ///< Values: literals, variables, nested expressions, … -} ExprOpLvl; - -typedef enum { - kEOpAssNo= 'n', ///< Not associative / not applicable. - kEOpAssLeft = 'l', ///< Left associativity. - kEOpAssRight = 'r', ///< Right associativity. -} ExprOpAssociativity; - static const ExprOpLvl node_type_to_op_lvl[] = { [kExprNodeMissing] = kEOpLvlInvalid, [kExprNodeOpMissing] = kEOpLvlMultiplication, @@ -868,6 +847,8 @@ static const ExprOpLvl node_type_to_op_lvl[] = { [kExprNodeTernary] = kEOpLvlTernary, + [kExprNodeTernaryValue] = kEOpLvlTernaryValue, + [kExprNodeBinaryPlus] = kEOpLvlAddition, [kExprNodeUnaryPlus] = kEOpLvlUnary, @@ -907,7 +888,9 @@ static const ExprOpAssociativity node_type_to_op_ass[] = { // about associativity, only about order of execution. [kExprNodeComma] = kEOpAssRight, - [kExprNodeTernary] = kEOpAssNo, + [kExprNodeTernary] = kEOpAssRight, + + [kExprNodeTernaryValue] = kEOpAssRight, [kExprNodeBinaryPlus] = kEOpAssLeft, @@ -1450,9 +1433,20 @@ viml_pexpr_parse_invalid_comma: const ExprOpLvl eastnode_lvl = node_lvl(**eastnode_p); STATIC_ASSERT(kEOpLvlTernary > kEOpLvlComma, "Unexpected operator priorities"); - if (can_be_ternary && eastnode_lvl == kEOpLvlTernary) { - assert(eastnode_type == kExprNodeTernary); + if (can_be_ternary && eastnode_type == kExprNodeTernaryValue + && !(*eastnode_p)->data.ter.got_colon) { + kv_drop(ast_stack, i); + (*eastnode_p)->start = cur_token.start; + (*eastnode_p)->len = cur_token.len; + if (prev_token.type == kExprLexSpacing) { + (*eastnode_p)->start = prev_token.start; + (*eastnode_p)->len += prev_token.len; + } is_ternary = true; + (*eastnode_p)->data.ter.got_colon = true; + assert((*eastnode_p)->children != NULL); + assert((*eastnode_p)->children->next == NULL); + kvi_push(ast_stack, &(*eastnode_p)->children->next); break; } else if (eastnode_type == kExprNodeUnknownFigure) { SELECT_FIGURE_BRACE_TYPE(*eastnode_p, DictLiteral, Dict); @@ -1460,7 +1454,7 @@ viml_pexpr_parse_invalid_comma: } else if (eastnode_type == kExprNodeDictLiteral || eastnode_type == kExprNodeComma) { break; - } else if (eastnode_lvl > kEOpLvlTernary) { + } else if (eastnode_lvl >= kEOpLvlTernaryValue) { // Do nothing } else if (eastnode_lvl > kEOpLvlComma) { can_be_ternary = false; @@ -1476,11 +1470,11 @@ viml_pexpr_parse_invalid_colon: goto viml_pexpr_parse_invalid_colon; } } - NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeColon); - viml_pexpr_handle_bop(&ast_stack, cur_node, &want_node); if (is_ternary) { HL_CUR_TOKEN(TernaryColon); } else { + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeColon); + viml_pexpr_handle_bop(&ast_stack, cur_node, &want_node); HL_CUR_TOKEN(Colon); } want_node = kENodeValue; @@ -1683,8 +1677,6 @@ viml_pexpr_parse_figure_brace_closing_error: cur_token.len - scope_shift, HL(Identifier)); } - // FIXME: Actually, g{foo}g:foo is valid: "1?g{foo}g:foo" is like - // "g{foo}g" and not an error. } else { if (cur_token.data.var.scope == 0) { ADD_IDENT( @@ -1792,6 +1784,21 @@ viml_pexpr_parse_no_paren_closing_error: {} } break; } + case kExprLexQuestion: { + ADD_VALUE_IF_MISSING(_("E15: Expected value, got question mark: %.*s")); + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeTernary); + viml_pexpr_handle_bop(&ast_stack, cur_node, &want_node); + HL_CUR_TOKEN(Ternary); + ExprASTNode *ter_val_node; + NEW_NODE_WITH_CUR_POS(ter_val_node, kExprNodeTernaryValue); + ter_val_node->data.ter.got_colon = false; + assert(cur_node->children != NULL); + assert(cur_node->children->next == NULL); + assert(kv_last(ast_stack) == &cur_node->children->next); + *kv_last(ast_stack) = ter_val_node; + kvi_push(ast_stack, &ter_val_node->children); + break; + } } viml_pexpr_parse_cycle_end: prev_token = cur_token; @@ -1815,6 +1822,7 @@ viml_pexpr_parse_end: const ExprASTNode *const cur_node = (*kv_pop(ast_stack)); // This should only happen when want_node == kENodeValue. assert(cur_node != NULL); + // TODO(ZyX-I): Rehighlight as invalid? switch (cur_node->type) { case kExprNodeOpMissing: case kExprNodeMissing: { @@ -1822,7 +1830,6 @@ viml_pexpr_parse_end: break; } case kExprNodeCall: { - // TODO(ZyX-I): Rehighlight as invalid? east_set_error( &ast, pstate, _("E116: Missing closing parenthesis for function call: %.*s"), @@ -1830,7 +1837,6 @@ viml_pexpr_parse_end: break; } case kExprNodeNested: { - // TODO(ZyX-I): Rehighlight as invalid? east_set_error( &ast, pstate, _("E110: Missing closing parenthesis for nested expression" @@ -1844,6 +1850,15 @@ viml_pexpr_parse_end: // It is OK to see these in the stack. break; } + case kExprNodeTernaryValue: { + if (!cur_node->data.ter.got_colon) { + // Actually Vim throws E109 in more cases. + east_set_error( + &ast, pstate, _("E109: Missing ':' after '?': %.*s"), + cur_node->start); + } + break; + } // TODO(ZyX-I): handle other values } } diff --git a/src/nvim/viml/parser/expressions.h b/src/nvim/viml/parser/expressions.h index 01a51e4eda..cf0907850a 100644 --- a/src/nvim/viml/parser/expressions.h +++ b/src/nvim/viml/parser/expressions.h @@ -145,6 +145,7 @@ typedef enum { kExprNodeMissing = 'X', kExprNodeOpMissing = '_', kExprNodeTernary = '?', ///< Ternary operator. + kExprNodeTernaryValue = 'C', ///< Ternary operator, colon. kExprNodeRegister = '@', ///< Register. kExprNodeSubscript = 's', ///< Subscript. kExprNodeListLiteral = 'l', ///< List literal. @@ -209,7 +210,10 @@ struct expr_ast_node { /// Points to inside parser reader state. const char *ident; size_t ident_len; ///< Actual identifier length. - } var; + } var; ///< For kExprNodePlainIdentifier. + struct { + bool got_colon; ///< True if colon was seen. + } ter; ///< For kExprNodeTernaryValue. } data; }; diff --git a/test/unit/viml/expressions/parser_spec.lua b/test/unit/viml/expressions/parser_spec.lua index 1f734c3c2a..ea37d64662 100644 --- a/test/unit/viml/expressions/parser_spec.lua +++ b/test/unit/viml/expressions/parser_spec.lua @@ -66,6 +66,7 @@ make_enum_conv_tab(lib, { 'kExprNodeMissing', 'kExprNodeOpMissing', 'kExprNodeTernary', + 'kExprNodeTernaryValue', 'kExprNodeRegister', 'kExprNodeSubscript', 'kExprNodeListLiteral', @@ -2489,6 +2490,652 @@ describe('Expressions parser', function() hl('Dict', '}'), }) end) - -- FIXME: Test sequence of arrows inside and outside lambdas. - -- FIXME: Test autoload character and scope in lambda arguments. + itp('works with ternary operator', function() + check_parsing('a ? b : c', 0, { + -- 012345678 + ast = { + { + 'Ternary:0:1: ?', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'TernaryValue:0:5: :', + children = { + 'PlainIdentifier(scope=0,ident=b):0:3: b', + 'PlainIdentifier(scope=0,ident=c):0:7: c', + }, + }, + }, + }, + }, + }, { + hl('Identifier', 'a'), + hl('Ternary', '?', 1), + hl('Identifier', 'b', 1), + hl('TernaryColon', ':', 1), + hl('Identifier', 'c', 1), + }) + check_parsing('@a?@b?@c:@d:@e', 0, { + -- 01234567890123 + -- 0 1 + ast = { + { + 'Ternary:0:2:?', + children = { + 'Register(name=a):0:0:@a', + { + 'TernaryValue:0:11::', + children = { + { + 'Ternary:0:5:?', + children = { + 'Register(name=b):0:3:@b', + { + 'TernaryValue:0:8::', + children = { + 'Register(name=c):0:6:@c', + 'Register(name=d):0:9:@d', + }, + }, + }, + }, + 'Register(name=e):0:12:@e', + }, + }, + }, + }, + }, + }, { + hl('Register', '@a'), + hl('Ternary', '?'), + hl('Register', '@b'), + hl('Ternary', '?'), + hl('Register', '@c'), + hl('TernaryColon', ':'), + hl('Register', '@d'), + hl('TernaryColon', ':'), + hl('Register', '@e'), + }) + check_parsing('@a?@b:@c?@d:@e', 0, { + -- 01234567890123 + -- 0 1 + ast = { + { + 'Ternary:0:2:?', + children = { + 'Register(name=a):0:0:@a', + { + 'TernaryValue:0:5::', + children = { + 'Register(name=b):0:3:@b', + { + 'Ternary:0:8:?', + children = { + 'Register(name=c):0:6:@c', + { + 'TernaryValue:0:11::', + children = { + 'Register(name=d):0:9:@d', + 'Register(name=e):0:12:@e', + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, { + hl('Register', '@a'), + hl('Ternary', '?'), + hl('Register', '@b'), + hl('TernaryColon', ':'), + hl('Register', '@c'), + hl('Ternary', '?'), + hl('Register', '@d'), + hl('TernaryColon', ':'), + hl('Register', '@e'), + }) + check_parsing('@a?@b?@c?@d:@e?@f:@g:@h?@i:@j:@k', 0, { + -- 01234567890123456789012345678901 + -- 0 1 2 3 + ast = { + { + 'Ternary:0:2:?', + children = { + 'Register(name=a):0:0:@a', + { + 'TernaryValue:0:29::', + children = { + { + 'Ternary:0:5:?', + children = { + 'Register(name=b):0:3:@b', + { + 'TernaryValue:0:20::', + children = { + { + 'Ternary:0:8:?', + children = { + 'Register(name=c):0:6:@c', + { + 'TernaryValue:0:11::', + children = { + 'Register(name=d):0:9:@d', + { + 'Ternary:0:14:?', + children = { + 'Register(name=e):0:12:@e', + { + 'TernaryValue:0:17::', + children = { + 'Register(name=f):0:15:@f', + 'Register(name=g):0:18:@g', + }, + }, + }, + }, + }, + }, + }, + }, + { + 'Ternary:0:23:?', + children = { + 'Register(name=h):0:21:@h', + { + 'TernaryValue:0:26::', + children = { + 'Register(name=i):0:24:@i', + 'Register(name=j):0:27:@j', + }, + }, + }, + }, + }, + }, + }, + }, + 'Register(name=k):0:30:@k', + }, + }, + }, + }, + }, + }, { + hl('Register', '@a'), + hl('Ternary', '?'), + hl('Register', '@b'), + hl('Ternary', '?'), + hl('Register', '@c'), + hl('Ternary', '?'), + hl('Register', '@d'), + hl('TernaryColon', ':'), + hl('Register', '@e'), + hl('Ternary', '?'), + hl('Register', '@f'), + hl('TernaryColon', ':'), + hl('Register', '@g'), + hl('TernaryColon', ':'), + hl('Register', '@h'), + hl('Ternary', '?'), + hl('Register', '@i'), + hl('TernaryColon', ':'), + hl('Register', '@j'), + hl('TernaryColon', ':'), + hl('Register', '@k'), + }) + check_parsing('?', 0, { + -- 0 + ast = { + { + 'Ternary:0:0:?', + children = { + 'Missing:0:0:', + 'TernaryValue:0:0:?', + }, + }, + }, + err = { + arg = '?', + msg = 'E15: Expected value, got question mark: %.*s', + }, + }, { + hl('InvalidTernary', '?'), + }) + + check_parsing('?:', 0, { + -- 01 + ast = { + { + 'Ternary:0:0:?', + children = { + 'Missing:0:0:', + { + 'TernaryValue:0:1::', + children = { + 'Missing:0:1:', + }, + }, + }, + }, + }, + err = { + arg = '?:', + msg = 'E15: Expected value, got question mark: %.*s', + }, + }, { + hl('InvalidTernary', '?'), + hl('InvalidTernaryColon', ':'), + }) + + check_parsing('?::', 0, { + -- 012 + ast = { + { + 'Colon:0:2::', + children = { + { + 'Ternary:0:0:?', + children = { + 'Missing:0:0:', + { + 'TernaryValue:0:1::', + children = { + 'Missing:0:1:', + 'Missing:0:2:', + }, + }, + }, + }, + }, + }, + }, + err = { + arg = '?::', + msg = 'E15: Expected value, got question mark: %.*s', + }, + }, { + hl('InvalidTernary', '?'), + hl('InvalidTernaryColon', ':'), + hl('InvalidColon', ':'), + }) + + check_parsing('a?b', 0, { + -- 012 + ast = { + { + 'Ternary:0:1:?', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'TernaryValue:0:1:?', + children = { + 'PlainIdentifier(scope=0,ident=b):0:2:b', + }, + }, + }, + }, + }, + err = { + arg = '?b', + msg = 'E109: Missing \':\' after \'?\': %.*s', + }, + }, { + hl('Identifier', 'a'), + hl('Ternary', '?'), + hl('Identifier', 'b'), + }) + check_parsing('a?b:', 0, { + -- 0123 + ast = { + { + 'Ternary:0:1:?', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'TernaryValue:0:1:?', + children = { + 'PlainIdentifier(scope=b,ident=):0:2:b:', + }, + }, + }, + }, + }, + err = { + arg = '?b:', + msg = 'E109: Missing \':\' after \'?\': %.*s', + }, + }, { + hl('Identifier', 'a'), + hl('Ternary', '?'), + hl('IdentifierScope', 'b'), + hl('IdentifierScopeDelimiter', ':'), + }) + + check_parsing('a?b::c', 0, { + -- 012345 + ast = { + { + 'Ternary:0:1:?', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'TernaryValue:0:4::', + children = { + 'PlainIdentifier(scope=b,ident=):0:2:b:', + 'PlainIdentifier(scope=0,ident=c):0:5:c', + }, + }, + }, + }, + }, + }, { + hl('Identifier', 'a'), + hl('Ternary', '?'), + hl('IdentifierScope', 'b'), + hl('IdentifierScopeDelimiter', ':'), + hl('TernaryColon', ':'), + hl('Identifier', 'c'), + }) + + check_parsing('a?b :', 0, { + -- 01234 + ast = { + { + 'Ternary:0:1:?', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'TernaryValue:0:3: :', + children = { + 'PlainIdentifier(scope=0,ident=b):0:2:b', + }, + }, + }, + }, + }, + err = { + arg = '', + msg = 'E15: Expected value, got EOC: %.*s', + }, + }, { + hl('Identifier', 'a'), + hl('Ternary', '?'), + hl('Identifier', 'b'), + hl('TernaryColon', ':', 1), + }) + + check_parsing('(@a?@b:@c)?@d:@e', 0, { + -- 0123456789012345 + -- 0 1 + ast = { + { + 'Ternary:0:10:?', + children = { + { + 'Nested:0:0:(', + children = { + { + 'Ternary:0:3:?', + children = { + 'Register(name=a):0:1:@a', + { + 'TernaryValue:0:6::', + children = { + 'Register(name=b):0:4:@b', + 'Register(name=c):0:7:@c', + }, + }, + }, + }, + }, + }, + { + 'TernaryValue:0:13::', + children = { + 'Register(name=d):0:11:@d', + 'Register(name=e):0:14:@e', + }, + }, + }, + }, + }, + }, { + hl('NestingParenthesis', '('), + hl('Register', '@a'), + hl('Ternary', '?'), + hl('Register', '@b'), + hl('TernaryColon', ':'), + hl('Register', '@c'), + hl('NestingParenthesis', ')'), + hl('Ternary', '?'), + hl('Register', '@d'), + hl('TernaryColon', ':'), + hl('Register', '@e'), + }) + + check_parsing('(@a?@b:@c)?(@d?@e:@f):(@g?@h:@i)', 0, { + -- 01234567890123456789012345678901 + -- 0 1 2 3 + ast = { + { + 'Ternary:0:10:?', + children = { + { + 'Nested:0:0:(', + children = { + { + 'Ternary:0:3:?', + children = { + 'Register(name=a):0:1:@a', + { + 'TernaryValue:0:6::', + children = { + 'Register(name=b):0:4:@b', + 'Register(name=c):0:7:@c', + }, + }, + }, + }, + }, + }, + { + 'TernaryValue:0:21::', + children = { + { + 'Nested:0:11:(', + children = { + { + 'Ternary:0:14:?', + children = { + 'Register(name=d):0:12:@d', + { + 'TernaryValue:0:17::', + children = { + 'Register(name=e):0:15:@e', + 'Register(name=f):0:18:@f', + }, + }, + }, + }, + }, + }, + { + 'Nested:0:22:(', + children = { + { + 'Ternary:0:25:?', + children = { + 'Register(name=g):0:23:@g', + { + 'TernaryValue:0:28::', + children = { + 'Register(name=h):0:26:@h', + 'Register(name=i):0:29:@i', + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, { + hl('NestingParenthesis', '('), + hl('Register', '@a'), + hl('Ternary', '?'), + hl('Register', '@b'), + hl('TernaryColon', ':'), + hl('Register', '@c'), + hl('NestingParenthesis', ')'), + hl('Ternary', '?'), + hl('NestingParenthesis', '('), + hl('Register', '@d'), + hl('Ternary', '?'), + hl('Register', '@e'), + hl('TernaryColon', ':'), + hl('Register', '@f'), + hl('NestingParenthesis', ')'), + hl('TernaryColon', ':'), + hl('NestingParenthesis', '('), + hl('Register', '@g'), + hl('Ternary', '?'), + hl('Register', '@h'), + hl('TernaryColon', ':'), + hl('Register', '@i'), + hl('NestingParenthesis', ')'), + }) + + check_parsing('(@a?@b:@c)?@d?@e:@f:@g?@h:@i', 0, { + -- 0123456789012345678901234567 + -- 0 1 2 + ast = { + { + 'Ternary:0:10:?', + children = { + { + 'Nested:0:0:(', + children = { + { + 'Ternary:0:3:?', + children = { + 'Register(name=a):0:1:@a', + { + 'TernaryValue:0:6::', + children = { + 'Register(name=b):0:4:@b', + 'Register(name=c):0:7:@c', + }, + }, + }, + }, + }, + }, + { + 'TernaryValue:0:19::', + children = { + { + 'Ternary:0:13:?', + children = { + 'Register(name=d):0:11:@d', + { + 'TernaryValue:0:16::', + children = { + 'Register(name=e):0:14:@e', + 'Register(name=f):0:17:@f', + }, + }, + }, + }, + { + 'Ternary:0:22:?', + children = { + 'Register(name=g):0:20:@g', + { + 'TernaryValue:0:25::', + children = { + 'Register(name=h):0:23:@h', + 'Register(name=i):0:26:@i', + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, { + hl('NestingParenthesis', '('), + hl('Register', '@a'), + hl('Ternary', '?'), + hl('Register', '@b'), + hl('TernaryColon', ':'), + hl('Register', '@c'), + hl('NestingParenthesis', ')'), + hl('Ternary', '?'), + hl('Register', '@d'), + hl('Ternary', '?'), + hl('Register', '@e'), + hl('TernaryColon', ':'), + hl('Register', '@f'), + hl('TernaryColon', ':'), + hl('Register', '@g'), + hl('Ternary', '?'), + hl('Register', '@h'), + hl('TernaryColon', ':'), + hl('Register', '@i'), + }) + check_parsing('a?b{cdef}g:h', 0, { + -- 012345678901 + -- 0 1 + ast = { + { + 'Ternary:0:1:?', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'TernaryValue:0:10::', + children = { + { + 'ComplexIdentifier:0:3:', + children = { + 'PlainIdentifier(scope=0,ident=b):0:2:b', + { + 'ComplexIdentifier:0:9:', + children = { + { + 'CurlyBracesIdentifier(--i):0:3:{', + children = { + 'PlainIdentifier(scope=0,ident=cdef):0:4:cdef', + }, + }, + 'PlainIdentifier(scope=0,ident=g):0:9:g', + }, + }, + }, + }, + 'PlainIdentifier(scope=0,ident=h):0:11:h', + }, + }, + }, + }, + }, + }, { + hl('Identifier', 'a'), + hl('Ternary', '?'), + hl('Identifier', 'b'), + hl('Curly', '{'), + hl('Identifier', 'cdef'), + hl('Curly', '}'), + hl('Identifier', 'g'), + hl('TernaryColon', ':'), + hl('Identifier', 'h'), + }) + end) end) -- cgit From 6791c574209c83570746c139d93f8e6a6b9cd135 Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 2 Oct 2017 01:22:35 +0300 Subject: viml/parser/expressions: Make sure that arrows outside lambda throw --- src/nvim/viml/parser/expressions.c | 3 +- test/unit/viml/expressions/parser_spec.lua | 190 +++++++++++++++++++++++++++++ 2 files changed, 192 insertions(+), 1 deletion(-) diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index 41c77c5c88..982465055e 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -1642,8 +1642,9 @@ viml_pexpr_parse_figure_brace_closing_error: lambda_node = NULL; } else { // Only first branch is valid. - is_invalid = true; ADD_VALUE_IF_MISSING(_("E15: Unexpected arrow: %.*s")); + ERROR_FROM_TOKEN_AND_MSG( + cur_token, _("E15: Arrow outside of lambda: %.*s")); NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeArrow); viml_pexpr_handle_bop(&ast_stack, cur_node, &want_node); } diff --git a/test/unit/viml/expressions/parser_spec.lua b/test/unit/viml/expressions/parser_spec.lua index ea37d64662..2c80b437dc 100644 --- a/test/unit/viml/expressions/parser_spec.lua +++ b/test/unit/viml/expressions/parser_spec.lua @@ -2489,6 +2489,196 @@ describe('Expressions parser', function() hl('Register', '@i', 1), hl('Dict', '}'), }) + check_parsing('-> -> ->', 0, { + -- 01234567 + ast = { + { + 'Arrow:0:0:->', + children = { + 'Missing:0:0:', + { + 'Arrow:0:2: ->', + children = { + 'Missing:0:2:', + { + 'Arrow:0:5: ->', + children = { + 'Missing:0:5:', + }, + }, + }, + }, + }, + }, + }, + err = { + arg = '-> -> ->', + msg = 'E15: Unexpected arrow: %.*s', + }, + }, { + hl('InvalidArrow', '->'), + hl('InvalidArrow', '->', 1), + hl('InvalidArrow', '->', 1), + }) + check_parsing('a -> b -> c -> d', 0, { + -- 0123456789012345 + -- 0 1 + ast = { + { + 'Arrow:0:1: ->', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'Arrow:0:6: ->', + children = { + 'PlainIdentifier(scope=0,ident=b):0:4: b', + { + 'Arrow:0:11: ->', + children = { + 'PlainIdentifier(scope=0,ident=c):0:9: c', + 'PlainIdentifier(scope=0,ident=d):0:14: d', + }, + }, + }, + }, + }, + }, + }, + err = { + arg = '-> b -> c -> d', + msg = 'E15: Arrow outside of lambda: %.*s', + }, + }, { + hl('Identifier', 'a'), + hl('InvalidArrow', '->', 1), + hl('Identifier', 'b', 1), + hl('InvalidArrow', '->', 1), + hl('Identifier', 'c', 1), + hl('InvalidArrow', '->', 1), + hl('Identifier', 'd', 1), + }) + check_parsing('{a -> b -> c}', 0, { + -- 0123456789012 + -- 0 1 + ast = { + { + 'Lambda(\\di):0:0:{', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + { + 'Arrow:0:2: ->', + children = { + { + 'Arrow:0:7: ->', + children = { + 'PlainIdentifier(scope=0,ident=b):0:5: b', + 'PlainIdentifier(scope=0,ident=c):0:10: c', + }, + }, + }, + }, + }, + }, + }, + err = { + arg = '-> c}', + msg = 'E15: Arrow outside of lambda: %.*s', + }, + }, { + hl('Lambda', '{'), + hl('Identifier', 'a'), + hl('Arrow', '->', 1), + hl('Identifier', 'b', 1), + hl('InvalidArrow', '->', 1), + hl('Identifier', 'c', 1), + hl('Lambda', '}'), + }) + check_parsing('{a: -> b}', 0, { + -- 012345678 + ast = { + { + 'CurlyBracesIdentifier(-di):0:0:{', + children = { + { + 'Arrow:0:3: ->', + children = { + 'PlainIdentifier(scope=a,ident=):0:1:a:', + 'PlainIdentifier(scope=0,ident=b):0:6: b', + }, + }, + }, + }, + }, + err = { + arg = '-> b}', + msg = 'E15: Arrow outside of lambda: %.*s', + }, + }, { + hl('Curly', '{'), + hl('IdentifierScope', 'a'), + hl('IdentifierScopeDelimiter', ':'), + hl('InvalidArrow', '->', 1), + hl('Identifier', 'b', 1), + hl('Curly', '}'), + }) + + check_parsing('{a:b -> b}', 0, { + -- 0123456789 + ast = { + { + 'CurlyBracesIdentifier(-di):0:0:{', + children = { + { + 'Arrow:0:4: ->', + children = { + 'PlainIdentifier(scope=a,ident=b):0:1:a:b', + 'PlainIdentifier(scope=0,ident=b):0:7: b', + }, + }, + }, + }, + }, + err = { + arg = '-> b}', + msg = 'E15: Arrow outside of lambda: %.*s', + }, + }, { + hl('Curly', '{'), + hl('IdentifierScope', 'a'), + hl('IdentifierScopeDelimiter', ':'), + hl('Identifier', 'b'), + hl('InvalidArrow', '->', 1), + hl('Identifier', 'b', 1), + hl('Curly', '}'), + }) + + check_parsing('{a#b -> b}', 0, { + -- 0123456789 + ast = { + { + 'CurlyBracesIdentifier(-di):0:0:{', + children = { + { + 'Arrow:0:4: ->', + children = { + 'PlainIdentifier(scope=0,ident=a#b):0:1:a#b', + 'PlainIdentifier(scope=0,ident=b):0:7: b', + }, + }, + }, + }, + }, + err = { + arg = '-> b}', + msg = 'E15: Arrow outside of lambda: %.*s', + }, + }, { + hl('Curly', '{'), + hl('Identifier', 'a#b'), + hl('InvalidArrow', '->', 1), + hl('Identifier', 'b', 1), + hl('Curly', '}'), + }) end) itp('works with ternary operator', function() check_parsing('a ? b : c', 0, { -- cgit From 6168e1127c1c80a3810854649b0776146545043b Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 2 Oct 2017 02:41:55 +0300 Subject: viml/parser/expressions: Add support for comparison operators --- src/nvim/viml/parser/expressions.c | 131 ++++++++--- src/nvim/viml/parser/expressions.h | 45 ++-- test/symbolic/klee/viml_expressions_parser.c | 8 +- test/unit/viml/expressions/lexer_spec.lua | 25 +-- test/unit/viml/expressions/parser_spec.lua | 325 ++++++++++++++++++++++++++- test/unit/viml/helpers.lua | 31 +++ 6 files changed, 483 insertions(+), 82 deletions(-) diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index 982465055e..8e6f991e03 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -102,7 +102,7 @@ LexExprToken viml_pexpr_next_token(ParserState *const pstate, const int flags) if (ret.len < pline.size \ && strchr("?#", pline.data[ret.len]) != NULL) { \ ret.data.cmp.ccs = \ - (CaseCompareStrategy)pline.data[ret.len]; \ + (ExprCaseCompareStrategy)pline.data[ret.len]; \ ret.len++; \ } else { \ ret.data.cmp.ccs = kCCStrategyUseOption; \ @@ -240,7 +240,7 @@ LexExprToken viml_pexpr_next_token(ParserState *const pstate, const int flags) && ((ret.len == 2 && memcmp(pline.data, "is", 2) == 0) || (ret.len == 5 && memcmp(pline.data, "isnot", 5) == 0))) { ret.type = kExprLexComparison; - ret.data.cmp.type = kExprLexCmpIdentical; + ret.data.cmp.type = kExprCmpIdentical; ret.data.cmp.inv = (ret.len == 5); GET_CCS(ret, pline); // Scope: `s:`, etc. @@ -381,10 +381,10 @@ viml_pexpr_next_token_invalid_comparison: ret.type = kExprLexComparison; ret.data.cmp.inv = (schar == '!'); if (pline.data[1] == '=') { - ret.data.cmp.type = kExprLexCmpEqual; + ret.data.cmp.type = kExprCmpEqual; ret.len++; } else if (pline.data[1] == '~') { - ret.data.cmp.type = kExprLexCmpMatches; + ret.data.cmp.type = kExprCmpMatches; ret.len++; } else { goto viml_pexpr_next_token_invalid_comparison; @@ -404,8 +404,8 @@ viml_pexpr_next_token_invalid_comparison: GET_CCS(ret, pline); ret.data.cmp.inv = (schar == '<'); ret.data.cmp.type = ((ret.data.cmp.inv ^ haseqsign) - ? kExprLexCmpGreaterOrEqual - : kExprLexCmpGreater); + ? kExprCmpGreaterOrEqual + : kExprCmpGreater); break; } @@ -503,11 +503,11 @@ static const char *const eltkn_type_tab[] = { }; static const char *const eltkn_cmp_type_tab[] = { - [kExprLexCmpEqual] = "Equal", - [kExprLexCmpMatches] = "Matches", - [kExprLexCmpGreater] = "Greater", - [kExprLexCmpGreaterOrEqual] = "GreaterOrEqual", - [kExprLexCmpIdentical] = "Identical", + [kExprCmpEqual] = "Equal", + [kExprCmpMatches] = "Matches", + [kExprCmpGreater] = "Greater", + [kExprCmpGreaterOrEqual] = "GreaterOrEqual", + [kExprCmpIdentical] = "Identical", }; static const char *const ccs_tab[] = { @@ -770,7 +770,8 @@ static inline void viml_pexpr_debug_print_token( // NVimOperator -> Operator // NVimUnaryOperator -> NVimOperator // NVimBinaryOperator -> NVimOperator -// NVimComparisonOperator -> NVimOperator +// NVimComparisonOperator -> NVimBinaryOperator +// NVimComparisonOperatorModifier -> NVimComparisonOperator // NVimTernary -> NVimOperator // NVimTernaryColon -> NVimTernary // @@ -805,6 +806,8 @@ static inline void viml_pexpr_debug_print_token( // NVimInvalidIdentifier -> NVimInvalidValue // NVimInvalidIdentifierScope -> NVimInvalidValue // NVimInvalidIdentifierScopeDelimiter -> NVimInvalidValue +// NVimInvalidComparisonOperator -> NVimInvalidOperator +// NVimInvalidComparisonOperatorModifier -> NVimInvalidComparisonOperator // // NVimUnaryPlus -> NVimUnaryOperator // NVimBinaryPlus -> NVimBinaryOperator @@ -849,6 +852,8 @@ static const ExprOpLvl node_type_to_op_lvl[] = { [kExprNodeTernaryValue] = kEOpLvlTernaryValue, + [kExprNodeComparison] = kEOpLvlComparison, + [kExprNodeBinaryPlus] = kEOpLvlAddition, [kExprNodeUnaryPlus] = kEOpLvlUnary, @@ -892,6 +897,8 @@ static const ExprOpAssociativity node_type_to_op_ass[] = { [kExprNodeTernaryValue] = kEOpAssRight, + [kExprNodeComparison] = kEOpAssRight, + [kExprNodeBinaryPlus] = kEOpAssLeft, [kExprNodeUnaryPlus] = kEOpAssNo, @@ -935,11 +942,23 @@ static inline ExprOpAssociativity node_ass(const ExprASTNode node) /// Handle binary operator /// /// This function is responsible for handling priority levels as well. -static void viml_pexpr_handle_bop(ExprASTStack *const ast_stack, +/// +/// @param[in] pstate Parser state, used for error reporting. +/// @param ast_stack AST stack. May be popped of some values and will +/// definitely receive new ones. +/// @param bop_node New node to handle. +/// @param[out] want_node_p New value of want_node. +/// @param[out] ast_err Location where error is saved, if any. +/// +/// @return True if no errors occurred, false otherwise. +static bool viml_pexpr_handle_bop(const ParserState *const pstate, + ExprASTStack *const ast_stack, ExprASTNode *const bop_node, - ExprASTWantedNode *const want_node_p) + ExprASTWantedNode *const want_node_p, + ExprASTError *const ast_err) FUNC_ATTR_NONNULL_ALL { + bool ret = true; ExprASTNode **top_node_p = NULL; ExprASTNode *top_node; ExprOpLvl top_node_lvl; @@ -977,7 +996,6 @@ static void viml_pexpr_handle_bop(ExprASTStack *const ast_stack, break; } } while (kv_size(*ast_stack)); - // FIXME: Handle no associativity if (top_node_ass == kEOpAssLeft || top_node_lvl != bop_node_lvl) { // outer(op(x,y)) -> outer(new_op(op(x,y),*)) // @@ -1008,10 +1026,18 @@ static void viml_pexpr_handle_bop(ExprASTStack *const ast_stack, kvi_push(*ast_stack, top_node_p); kvi_push(*ast_stack, &top_node->children->next); kvi_push(*ast_stack, &bop_node->children->next); + // TODO(ZyX-I): Make this not error, but treat like Python does + if (bop_node->type == kExprNodeComparison) { + east_set_error(pstate, ast_err, + _("E15: Operator is not associative: %.*s"), + bop_node->start); + ret = false; + } } *want_node_p = (*want_node_p == kENodeArgumentSeparator ? kENodeArgument : kENodeValue); + return ret; } /// ParserPosition literal based on ParserPosition pos with columns shifted @@ -1074,6 +1100,13 @@ static inline ParserPosition shifted_pos(const ParserPosition pos, #define MAY_HAVE_NEXT_EXPR \ (kv_size(ast_stack) == 1) +/// Add operator node +/// +/// @param[in] cur_node Node to add. +#define ADD_OP_NODE(cur_node) \ + is_invalid |= !viml_pexpr_handle_bop(pstate, &ast_stack, cur_node, \ + &want_node, &ast.err) + /// Record missing operator: for things like /// /// :echo @a @a @@ -1094,7 +1127,7 @@ static inline ParserPosition shifted_pos(const ParserPosition pos, ERROR_FROM_TOKEN_AND_MSG(cur_token, _("E15: Missing operator: %.*s")); \ NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeOpMissing); \ cur_node->len = 0; \ - viml_pexpr_handle_bop(&ast_stack, cur_node, &want_node); \ + ADD_OP_NODE(cur_node); \ goto viml_pexpr_parse_process_token; \ } \ } while (0) @@ -1120,34 +1153,33 @@ static inline ParserPosition shifted_pos(const ParserPosition pos, /// @param[in] msg Error message, assumed to be already translated and /// containing a single %token "%.*s". /// @param[in] start Position at which error occurred. -static inline void east_set_error(ExprAST *const ret_ast, - const ParserState *const pstate, +static inline void east_set_error(const ParserState *const pstate, + ExprASTError *const ret_ast_err, const char *const msg, const ParserPosition start) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_ALWAYS_INLINE { - if (!ret_ast->correct) { + if (ret_ast_err->msg != NULL) { return; } const ParserLine pline = pstate->reader.lines.items[start.line]; - ret_ast->correct = false; - ret_ast->err.msg = msg; - ret_ast->err.arg_len = (int)(pline.size - start.col); - ret_ast->err.arg = pline.data + start.col; + ret_ast_err->msg = msg; + ret_ast_err->arg_len = (int)(pline.size - start.col); + ret_ast_err->arg = pline.data + start.col; } /// Set error from the given token and given message #define ERROR_FROM_TOKEN_AND_MSG(cur_token, msg) \ do { \ is_invalid = true; \ - east_set_error(&ast, pstate, msg, cur_token.start); \ + east_set_error(pstate, &ast.err, msg, cur_token.start); \ } while (0) /// Like #ERROR_FROM_TOKEN_AND_MSG, but gets position from a node #define ERROR_FROM_NODE_AND_MSG(node, msg) \ do { \ is_invalid = true; \ - east_set_error(&ast, pstate, msg, node->start); \ + east_set_error(pstate, &ast.err, msg, node->start); \ } while (0) /// Set error from the given kExprLexInvalid token @@ -1231,7 +1263,6 @@ ExprAST viml_pexpr_parse(ParserState *const pstate, const int flags) FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { ExprAST ast = { - .correct = true, .err = { .msg = NULL, .arg_len = 0, @@ -1359,12 +1390,38 @@ viml_pexpr_parse_process_token: HL_CUR_TOKEN(UnaryPlus); } else { NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeBinaryPlus); - viml_pexpr_handle_bop(&ast_stack, cur_node, &want_node); + ADD_OP_NODE(cur_node); HL_CUR_TOKEN(BinaryPlus); } want_node = kENodeValue; break; } + case kExprLexComparison: { + ADD_VALUE_IF_MISSING( + _("E15: Expected value, got comparison operator: %.*s")); + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeComparison); + if (cur_token.type == kExprLexInvalid) { + cur_node->data.cmp.ccs = kCCStrategyUseOption; + cur_node->data.cmp.type = kExprCmpEqual; + cur_node->data.cmp.inv = false; + } else { + cur_node->data.cmp.ccs = cur_token.data.cmp.ccs; + cur_node->data.cmp.type = cur_token.data.cmp.type; + cur_node->data.cmp.inv = cur_token.data.cmp.inv; + } + ADD_OP_NODE(cur_node); + if (cur_token.data.cmp.ccs != kCCStrategyUseOption) { + viml_parser_highlight(pstate, cur_token.start, cur_token.len - 1, + HL(ComparisonOperator)); + viml_parser_highlight( + pstate, shifted_pos(cur_token.start, cur_token.len - 1), 1, + HL(ComparisonOperatorModifier)); + } else { + HL_CUR_TOKEN(ComparisonOperator); + } + want_node = kENodeValue; + break; + } case kExprLexComma: { assert(want_node != kENodeArgument); if (want_node == kENodeValue) { @@ -1415,7 +1472,7 @@ viml_pexpr_parse_invalid_comma: } } NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeComma); - viml_pexpr_handle_bop(&ast_stack, cur_node, &want_node); + ADD_OP_NODE(cur_node); HL_CUR_TOKEN(Comma); break; } @@ -1474,7 +1531,7 @@ viml_pexpr_parse_invalid_colon: HL_CUR_TOKEN(TernaryColon); } else { NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeColon); - viml_pexpr_handle_bop(&ast_stack, cur_node, &want_node); + ADD_OP_NODE(cur_node); HL_CUR_TOKEN(Colon); } want_node = kENodeValue; @@ -1646,7 +1703,7 @@ viml_pexpr_parse_figure_brace_closing_error: ERROR_FROM_TOKEN_AND_MSG( cur_token, _("E15: Arrow outside of lambda: %.*s")); NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeArrow); - viml_pexpr_handle_bop(&ast_stack, cur_node, &want_node); + ADD_OP_NODE(cur_node); } want_node = kENodeValue; HL_CUR_TOKEN(Arrow); @@ -1775,7 +1832,7 @@ viml_pexpr_parse_no_paren_closing_error: {} } } NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeCall); - viml_pexpr_handle_bop(&ast_stack, cur_node, &want_node); + ADD_OP_NODE(cur_node); HL_CUR_TOKEN(CallingParenthesis); } else { // Currently it is impossible to reach this. @@ -1788,7 +1845,7 @@ viml_pexpr_parse_no_paren_closing_error: {} case kExprLexQuestion: { ADD_VALUE_IF_MISSING(_("E15: Expected value, got question mark: %.*s")); NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeTernary); - viml_pexpr_handle_bop(&ast_stack, cur_node, &want_node); + ADD_OP_NODE(cur_node); HL_CUR_TOKEN(Ternary); ExprASTNode *ter_val_node; NEW_NODE_WITH_CUR_POS(ter_val_node, kExprNodeTernaryValue); @@ -1808,7 +1865,7 @@ viml_pexpr_parse_cycle_end: } while (true); viml_pexpr_parse_end: if (want_node == kENodeValue) { - east_set_error(&ast, pstate, _("E15: Expected value, got EOC: %.*s"), + east_set_error(pstate, &ast.err, _("E15: Expected value, got EOC: %.*s"), pstate->pos); } else if (kv_size(ast_stack) != 1) { // Something may be wrong, check whether it really is. @@ -1819,7 +1876,7 @@ viml_pexpr_parse_end: // Topmost stack item must be a *finished* value, so it must not be // analyzed. E.g. it may contain an already finished nested expression. kv_drop(ast_stack, 1); - while (ast.correct && kv_size(ast_stack)) { + while (ast.err.msg == NULL && kv_size(ast_stack)) { const ExprASTNode *const cur_node = (*kv_pop(ast_stack)); // This should only happen when want_node == kENodeValue. assert(cur_node != NULL); @@ -1832,14 +1889,14 @@ viml_pexpr_parse_end: } case kExprNodeCall: { east_set_error( - &ast, pstate, + pstate, &ast.err, _("E116: Missing closing parenthesis for function call: %.*s"), cur_node->start); break; } case kExprNodeNested: { east_set_error( - &ast, pstate, + pstate, &ast.err, _("E110: Missing closing parenthesis for nested expression" ": %.*s"), cur_node->start); @@ -1855,7 +1912,7 @@ viml_pexpr_parse_end: if (!cur_node->data.ter.got_colon) { // Actually Vim throws E109 in more cases. east_set_error( - &ast, pstate, _("E109: Missing ':' after '?': %.*s"), + pstate, &ast.err, _("E109: Missing ':' after '?': %.*s"), cur_node->start); } break; diff --git a/src/nvim/viml/parser/expressions.h b/src/nvim/viml/parser/expressions.h index cf0907850a..8ca3ceacb9 100644 --- a/src/nvim/viml/parser/expressions.h +++ b/src/nvim/viml/parser/expressions.h @@ -16,7 +16,7 @@ typedef enum { kCCStrategyUseOption = 0, // 0 for xcalloc kCCStrategyMatchCase = '#', kCCStrategyIgnoreCase = '?', -} CaseCompareStrategy; +} ExprCaseCompareStrategy; /// Lexer token type typedef enum { @@ -52,6 +52,14 @@ typedef enum { kExprLexArrow, ///< Arrow, like from lambda expressions. } LexExprTokenType; +typedef enum { + kExprCmpEqual, ///< Equality, unequality. + kExprCmpMatches, ///< Matches regex, not matches regex. + kExprCmpGreater, ///< `>` or `<=` + kExprCmpGreaterOrEqual, ///< `>=` or `<`. + kExprCmpIdentical, ///< `is` or `isnot` +} ExprComparisonType; + /// Lexer token typedef struct { ParserPosition start; @@ -59,14 +67,8 @@ typedef struct { LexExprTokenType type; union { struct { - enum { - kExprLexCmpEqual, ///< Equality, unequality. - kExprLexCmpMatches, ///< Matches regex, not matches regex. - kExprLexCmpGreater, ///< `>` or `<=` - kExprLexCmpGreaterOrEqual, ///< `>=` or `<`. - kExprLexCmpIdentical, ///< `is` or `isnot` - } type; ///< Comparison type. - CaseCompareStrategy ccs; ///< Case comparison strategy. + ExprComparisonType type; ///< Comparison type. + ExprCaseCompareStrategy ccs; ///< Case comparison strategy. bool inv; ///< True if comparison is to be inverted. } cmp; ///< For kExprLexComparison. @@ -171,6 +173,7 @@ typedef enum { kExprNodeComma = ',', ///< Comma “operator”. kExprNodeColon = ':', ///< Colon “operator”. kExprNodeArrow = '>', ///< Arrow “operator”. + kExprNodeComparison = '=', ///< Various comparison operators. } ExprASTNodeType; typedef struct expr_ast_node ExprASTNode; @@ -214,6 +217,11 @@ struct expr_ast_node { struct { bool got_colon; ///< True if colon was seen. } ter; ///< For kExprNodeTernaryValue. + struct { + ExprComparisonType type; ///< Comparison type. + ExprCaseCompareStrategy ccs; ///< Case comparison strategy. + bool inv; ///< True if comparison is to be inverted. + } cmp; ///< For kExprNodeComparison. } data; }; @@ -235,19 +243,22 @@ enum { // viml_expressions_parser.c. } ExprParserFlags; +/// AST error definition +typedef struct { + /// Error message. Must contain a single printf format atom: %.*s. + const char *msg; + /// Error message argument: points to the location of the error. + const char *arg; + /// Message argument length: length till the end of string. + int arg_len; +} ExprASTError; + /// Structure representing complety AST for one expression typedef struct { - /// True if represented AST is correct and can be executed. Incorrect ones may - /// still be used for completion, or in linters. - bool correct; /// When AST is not correct this message will be printed. /// /// Uses `emsgf(msg, arg_len, arg);`, `msg` is assumed to contain only `%.*s`. - struct { - const char *msg; - int arg_len; - const char *arg; - } err; + ExprASTError err; /// Root node of the AST. ExprASTNode *root; } ExprAST; diff --git a/test/symbolic/klee/viml_expressions_parser.c b/test/symbolic/klee/viml_expressions_parser.c index 2bad1adc53..5ad592b99f 100644 --- a/test/symbolic/klee/viml_expressions_parser.c +++ b/test/symbolic/klee/viml_expressions_parser.c @@ -91,12 +91,6 @@ int main(const int argc, const char *const *const argv, const ExprAST ast = viml_pexpr_parse(&pstate, flags); assert(ast.root != NULL || plines[0].size == 0); - assert(ast.root != NULL || !ast.correct); - assert(ast.correct - || (ast.err.msg != NULL - && ast.err.arg != NULL - && ast.err.arg >= plines[0].data - && ((size_t)(ast.err.arg - plines[0].data) + ast.err.arg_len - <= plines[0].size))); + assert(ast.root != NULL || ast.err.msg); // FIXME: free memory and assert no memory leaks } diff --git a/test/unit/viml/expressions/lexer_spec.lua b/test/unit/viml/expressions/lexer_spec.lua index 972478c2e5..d201d54526 100644 --- a/test/unit/viml/expressions/lexer_spec.lua +++ b/test/unit/viml/expressions/lexer_spec.lua @@ -1,7 +1,7 @@ local helpers = require('test.unit.helpers')(after_each) -local viml_helpers = require('test.unit.viml.helpers') local global_helpers = require('test.helpers') local itp = helpers.gen_itp(it) +local viml_helpers = require('test.unit.viml.helpers') local child_call_once = helpers.child_call_once local conv_enum = helpers.conv_enum @@ -9,17 +9,18 @@ local cimport = helpers.cimport local ffi = helpers.ffi local eq = helpers.eq +local conv_ccs = viml_helpers.conv_ccs local pline2lua = viml_helpers.pline2lua local new_pstate = viml_helpers.new_pstate local intchar2lua = viml_helpers.intchar2lua +local conv_cmp_type = viml_helpers.conv_cmp_type local pstate_set_str = viml_helpers.pstate_set_str local shallowcopy = global_helpers.shallowcopy local lib = cimport('./src/nvim/viml/parser/expressions.h') -local eltkn_type_tab, eltkn_cmp_type_tab, ccs_tab, eltkn_mul_type_tab -local eltkn_opt_scope_tab +local eltkn_type_tab, eltkn_mul_type_tab, eltkn_opt_scope_tab child_call_once(function() eltkn_type_tab = { [tonumber(lib.kExprLexInvalid)] = 'Invalid', @@ -54,20 +55,6 @@ child_call_once(function() [tonumber(lib.kExprLexArrow)] = 'Arrow', } - eltkn_cmp_type_tab = { - [tonumber(lib.kExprLexCmpEqual)] = 'Equal', - [tonumber(lib.kExprLexCmpMatches)] = 'Matches', - [tonumber(lib.kExprLexCmpGreater)] = 'Greater', - [tonumber(lib.kExprLexCmpGreaterOrEqual)] = 'GreaterOrEqual', - [tonumber(lib.kExprLexCmpIdentical)] = 'Identical', - } - - ccs_tab = { - [tonumber(lib.kCCStrategyUseOption)] = 'UseOption', - [tonumber(lib.kCCStrategyMatchCase)] = 'MatchCase', - [tonumber(lib.kCCStrategyIgnoreCase)] = 'IgnoreCase', - } - eltkn_mul_type_tab = { [tonumber(lib.kExprLexMulMul)] = 'Mul', [tonumber(lib.kExprLexMulDiv)] = 'Div', @@ -101,8 +88,8 @@ local function eltkn2lua(pstate, tkn) end if ret.type == 'Comparison' then ret.data = { - type = conv_enum(eltkn_cmp_type_tab, tkn.data.cmp.type), - ccs = conv_enum(ccs_tab, tkn.data.cmp.ccs), + type = conv_cmp_type(tkn.data.cmp.type), + ccs = conv_ccs(tkn.data.cmp.ccs), inv = (not not tkn.data.cmp.inv), } elseif ret.type == 'Multiplication' then diff --git a/test/unit/viml/expressions/parser_spec.lua b/test/unit/viml/expressions/parser_spec.lua index 2c80b437dc..efa88455e4 100644 --- a/test/unit/viml/expressions/parser_spec.lua +++ b/test/unit/viml/expressions/parser_spec.lua @@ -1,7 +1,7 @@ local helpers = require('test.unit.helpers')(after_each) -local viml_helpers = require('test.unit.viml.helpers') local global_helpers = require('test.helpers') local itp = helpers.gen_itp(it) +local viml_helpers = require('test.unit.viml.helpers') local make_enum_conv_tab = helpers.make_enum_conv_tab local child_call_once = helpers.child_call_once @@ -11,9 +11,11 @@ local cimport = helpers.cimport local ffi = helpers.ffi local eq = helpers.eq +local conv_ccs = viml_helpers.conv_ccs local pline2lua = viml_helpers.pline2lua local new_pstate = viml_helpers.new_pstate local intchar2lua = viml_helpers.intchar2lua +local conv_cmp_type = viml_helpers.conv_cmp_type local pstate_set_str = viml_helpers.pstate_set_str local format_string = global_helpers.format_string @@ -83,6 +85,7 @@ make_enum_conv_tab(lib, { 'kExprNodeComma', 'kExprNodeColon', 'kExprNodeArrow', + 'kExprNodeComparison', }, 'kExprNode', function(ret) east_node_type_tab = ret end) local function conv_east_node_type(typ) @@ -121,6 +124,10 @@ local function eastnode2lua(pstate, eastnode, checked_nodes) (eastnode.data.fig.type_guesses.allow_lambda and '\\' or '-') .. (eastnode.data.fig.type_guesses.allow_dict and 'd' or '-') .. (eastnode.data.fig.type_guesses.allow_ident and 'i' or '-')) + elseif typ == 'Comparison' then + typ = typ .. ('(type=%s,inv=%u,ccs=%s)'):format( + conv_cmp_type(eastnode.data.cmp.type), eastnode.data.cmp.inv and 1 or 0, + conv_ccs(eastnode.data.cmp.ccs)) end ret_str = typ .. ':' .. ret_str local can_simplify = true @@ -150,7 +157,7 @@ end local function east2lua(pstate, east) local checked_nodes = {} return { - err = (not east.correct) and { + err = east.err.msg ~= nil and { msg = ffi.string(east.err.msg), arg = ('%s'):format( ffi.string(east.err.arg, east.err.arg_len)), @@ -3328,4 +3335,318 @@ describe('Expressions parser', function() hl('Identifier', 'h'), }) end) + itp('works with comparison operators', function() + check_parsing('a == b', 0, { + -- 012345 + ast = { + { + 'Comparison(type=Equal,inv=0,ccs=UseOption):0:1: ==', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainIdentifier(scope=0,ident=b):0:4: b', + }, + }, + }, + }, { + hl('Identifier', 'a'), + hl('ComparisonOperator', '==', 1), + hl('Identifier', 'b', 1), + }) + + check_parsing('a ==? b', 0, { + -- 0123456 + ast = { + { + 'Comparison(type=Equal,inv=0,ccs=IgnoreCase):0:1: ==?', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainIdentifier(scope=0,ident=b):0:5: b', + }, + }, + }, + }, { + hl('Identifier', 'a'), + hl('ComparisonOperator', '==', 1), + hl('ComparisonOperatorModifier', '?'), + hl('Identifier', 'b', 1), + }) + + check_parsing('a ==# b', 0, { + -- 0123456 + ast = { + { + 'Comparison(type=Equal,inv=0,ccs=MatchCase):0:1: ==#', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainIdentifier(scope=0,ident=b):0:5: b', + }, + }, + }, + }, { + hl('Identifier', 'a'), + hl('ComparisonOperator', '==', 1), + hl('ComparisonOperatorModifier', '#'), + hl('Identifier', 'b', 1), + }) + + check_parsing('a !=# b', 0, { + -- 0123456 + ast = { + { + 'Comparison(type=Equal,inv=1,ccs=MatchCase):0:1: !=#', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainIdentifier(scope=0,ident=b):0:5: b', + }, + }, + }, + }, { + hl('Identifier', 'a'), + hl('ComparisonOperator', '!=', 1), + hl('ComparisonOperatorModifier', '#'), + hl('Identifier', 'b', 1), + }) + + check_parsing('a <=# b', 0, { + -- 0123456 + ast = { + { + 'Comparison(type=Greater,inv=1,ccs=MatchCase):0:1: <=#', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainIdentifier(scope=0,ident=b):0:5: b', + }, + }, + }, + }, { + hl('Identifier', 'a'), + hl('ComparisonOperator', '<=', 1), + hl('ComparisonOperatorModifier', '#'), + hl('Identifier', 'b', 1), + }) + + check_parsing('a >=# b', 0, { + -- 0123456 + ast = { + { + 'Comparison(type=GreaterOrEqual,inv=0,ccs=MatchCase):0:1: >=#', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainIdentifier(scope=0,ident=b):0:5: b', + }, + }, + }, + }, { + hl('Identifier', 'a'), + hl('ComparisonOperator', '>=', 1), + hl('ComparisonOperatorModifier', '#'), + hl('Identifier', 'b', 1), + }) + + check_parsing('a ># b', 0, { + -- 012345 + ast = { + { + 'Comparison(type=Greater,inv=0,ccs=MatchCase):0:1: >#', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainIdentifier(scope=0,ident=b):0:4: b', + }, + }, + }, + }, { + hl('Identifier', 'a'), + hl('ComparisonOperator', '>', 1), + hl('ComparisonOperatorModifier', '#'), + hl('Identifier', 'b', 1), + }) + + check_parsing('a <# b', 0, { + -- 012345 + ast = { + { + 'Comparison(type=GreaterOrEqual,inv=1,ccs=MatchCase):0:1: <#', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainIdentifier(scope=0,ident=b):0:4: b', + }, + }, + }, + }, { + hl('Identifier', 'a'), + hl('ComparisonOperator', '<', 1), + hl('ComparisonOperatorModifier', '#'), + hl('Identifier', 'b', 1), + }) + + check_parsing('a is#b', 0, { + -- 012345 + ast = { + { + 'Comparison(type=Identical,inv=0,ccs=MatchCase):0:1: is#', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainIdentifier(scope=0,ident=b):0:5:b', + }, + }, + }, + }, { + hl('Identifier', 'a'), + hl('ComparisonOperator', 'is', 1), + hl('ComparisonOperatorModifier', '#'), + hl('Identifier', 'b'), + }) + + check_parsing('a is?b', 0, { + -- 012345 + ast = { + { + 'Comparison(type=Identical,inv=0,ccs=IgnoreCase):0:1: is?', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainIdentifier(scope=0,ident=b):0:5:b', + }, + }, + }, + }, { + hl('Identifier', 'a'), + hl('ComparisonOperator', 'is', 1), + hl('ComparisonOperatorModifier', '?'), + hl('Identifier', 'b'), + }) + + check_parsing('a isnot b', 0, { + -- 012345678 + ast = { + { + 'Comparison(type=Identical,inv=1,ccs=UseOption):0:1: isnot', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainIdentifier(scope=0,ident=b):0:7: b', + }, + }, + }, + }, { + hl('Identifier', 'a'), + hl('ComparisonOperator', 'isnot', 1), + hl('Identifier', 'b', 1), + }) + + check_parsing('a < b < c', 0, { + -- 012345678 + ast = { + { + 'Comparison(type=GreaterOrEqual,inv=1,ccs=UseOption):0:1: <', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'Comparison(type=GreaterOrEqual,inv=1,ccs=UseOption):0:5: <', + children = { + 'PlainIdentifier(scope=0,ident=b):0:3: b', + 'PlainIdentifier(scope=0,ident=c):0:7: c', + }, + }, + }, + }, + }, + err = { + arg = ' < c', + msg = 'E15: Operator is not associative: %.*s', + }, + }, { + hl('Identifier', 'a'), + hl('ComparisonOperator', '<', 1), + hl('Identifier', 'b', 1), + hl('InvalidComparisonOperator', '<', 1), + hl('Identifier', 'c', 1), + }) + check_parsing('a += b', 0, { + -- 012345 + ast = { + { + 'Comparison(type=Equal,inv=0,ccs=UseOption):0:3:=', + children = { + { + 'BinaryPlus:0:1: +', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'Missing:0:3:', + }, + }, + 'PlainIdentifier(scope=0,ident=b):0:4: b', + }, + }, + }, + err = { + arg = '= b', + msg = 'E15: Expected == or =~: %.*s', + }, + }, { + hl('Identifier', 'a'), + hl('BinaryPlus', '+', 1), + hl('InvalidComparisonOperator', '='), + hl('Identifier', 'b', 1), + }) + check_parsing('a + b == c + d', 0, { + -- 01234567890123 + -- 0 1 + ast = { + { + 'Comparison(type=Equal,inv=0,ccs=UseOption):0:5: ==', + children = { + { + 'BinaryPlus:0:1: +', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainIdentifier(scope=0,ident=b):0:3: b', + }, + }, + { + 'BinaryPlus:0:10: +', + children = { + 'PlainIdentifier(scope=0,ident=c):0:8: c', + 'PlainIdentifier(scope=0,ident=d):0:12: d', + }, + }, + }, + }, + }, + }, { + hl('Identifier', 'a'), + hl('BinaryPlus', '+', 1), + hl('Identifier', 'b', 1), + hl('ComparisonOperator', '==', 1), + hl('Identifier', 'c', 1), + hl('BinaryPlus', '+', 1), + hl('Identifier', 'd', 1), + }) + check_parsing('+ a == + b', 0, { + -- 0123456789 + ast = { + { + 'Comparison(type=Equal,inv=0,ccs=UseOption):0:3: ==', + children = { + { + 'UnaryPlus:0:0:+', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1: a', + }, + }, + { + 'UnaryPlus:0:6: +', + children = { + 'PlainIdentifier(scope=0,ident=b):0:8: b', + }, + }, + }, + }, + }, + }, { + hl('UnaryPlus', '+'), + hl('Identifier', 'a', 1), + hl('ComparisonOperator', '==', 1), + hl('UnaryPlus', '+', 1), + hl('Identifier', 'b', 1), + }) + end) end) diff --git a/test/unit/viml/helpers.lua b/test/unit/viml/helpers.lua index 2cb60499eb..0b92be2654 100644 --- a/test/unit/viml/helpers.lua +++ b/test/unit/viml/helpers.lua @@ -1,8 +1,13 @@ local helpers = require('test.unit.helpers')(nil) local ffi = helpers.ffi +local cimport = helpers.cimport local kvi_new = helpers.kvi_new local kvi_init = helpers.kvi_init +local conv_enum = helpers.conv_enum +local make_enum_conv_tab = helpers.make_enum_conv_tab + +local lib = cimport('./src/nvim/viml/parser/expressions.h') local function new_pstate(strings) local strings_idx = 0 @@ -88,10 +93,36 @@ local function pstate_set_str(pstate, start, len, ret) return ret end +local eltkn_cmp_type_tab +make_enum_conv_tab(lib, { + 'kExprCmpEqual', + 'kExprCmpMatches', + 'kExprCmpGreater', + 'kExprCmpGreaterOrEqual', + 'kExprCmpIdentical', +}, 'kExprCmp', function(ret) eltkn_cmp_type_tab = ret end) + +local function conv_cmp_type(typ) + return conv_enum(eltkn_cmp_type_tab, typ) +end + +local ccs_tab +make_enum_conv_tab(lib, { + 'kCCStrategyUseOption', + 'kCCStrategyMatchCase', + 'kCCStrategyIgnoreCase', +}, 'kCCStrategy', function(ret) ccs_tab = ret end) + +local function conv_ccs(ccs) + return conv_enum(ccs_tab, ccs) +end + return { + conv_ccs = conv_ccs, pline2lua = pline2lua, pstate_str = pstate_str, new_pstate = new_pstate, intchar2lua = intchar2lua, + conv_cmp_type = conv_cmp_type, pstate_set_str = pstate_set_str, } -- cgit From 0bc4e2237960712426da3774c1430f5874c49aea Mon Sep 17 00:00:00 2001 From: ZyX Date: Tue, 3 Oct 2017 00:39:40 +0300 Subject: viml/parser/expressions: Forbid dot or alpha characters after a float This is basically what Vim already does, in addition to forbidding floats should there be a concat immediately before it. --- src/nvim/viml/parser/expressions.c | 6 ++++++ test/unit/viml/expressions/lexer_spec.lua | 21 +++++++++------------ 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index 8e6f991e03..8c95d1db14 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -186,6 +186,7 @@ LexExprToken viml_pexpr_next_token(ParserState *const pstate, const int flags) ret.data.num.is_float = false; CHARREG(kExprLexNumber, ascii_isdigit); if (flags & kELFlagAllowFloat) { + const LexExprToken non_float_ret = ret; if (pline.size > ret.len + 1 && pline.data[ret.len] == '.' && ascii_isdigit(pline.data[ret.len + 1])) { @@ -207,6 +208,11 @@ LexExprToken viml_pexpr_next_token(ParserState *const pstate, const int flags) CHARREG(kExprLexNumber, ascii_isdigit); } } + if (pline.size > ret.len + && (pline.data[ret.len] == '.' + || ASCII_ISALPHA(pline.data[ret.len]))) { + ret = non_float_ret; + } } break; } diff --git a/test/unit/viml/expressions/lexer_spec.lua b/test/unit/viml/expressions/lexer_spec.lua index d201d54526..bd8045632e 100644 --- a/test/unit/viml/expressions/lexer_spec.lua +++ b/test/unit/viml/expressions/lexer_spec.lua @@ -264,6 +264,15 @@ describe('Expressions lexer', function() simple_test({''}, 'EOC', 0, {error='start.col >= #pstr'}) simple_test({'2.'}, 'Number', 1, {data={is_float=false}, str='2'}) simple_test({'2.x'}, 'Number', 1, {data={is_float=false}, str='2'}) + simple_test({'2.2.'}, 'Number', 1, {data={is_float=false}, str='2'}) + simple_test({'2.0x'}, 'Number', 1, {data={is_float=false}, str='2'}) + simple_test({'2.0e'}, 'Number', 1, {data={is_float=false}, str='2'}) + simple_test({'2.0e+'}, 'Number', 1, {data={is_float=false}, str='2'}) + simple_test({'2.0e-'}, 'Number', 1, {data={is_float=false}, str='2'}) + simple_test({'2.0e+x'}, 'Number', 1, {data={is_float=false}, str='2'}) + simple_test({'2.0e-x'}, 'Number', 1, {data={is_float=false}, str='2'}) + simple_test({'2.0e+1a'}, 'Number', 1, {data={is_float=false}, str='2'}) + simple_test({'2.0e-1a'}, 'Number', 1, {data={is_float=false}, str='2'}) end local function regular_scope_tests() @@ -296,12 +305,6 @@ describe('Expressions lexer', function() local function regular_number_tests() simple_test({'2.0'}, 'Number', 1, {data={is_float=false}, str='2'}) - simple_test({'2.0x'}, 'Number', 1, {data={is_float=false}, str='2'}) - simple_test({'2.0e'}, 'Number', 1, {data={is_float=false}, str='2'}) - simple_test({'2.0e+'}, 'Number', 1, {data={is_float=false}, str='2'}) - simple_test({'2.0e-'}, 'Number', 1, {data={is_float=false}, str='2'}) - simple_test({'2.0e+x'}, 'Number', 1, {data={is_float=false}, str='2'}) - simple_test({'2.0e-x'}, 'Number', 1, {data={is_float=false}, str='2'}) simple_test({'2.0e5'}, 'Number', 1, {data={is_float=false}, str='2'}) simple_test({'2.0e+5'}, 'Number', 1, {data={is_float=false}, str='2'}) simple_test({'2.0e-5'}, 'Number', 1, {data={is_float=false}, str='2'}) @@ -350,12 +353,6 @@ describe('Expressions lexer', function() regular_is_tests() simple_test({'2.0'}, 'Number', 3, {data={is_float=true}, str='2.0'}) - simple_test({'2.0x'}, 'Number', 3, {data={is_float=true}, str='2.0'}) - simple_test({'2.0e'}, 'Number', 3, {data={is_float=true}, str='2.0'}) - simple_test({'2.0e+'}, 'Number', 3, {data={is_float=true}, str='2.0'}) - simple_test({'2.0e-'}, 'Number', 3, {data={is_float=true}, str='2.0'}) - simple_test({'2.0e+x'}, 'Number', 3, {data={is_float=true}, str='2.0'}) - simple_test({'2.0e-x'}, 'Number', 3, {data={is_float=true}, str='2.0'}) simple_test({'2.0e5'}, 'Number', 5, {data={is_float=true}, str='2.0e5'}) simple_test({'2.0e+5'}, 'Number', 6, {data={is_float=true}, str='2.0e+5'}) simple_test({'2.0e-5'}, 'Number', 6, {data={is_float=true}, str='2.0e-5'}) -- cgit From 163792e9b9854fe046ada3233dec0fd0f6c55737 Mon Sep 17 00:00:00 2001 From: ZyX Date: Fri, 6 Oct 2017 01:19:43 +0300 Subject: viml/parser/expressions: Make lexer parse numbers, support non-decimal --- src/nvim/viml/parser/expressions.c | 146 ++++++++++++++++++++++-- src/nvim/viml/parser/expressions.h | 6 + test/symbolic/klee/nvim/charset.c | 165 ++++++++++++++++++++++++++++ test/symbolic/klee/viml_expressions_lexer.c | 6 +- test/unit/viml/expressions/lexer_spec.lua | 73 ++++++++---- 5 files changed, 362 insertions(+), 34 deletions(-) diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index 8c95d1db14..5d892fb8f8 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -15,10 +15,13 @@ #include "nvim/ascii.h" #include "nvim/assert.h" #include "nvim/lib/kvec.h" +#include "nvim/eval/typval.h" #include "nvim/viml/parser/expressions.h" #include "nvim/viml/parser/parser.h" +#define vim_str2nr(s, ...) vim_str2nr((const char_u *)(s), __VA_ARGS__) + typedef kvec_withinit_t(ExprASTNode **, 16) ExprASTStack; /// Which nodes may be wanted @@ -72,6 +75,43 @@ typedef enum { /// Character used as a separator in autoload function/variable names. #define AUTOLOAD_CHAR '#' +/// Scale number by a given factor +/// +/// Used to apply exponent to a number. Idea taken from uClibc. +/// +/// @param[in] num Number to scale. Does not bother doing anything if it is +/// zero. +/// @param[in] base Base, should be 10 since non-decimal floating-point +/// numbers are not supported. +/// @param[in] exponent Exponent to scale by. +/// @param[in] exponent_negative True if exponent is negative. +static inline float_T scale_number(const float_T num, + const uint8_t base, + const uvarnumber_T exponent, + const bool exponent_negative) + FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_CONST +{ + if (num == 0 || exponent == 0) { + return num; + } + assert(base); + uvarnumber_T exp = exponent; + float_T p_base = (float_T)base; + float_T ret = num; + while (exp) { + if (exp & 1) { + if (exponent_negative) { + ret /= p_base; + } else { + ret *= p_base; + } + } + exp >>= 1; + p_base *= p_base; + } + return ret; +} + /// Get next token for the VimL expression input /// /// @param pstate Parser state. @@ -184,6 +224,11 @@ LexExprToken viml_pexpr_next_token(ParserState *const pstate, const int flags) case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { ret.data.num.is_float = false; + ret.data.num.base = 10; + size_t frac_start = 0; + size_t exp_start = 0; + size_t frac_end = 0; + bool exp_negative = false; CHARREG(kExprLexNumber, ascii_isdigit); if (flags & kELFlagAllowFloat) { const LexExprToken non_float_ret = ret; @@ -191,8 +236,18 @@ LexExprToken viml_pexpr_next_token(ParserState *const pstate, const int flags) && pline.data[ret.len] == '.' && ascii_isdigit(pline.data[ret.len + 1])) { ret.len++; + frac_start = ret.len; + frac_end = ret.len; ret.data.num.is_float = true; - CHARREG(kExprLexNumber, ascii_isdigit); + for (; ret.len < pline.size && ascii_isdigit(pline.data[ret.len]) + ; ret.len++) { + // A small optimization: trailing zeroes in fractional part do not + // add anything to significand, so it is useless to include them in + // frac_end. + if (pline.data[ret.len] != '0') { + frac_end = ret.len + 1; + } + } if (pline.size > ret.len + 1 && (pline.data[ret.len] == 'e' || pline.data[ret.len] == 'E') @@ -202,9 +257,11 @@ LexExprToken viml_pexpr_next_token(ParserState *const pstate, const int flags) && ascii_isdigit(pline.data[ret.len + 2])) || ascii_isdigit(pline.data[ret.len + 1]))) { ret.len++; - if (pline.data[ret.len] == '+' || pline.data[ret.len] == '-') { + if (pline.data[ret.len] == '+' + || (exp_negative = (pline.data[ret.len] == '-'))) { ret.len++; } + exp_start = ret.len; CHARREG(kExprLexNumber, ascii_isdigit); } } @@ -214,6 +271,58 @@ LexExprToken viml_pexpr_next_token(ParserState *const pstate, const int flags) ret = non_float_ret; } } + // TODO(ZyX-I): detect overflows + if (ret.data.num.is_float) { + // Vim used to use string2float here which in turn uses strtod(). There + // are two problems with this approach: + // 1. strtod() is locale-dependent. Not sure how it is worked around so + // that I do not see relevant bugs, but it still does not look like + // a good idea. + // 2. strtod() does not accept length argument. + // + // The below variant of parsing floats was recognized as acceptable + // because it is basically how uClibc does the thing: it generates + // a number ignoring decimal point (but recording its position), then + // uses recorded position to scale number down when processing exponent. + float_T significand_part = 0; + uvarnumber_T exp_part = 0; + const size_t frac_size = (size_t)(frac_end - frac_start); + for (size_t i = 0; i < frac_end; i++) { + if (i == frac_start - 1) { + continue; + } + significand_part = significand_part * 10 + (pline.data[i] - '0'); + } + if (exp_start) { + vim_str2nr(pline.data + exp_start, NULL, NULL, 0, NULL, &exp_part, + (int)(ret.len - exp_start)); + } + if (exp_negative) { + exp_part += frac_size; + } else { + if (exp_part < frac_size) { + exp_negative = true; + exp_part = frac_size - exp_part; + } else { + exp_part -= frac_size; + } + } + ret.data.num.val.floating = scale_number(significand_part, 10, exp_part, + exp_negative); + } else { + int len; + int prep; + vim_str2nr(pline.data, &prep, &len, STR2NR_ALL, NULL, + &ret.data.num.val.integer, (int)pline.size); + ret.len = (size_t)len; + const uint8_t bases[] = { + [0] = 10, + ['0'] = 8, + ['x'] = 16, ['X'] = 16, + ['b'] = 2, ['B'] = 2, + }; + ret.data.num.base = bases[prep]; + } break; } @@ -474,7 +583,6 @@ viml_pexpr_next_token_adv_return: return ret; } -#ifdef UNIT_TESTING static const char *const eltkn_type_tab[] = { [kExprLexInvalid] = "Invalid", [kExprLexMissing] = "Missing", @@ -617,7 +725,12 @@ const char *viml_pexpr_repr_token(const ParserState *const pstate, (int)token.data.opt.len, token.data.opt.name) TKNARGS(kExprLexPlainIdentifier, "(scope=%s,autoload=%i)", intchar2str(token.data.var.scope), (int)token.data.var.autoload) - TKNARGS(kExprLexNumber, "(is_float=%i)", (int)token.data.num.is_float) + TKNARGS(kExprLexNumber, "(is_float=%i,base=%i,val=%lg)", + (int)token.data.num.is_float, + (int)token.data.num.base, + (double)(token.data.num.is_float + ? token.data.num.val.floating + : token.data.num.val.integer)) TKNARGS(kExprLexInvalid, "(msg=%s)", token.data.err.msg) default: { // No additional arguments. @@ -642,7 +755,6 @@ viml_pexpr_repr_token_end: } return ret; } -#endif #ifdef UNIT_TESTING #include @@ -776,8 +888,10 @@ static inline void viml_pexpr_debug_print_token( // NVimOperator -> Operator // NVimUnaryOperator -> NVimOperator // NVimBinaryOperator -> NVimOperator +// // NVimComparisonOperator -> NVimBinaryOperator // NVimComparisonOperatorModifier -> NVimComparisonOperator +// // NVimTernary -> NVimOperator // NVimTernaryColon -> NVimTernary // @@ -795,8 +909,21 @@ static inline void viml_pexpr_debug_print_token( // NVimIdentifierScope -> NVimIdentifier // NVimIdentifierScopeDelimiter -> NVimIdentifier // +// NVimIdentifierKey -> Identifier +// // NVimFigureBrace -> NVimInternalError // +// NVimUnaryPlus -> NVimUnaryOperator +// NVimBinaryPlus -> NVimBinaryOperator +// NVimConcatOrSubscript -> NVimBinaryOperator +// +// NVimRegister -> SpecialChar +// NVimNumber -> Number +// NVimFloat -> NVimNumber +// +// NVimNestingParenthesis -> NVimParenthesis +// NVimCallingParenthesis -> NVimParenthesis +// // NVimInvalidComma -> NVimInvalidDelimiter // NVimInvalidSpacing -> NVimInvalid // NVimInvalidTernary -> NVimInvalidOperator @@ -814,12 +941,9 @@ static inline void viml_pexpr_debug_print_token( // NVimInvalidIdentifierScopeDelimiter -> NVimInvalidValue // NVimInvalidComparisonOperator -> NVimInvalidOperator // NVimInvalidComparisonOperatorModifier -> NVimInvalidComparisonOperator -// -// NVimUnaryPlus -> NVimUnaryOperator -// NVimBinaryPlus -> NVimBinaryOperator -// NVimRegister -> SpecialChar -// NVimNestingParenthesis -> NVimParenthesis -// NVimCallingParenthesis -> NVimParenthesis +// NVimInvalidNumber -> NVimInvalidValue +// NVimInvalidFloat -> NVimInvalidValue +// NVimInvalidIdentifierKey -> NVimInvalidIdentifier /// Allocate a new node and set some of the values /// diff --git a/src/nvim/viml/parser/expressions.h b/src/nvim/viml/parser/expressions.h index 8ca3ceacb9..29903490bb 100644 --- a/src/nvim/viml/parser/expressions.h +++ b/src/nvim/viml/parser/expressions.h @@ -7,6 +7,7 @@ #include "nvim/types.h" #include "nvim/viml/parser/parser.h" +#include "nvim/eval/typval.h" // Defines whether to ignore case: // == kCCStrategyUseOption @@ -113,6 +114,11 @@ typedef struct { } err; ///< For kExprLexInvalid struct { + union { + float_T floating; + uvarnumber_T integer; + } val; ///< Number value. + uint8_t base; ///< Base: 2, 8, 10 or 16. bool is_float; ///< True if number is a floating-point. } num; ///< For kExprLexNumber } data; ///< Additional data, if needed. diff --git a/test/symbolic/klee/nvim/charset.c b/test/symbolic/klee/nvim/charset.c index a40488920e..409d7d443c 100644 --- a/test/symbolic/klee/nvim/charset.c +++ b/test/symbolic/klee/nvim/charset.c @@ -3,8 +3,173 @@ #include "nvim/ascii.h" #include "nvim/macros.h" #include "nvim/charset.h" +#include "nvim/eval/typval.h" +#include "nvim/vim.h" bool vim_isIDc(int c) { return ASCII_ISALNUM(c); } + +int hex2nr(int c) +{ + if ((c >= 'a') && (c <= 'f')) { + return c - 'a' + 10; + } + + if ((c >= 'A') && (c <= 'F')) { + return c - 'A' + 10; + } + return c - '0'; +} + +void vim_str2nr(const char_u *const start, int *const prep, int *const len, + const int what, varnumber_T *const nptr, + uvarnumber_T *const unptr, const int maxlen) +{ + const char_u *ptr = start; + int pre = 0; // default is decimal + bool negative = false; + uvarnumber_T un = 0; + + if (ptr[0] == '-') { + negative = true; + ptr++; + } + + // Recognize hex, octal and bin. + if ((ptr[0] == '0') && (ptr[1] != '8') && (ptr[1] != '9') + && (maxlen == 0 || maxlen > 1)) { + pre = ptr[1]; + + if ((what & STR2NR_HEX) + && ((pre == 'X') || (pre == 'x')) + && ascii_isxdigit(ptr[2]) + && (maxlen == 0 || maxlen > 2)) { + // hexadecimal + ptr += 2; + } else if ((what & STR2NR_BIN) + && ((pre == 'B') || (pre == 'b')) + && ascii_isbdigit(ptr[2]) + && (maxlen == 0 || maxlen > 2)) { + // binary + ptr += 2; + } else { + // decimal or octal, default is decimal + pre = 0; + + if (what & STR2NR_OCT) { + // Don't interpret "0", "08" or "0129" as octal. + for (int n = 1; ascii_isdigit(ptr[n]); ++n) { + if (ptr[n] > '7') { + // can't be octal + pre = 0; + break; + } + if (ptr[n] >= '0') { + // assume octal + pre = '0'; + } + if (n == maxlen) { + break; + } + } + } + } + } + + // Do the string-to-numeric conversion "manually" to avoid sscanf quirks. + int n = 1; + if ((pre == 'B') || (pre == 'b') || what == STR2NR_BIN + STR2NR_FORCE) { + // bin + if (pre != 0) { + n += 2; // skip over "0b" + } + while ('0' <= *ptr && *ptr <= '1') { + // avoid ubsan error for overflow + if (un < UVARNUMBER_MAX / 2) { + un = 2 * un + (uvarnumber_T)(*ptr - '0'); + } else { + un = UVARNUMBER_MAX; + } + ptr++; + if (n++ == maxlen) { + break; + } + } + } else if ((pre == '0') || what == STR2NR_OCT + STR2NR_FORCE) { + // octal + while ('0' <= *ptr && *ptr <= '7') { + // avoid ubsan error for overflow + if (un < UVARNUMBER_MAX / 8) { + un = 8 * un + (uvarnumber_T)(*ptr - '0'); + } else { + un = UVARNUMBER_MAX; + } + ptr++; + if (n++ == maxlen) { + break; + } + } + } else if ((pre == 'X') || (pre == 'x') + || what == STR2NR_HEX + STR2NR_FORCE) { + // hex + if (pre != 0) { + n += 2; // skip over "0x" + } + while (ascii_isxdigit(*ptr)) { + // avoid ubsan error for overflow + if (un < UVARNUMBER_MAX / 16) { + un = 16 * un + (uvarnumber_T)hex2nr(*ptr); + } else { + un = UVARNUMBER_MAX; + } + ptr++; + if (n++ == maxlen) { + break; + } + } + } else { + // decimal + while (ascii_isdigit(*ptr)) { + // avoid ubsan error for overflow + if (un < UVARNUMBER_MAX / 10) { + un = 10 * un + (uvarnumber_T)(*ptr - '0'); + } else { + un = UVARNUMBER_MAX; + } + ptr++; + if (n++ == maxlen) { + break; + } + } + } + + if (prep != NULL) { + *prep = pre; + } + + if (len != NULL) { + *len = (int)(ptr - start); + } + + if (nptr != NULL) { + if (negative) { // account for leading '-' for decimal numbers + // avoid ubsan error for overflow + if (un > VARNUMBER_MAX) { + *nptr = VARNUMBER_MIN; + } else { + *nptr = -(varnumber_T)un; + } + } else { + if (un > VARNUMBER_MAX) { + un = VARNUMBER_MAX; + } + *nptr = (varnumber_T)un; + } + } + + if (unptr != NULL) { + *unptr = un; + } +} diff --git a/test/symbolic/klee/viml_expressions_lexer.c b/test/symbolic/klee/viml_expressions_lexer.c index 67f3eb7faa..cddc1cb2f1 100644 --- a/test/symbolic/klee/viml_expressions_lexer.c +++ b/test/symbolic/klee/viml_expressions_lexer.c @@ -2,6 +2,7 @@ # include #else # include +# include #endif #include #include @@ -56,7 +57,7 @@ int main(const int argc, const char *const *const argv, .data = &input[shift], .size = sizeof(input) - shift, #else - .data = (const char *)&argv[1], + .data = (const char *)argv[1], .size = strlen(argv[1]), #endif .allocated = false, @@ -97,4 +98,7 @@ int main(const int argc, const char *const *const argv, } assert(allocated_memory == 0); assert(ever_allocated_memory == 0); +#ifndef USE_KLEE + fprintf(stderr, "tkn: %s\n", viml_pexpr_repr_token(&pstate, token, NULL)); +#endif } diff --git a/test/unit/viml/expressions/lexer_spec.lua b/test/unit/viml/expressions/lexer_spec.lua index bd8045632e..f180d8ceff 100644 --- a/test/unit/viml/expressions/lexer_spec.lua +++ b/test/unit/viml/expressions/lexer_spec.lua @@ -114,7 +114,11 @@ local function eltkn2lua(pstate, tkn) elseif ret.type == 'Number' then ret.data = { is_float = (not not tkn.data.num.is_float), + base = tonumber(tkn.data.num.base), } + ret.data.val = tonumber(tkn.data.num.is_float + and tkn.data.num.val.floating + or tkn.data.num.val.integer) elseif ret.type == 'Invalid' then ret.data = { error = ffi.string(tkn.data.err.msg) } end @@ -204,9 +208,20 @@ describe('Expressions lexer', function() singl_eltkn_test('Spacing', ' ') singl_eltkn_test('Spacing', '\t') singl_eltkn_test('Invalid', '\x01\x02\x03', {error='E15: Invalid control character present in input: %.*s'}) - singl_eltkn_test('Number', '0123', {is_float=false}) - singl_eltkn_test('Number', '0', {is_float=false}) - singl_eltkn_test('Number', '9', {is_float=false}) + singl_eltkn_test('Number', '0123', {is_float=false, base=8, val=83}) + singl_eltkn_test('Number', '01234567', {is_float=false, base=8, val=342391}) + singl_eltkn_test('Number', '012345678', {is_float=false, base=10, val=12345678}) + singl_eltkn_test('Number', '0x123', {is_float=false, base=16, val=291}) + singl_eltkn_test('Number', '0x56FF', {is_float=false, base=16, val=22271}) + singl_eltkn_test('Number', '0xabcdef', {is_float=false, base=16, val=11259375}) + singl_eltkn_test('Number', '0xABCDEF', {is_float=false, base=16, val=11259375}) + singl_eltkn_test('Number', '0x0', {is_float=false, base=16, val=0}) + singl_eltkn_test('Number', '00', {is_float=false, base=8, val=0}) + singl_eltkn_test('Number', '0b0', {is_float=false, base=2, val=0}) + singl_eltkn_test('Number', '0b010111', {is_float=false, base=2, val=23}) + singl_eltkn_test('Number', '0b100111', {is_float=false, base=2, val=39}) + singl_eltkn_test('Number', '0', {is_float=false, base=10, val=0}) + singl_eltkn_test('Number', '9', {is_float=false, base=10, val=9}) singl_eltkn_test('Env', '$abc') singl_eltkn_test('Env', '$') singl_eltkn_test('PlainIdentifier', 'test', {autoload=false, scope=0}) @@ -262,17 +277,21 @@ describe('Expressions lexer', function() singl_eltkn_test('Invalid', '~', {error='E15: Unidentified character: %.*s'}) simple_test({{data=nil, size=0}}, 'EOC', 0, {error='start.col >= #pstr'}) simple_test({''}, 'EOC', 0, {error='start.col >= #pstr'}) - simple_test({'2.'}, 'Number', 1, {data={is_float=false}, str='2'}) - simple_test({'2.x'}, 'Number', 1, {data={is_float=false}, str='2'}) - simple_test({'2.2.'}, 'Number', 1, {data={is_float=false}, str='2'}) - simple_test({'2.0x'}, 'Number', 1, {data={is_float=false}, str='2'}) - simple_test({'2.0e'}, 'Number', 1, {data={is_float=false}, str='2'}) - simple_test({'2.0e+'}, 'Number', 1, {data={is_float=false}, str='2'}) - simple_test({'2.0e-'}, 'Number', 1, {data={is_float=false}, str='2'}) - simple_test({'2.0e+x'}, 'Number', 1, {data={is_float=false}, str='2'}) - simple_test({'2.0e-x'}, 'Number', 1, {data={is_float=false}, str='2'}) - simple_test({'2.0e+1a'}, 'Number', 1, {data={is_float=false}, str='2'}) - simple_test({'2.0e-1a'}, 'Number', 1, {data={is_float=false}, str='2'}) + simple_test({'2.'}, 'Number', 1, {data={is_float=false, base=10, val=2}, str='2'}) + simple_test({'2e5'}, 'Number', 1, {data={is_float=false, base=10, val=2}, str='2'}) + simple_test({'2.x'}, 'Number', 1, {data={is_float=false, base=10, val=2}, str='2'}) + simple_test({'2.2.'}, 'Number', 1, {data={is_float=false, base=10, val=2}, str='2'}) + simple_test({'2.0x'}, 'Number', 1, {data={is_float=false, base=10, val=2}, str='2'}) + simple_test({'2.0e'}, 'Number', 1, {data={is_float=false, base=10, val=2}, str='2'}) + simple_test({'2.0e+'}, 'Number', 1, {data={is_float=false, base=10, val=2}, str='2'}) + simple_test({'2.0e-'}, 'Number', 1, {data={is_float=false, base=10, val=2}, str='2'}) + simple_test({'2.0e+x'}, 'Number', 1, {data={is_float=false, base=10, val=2}, str='2'}) + simple_test({'2.0e-x'}, 'Number', 1, {data={is_float=false, base=10, val=2}, str='2'}) + simple_test({'2.0e+1a'}, 'Number', 1, {data={is_float=false, base=10, val=2}, str='2'}) + simple_test({'2.0e-1a'}, 'Number', 1, {data={is_float=false, base=10, val=2}, str='2'}) + simple_test({'0b102'}, 'Number', 4, {data={is_float=false, base=2, val=2}, str='0b10'}) + simple_test({'10F'}, 'Number', 2, {data={is_float=false, base=10, val=10}, str='10'}) + simple_test({'0x0123456789ABCDEFG'}, 'Number', 18, {data={is_float=false, base=16, val=81985529216486895}, str='0x0123456789ABCDEF'}) end local function regular_scope_tests() @@ -304,10 +323,10 @@ describe('Expressions lexer', function() end local function regular_number_tests() - simple_test({'2.0'}, 'Number', 1, {data={is_float=false}, str='2'}) - simple_test({'2.0e5'}, 'Number', 1, {data={is_float=false}, str='2'}) - simple_test({'2.0e+5'}, 'Number', 1, {data={is_float=false}, str='2'}) - simple_test({'2.0e-5'}, 'Number', 1, {data={is_float=false}, str='2'}) + simple_test({'2.0'}, 'Number', 1, {data={is_float=false, base=10, val=2}, str='2'}) + simple_test({'2.0e5'}, 'Number', 1, {data={is_float=false, base=10, val=2}, str='2'}) + simple_test({'2.0e+5'}, 'Number', 1, {data={is_float=false, base=10, val=2}, str='2'}) + simple_test({'2.0e-5'}, 'Number', 1, {data={is_float=false, base=10, val=2}, str='2'}) end local function regular_eoc_tests() @@ -352,10 +371,20 @@ describe('Expressions lexer', function() regular_scope_tests() regular_is_tests() - simple_test({'2.0'}, 'Number', 3, {data={is_float=true}, str='2.0'}) - simple_test({'2.0e5'}, 'Number', 5, {data={is_float=true}, str='2.0e5'}) - simple_test({'2.0e+5'}, 'Number', 6, {data={is_float=true}, str='2.0e+5'}) - simple_test({'2.0e-5'}, 'Number', 6, {data={is_float=true}, str='2.0e-5'}) + simple_test({'2.2'}, 'Number', 3, {data={is_float=true, base=10, val=2.2}, str='2.2'}) + simple_test({'2.0e5'}, 'Number', 5, {data={is_float=true, base=10, val=2e5}, str='2.0e5'}) + simple_test({'2.0e+5'}, 'Number', 6, {data={is_float=true, base=10, val=2e5}, str='2.0e+5'}) + simple_test({'2.0e-5'}, 'Number', 6, {data={is_float=true, base=10, val=2e-5}, str='2.0e-5'}) + simple_test({'2.500000e-5'}, 'Number', 11, {data={is_float=true, base=10, val=2.5e-5}, str='2.500000e-5'}) + simple_test({'2.5555e2'}, 'Number', 8, {data={is_float=true, base=10, val=2.5555e2}, str='2.5555e2'}) + simple_test({'2.5555e+2'}, 'Number', 9, {data={is_float=true, base=10, val=2.5555e2}, str='2.5555e+2'}) + simple_test({'2.5555e-2'}, 'Number', 9, {data={is_float=true, base=10, val=2.5555e-2}, str='2.5555e-2'}) + simple_test({{data='2.5e-5', size=3}}, + 'Number', 3, {data={is_float=true, base=10, val=2.5}, str='2.5'}) + simple_test({{data='2.5e5', size=4}}, + 'Number', 1, {data={is_float=false, base=10, val=2}, str='2'}) + simple_test({{data='2.5e-50', size=6}}, + 'Number', 6, {data={is_float=true, base=10, val=2.5e-5}, str='2.5e-5'}) end) itp('treats `is` as an identifier', function() flags = tonumber(lib.kELFlagIsNotCmp) -- cgit From 21a5ce033c5a853bed3204ea9f0f7a3cfc1d164f Mon Sep 17 00:00:00 2001 From: ZyX Date: Tue, 3 Oct 2017 01:30:02 +0300 Subject: viml/parser/expressions: Add support for the dot operator and numbers --- src/nvim/viml/parser/expressions.c | 108 ++++++++++- src/nvim/viml/parser/expressions.h | 23 ++- test/unit/viml/expressions/parser_spec.lua | 294 +++++++++++++++++++++++++++++ 3 files changed, 416 insertions(+), 9 deletions(-) diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index 5d892fb8f8..4babf4312c 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -915,7 +915,8 @@ static inline void viml_pexpr_debug_print_token( // // NVimUnaryPlus -> NVimUnaryOperator // NVimBinaryPlus -> NVimBinaryOperator -// NVimConcatOrSubscript -> NVimBinaryOperator +// NVimConcat -> NVimBinaryOperator +// NVimConcatOrSubscript -> NVimConcat // // NVimRegister -> SpecialChar // NVimNumber -> Number @@ -971,6 +972,7 @@ static const ExprOpLvl node_type_to_op_lvl[] = { [kExprNodeUnknownFigure] = kEOpLvlParens, [kExprNodeLambda] = kEOpLvlParens, [kExprNodeDictLiteral] = kEOpLvlParens, + [kExprNodeListLiteral] = kEOpLvlParens, [kExprNodeArrow] = kEOpLvlArrow, @@ -985,17 +987,21 @@ static const ExprOpLvl node_type_to_op_lvl[] = { [kExprNodeComparison] = kEOpLvlComparison, [kExprNodeBinaryPlus] = kEOpLvlAddition, + [kExprNodeConcat] = kEOpLvlAddition, [kExprNodeUnaryPlus] = kEOpLvlUnary, + [kExprNodeConcatOrSubscript] = kEOpLvlSubscript, [kExprNodeSubscript] = kEOpLvlSubscript, [kExprNodeCurlyBracesIdentifier] = kEOpLvlComplexIdentifier, [kExprNodeComplexIdentifier] = kEOpLvlValue, [kExprNodePlainIdentifier] = kEOpLvlValue, + [kExprNodePlainKey] = kEOpLvlValue, [kExprNodeRegister] = kEOpLvlValue, - [kExprNodeListLiteral] = kEOpLvlValue, + [kExprNodeInteger] = kEOpLvlValue, + [kExprNodeFloat] = kEOpLvlValue, }; static const ExprOpAssociativity node_type_to_op_ass[] = { @@ -1008,6 +1014,7 @@ static const ExprOpAssociativity node_type_to_op_ass[] = { [kExprNodeUnknownFigure] = kEOpAssLeft, [kExprNodeLambda] = kEOpAssNo, [kExprNodeDictLiteral] = kEOpAssNo, + [kExprNodeListLiteral] = kEOpAssNo, // Does not really matter. [kExprNodeArrow] = kEOpAssNo, @@ -1030,17 +1037,21 @@ static const ExprOpAssociativity node_type_to_op_ass[] = { [kExprNodeComparison] = kEOpAssRight, [kExprNodeBinaryPlus] = kEOpAssLeft, + [kExprNodeConcat] = kEOpAssLeft, [kExprNodeUnaryPlus] = kEOpAssNo, + [kExprNodeConcatOrSubscript] = kEOpAssLeft, [kExprNodeSubscript] = kEOpAssLeft, [kExprNodeCurlyBracesIdentifier] = kEOpAssLeft, [kExprNodeComplexIdentifier] = kEOpAssLeft, [kExprNodePlainIdentifier] = kEOpAssNo, + [kExprNodePlainKey] = kEOpAssNo, [kExprNodeRegister] = kEOpAssNo, - [kExprNodeListLiteral] = kEOpAssNo, + [kExprNodeInteger] = kEOpAssNo, + [kExprNodeFloat] = kEOpAssNo, }; /// Get AST node priority level @@ -1420,10 +1431,20 @@ ExprAST viml_pexpr_parse(ParserState *const pstate, const int flags) [kENodeArgument] = kELFlagIsNotCmp, [kENodeArgumentSeparator] = kELFlagForbidScope, }; - // FIXME Determine when (not) to allow floating-point numbers. + const bool is_concat_or_subscript = ( + want_node == kENodeValue + && kv_size(ast_stack) > 1 + && (*kv_Z(ast_stack, 1))->type == kExprNodeConcatOrSubscript); const int lexer_additional_flags = ( kELFlagPeek - | ((flags & kExprFlagsDisallowEOC) ? kELFlagForbidEOC : 0)); + | ((flags & kExprFlagsDisallowEOC) ? kELFlagForbidEOC : 0) + | ((want_node == kENodeValue + && (kv_size(ast_stack) == 1 + || ((*kv_Z(ast_stack, 1))->type != kExprNodeConcat + && ((*kv_Z(ast_stack, 1))->type + != kExprNodeConcatOrSubscript)))) + ? kELFlagAllowFloat + : 0)); LexExprToken cur_token = viml_pexpr_next_token( pstate, want_node_to_lexer_flags[want_node] | lexer_additional_flags); if (cur_token.type == kExprLexEOC) { @@ -1456,11 +1477,42 @@ viml_pexpr_parse_process_token: ExprASTNode *cur_node = NULL; assert((want_node == kENodeValue || want_node == kENodeArgument) == (*top_node_p == NULL)); + // Note: in Vim whether expression "cond?d.a:2" is valid depends both on + // "cond" and whether "d" is a dictionary: expression is valid if condition + // is true and "d" is a dictionary (with "a" key or it will complain about + // missing one, but this is not relevant); if any of the requirements is + // broken then this thing is parsed as "d . a:2" yielding missing colon + // error. This parser does not allow such ambiguity, especially because it + // simply can’t: whether "d" is a dictionary is not known at the parsing + // time. + // + // Here example will always contain a concat with "a:2" sucking colon, + // making expression invalid both because there is no longer a spare colon + // for ternary and because concatenating dictionary with anything is not + // valid. There are more cases when this will make a difference though. + const bool node_is_key = ( + is_concat_or_subscript + && (cur_token.type == kExprLexPlainIdentifier + ? (!cur_token.data.var.autoload + && cur_token.data.var.scope == 0) + : (cur_token.type == kExprLexNumber)) + && prev_token.type != kExprLexSpacing); + if (is_concat_or_subscript && !node_is_key) { + // Note: in Vim "d. a" (this is the reason behind `prev_token.type != + // kExprLexSpacing` part of the condition) as well as any other "d.{expr}" + // where "{expr}" does not look like a key is invalid whenever "d" happens + // to be a dictionary. Since parser has no idea whether preceding + // expression is actually a dictionary it can’t outright reject anything, + // so it turns kExprNodeConcatOrSubscript into kExprNodeConcat instead, + // which will yield different errors then Vim does in a number of + // circumstances, and in any case runtime and not parse time errors. + (*kv_Z(ast_stack, 1))->type = kExprNodeConcat; + } if ((want_node == kENodeArgumentSeparator && tok_type != kExprLexComma && tok_type != kExprLexArrow) || (want_node == kENodeArgument - && !(tok_type == kExprLexPlainIdentifier + && !(cur_token.type == kExprLexPlainIdentifier && cur_token.data.var.scope == 0 && !cur_token.data.var.autoload) && tok_type != kExprLexArrow)) { @@ -1844,7 +1896,10 @@ viml_pexpr_parse_figure_brace_closing_error: want_node = (want_node == kENodeArgument ? kENodeArgumentSeparator : kENodeOperator); - NEW_NODE_WITH_CUR_POS(cur_node, kExprNodePlainIdentifier); + NEW_NODE_WITH_CUR_POS(cur_node, + (node_is_key + ? kExprNodePlainKey + : kExprNodePlainIdentifier)); cur_node->data.var.scope = cur_token.data.var.scope; const size_t scope_shift = (cur_token.data.var.scope == 0 ? 0 @@ -1854,6 +1909,7 @@ viml_pexpr_parse_figure_brace_closing_error: cur_node->data.var.ident_len = cur_token.len - scope_shift; *top_node_p = cur_node; if (scope_shift) { + assert(!node_is_key); viml_parser_highlight(pstate, cur_token.start, 1, HL(IdentifierScope)); viml_parser_highlight(pstate, shifted_pos(cur_token.start, 1), 1, @@ -1863,7 +1919,9 @@ viml_pexpr_parse_figure_brace_closing_error: viml_parser_highlight(pstate, shifted_pos(cur_token.start, scope_shift), cur_token.len - scope_shift, - HL(Identifier)); + (node_is_key + ? HL(IdentifierKey) + : HL(Identifier))); } } else { if (cur_token.data.var.scope == 0) { @@ -1882,6 +1940,40 @@ viml_pexpr_parse_figure_brace_closing_error: } break; } + case kExprLexNumber: { + if (want_node != kENodeValue) { + OP_MISSING; + } + if (node_is_key) { + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodePlainKey); + cur_node->data.var.ident = pline.data + cur_token.start.col; + cur_node->data.var.ident_len = cur_token.len; + HL_CUR_TOKEN(IdentifierKey); + } else if (cur_token.data.num.is_float) { + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeFloat); + cur_node->data.flt.value = cur_token.data.num.val.floating; + HL_CUR_TOKEN(Float); + } else { + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeInteger); + cur_node->data.num.value = cur_token.data.num.val.integer; + HL_CUR_TOKEN(Number); + } + want_node = kENodeOperator; + *top_node_p = cur_node; + break; + } + case kExprLexDot: { + ADD_VALUE_IF_MISSING(_("E15: Unexpected dot: %.*s")); + if (prev_token.type == kExprLexSpacing) { + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeConcat); + HL_CUR_TOKEN(Concat); + } else { + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeConcatOrSubscript); + HL_CUR_TOKEN(ConcatOrSubscript); + } + ADD_OP_NODE(cur_node); + break; + } case kExprLexParenthesis: { if (cur_token.data.brc.closing) { if (want_node == kENodeValue) { diff --git a/src/nvim/viml/parser/expressions.h b/src/nvim/viml/parser/expressions.h index 29903490bb..0d496c87ba 100644 --- a/src/nvim/viml/parser/expressions.h +++ b/src/nvim/viml/parser/expressions.h @@ -166,6 +166,8 @@ typedef enum { /// Looks like "string", "g:Foo", etc: consists from a single /// kExprLexPlainIdentifier token. kExprNodePlainIdentifier = 'i', + /// Plain dictionary key, for use with kExprNodeConcatOrSubscript + kExprNodePlainKey = 'k', /// Complex identifier: variable/function name with curly braces kExprNodeComplexIdentifier = 'I', /// Figure brace expression which is not yet known @@ -180,6 +182,19 @@ typedef enum { kExprNodeColon = ':', ///< Colon “operator”. kExprNodeArrow = '>', ///< Arrow “operator”. kExprNodeComparison = '=', ///< Various comparison operators. + /// Concat operator + /// + /// To be only used in cases when it is known for sure it is not a subscript. + kExprNodeConcat = '.', + /// Concat or subscript operator + /// + /// For cases when it is not obvious whether expression is a concat or + /// a subscript. May only have either number or plain identifier as the second + /// child. To make it easier to avoid curly braces in place of + /// kExprNodePlainIdentifier node kExprNodePlainKey is used. + kExprNodeConcatOrSubscript = 'S', + kExprNodeInteger = '0', ///< Integral number. + kExprNodeFloat = '1', ///< Floating-point number. } ExprASTNodeType; typedef struct expr_ast_node ExprASTNode; @@ -219,7 +234,7 @@ struct expr_ast_node { /// Points to inside parser reader state. const char *ident; size_t ident_len; ///< Actual identifier length. - } var; ///< For kExprNodePlainIdentifier. + } var; ///< For kExprNodePlainIdentifier and kExprNodePlainKey. struct { bool got_colon; ///< True if colon was seen. } ter; ///< For kExprNodeTernaryValue. @@ -228,6 +243,12 @@ struct expr_ast_node { ExprCaseCompareStrategy ccs; ///< Case comparison strategy. bool inv; ///< True if comparison is to be inverted. } cmp; ///< For kExprNodeComparison. + struct { + uvarnumber_T value; + } num; ///< For kExprNodeInteger. + struct { + float_T value; + } flt; ///< For kExprNodeFloat. } data; }; diff --git a/test/unit/viml/expressions/parser_spec.lua b/test/unit/viml/expressions/parser_spec.lua index efa88455e4..8d96c29db7 100644 --- a/test/unit/viml/expressions/parser_spec.lua +++ b/test/unit/viml/expressions/parser_spec.lua @@ -77,6 +77,7 @@ make_enum_conv_tab(lib, { 'kExprNodeNested', 'kExprNodeCall', 'kExprNodePlainIdentifier', + 'kExprNodePlainKey', 'kExprNodeComplexIdentifier', 'kExprNodeUnknownFigure', 'kExprNodeLambda', @@ -86,6 +87,10 @@ make_enum_conv_tab(lib, { 'kExprNodeColon', 'kExprNodeArrow', 'kExprNodeComparison', + 'kExprNodeConcat', + 'kExprNodeConcatOrSubscript', + 'kExprNodeInteger', + 'kExprNodeFloat', }, 'kExprNode', function(ret) east_node_type_tab = ret end) local function conv_east_node_type(typ) @@ -118,6 +123,9 @@ local function eastnode2lua(pstate, eastnode, checked_nodes) typ = typ .. ('(scope=%s,ident=%s)'):format( tostring(intchar2lua(eastnode.data.var.scope)), ffi.string(eastnode.data.var.ident, eastnode.data.var.ident_len)) + elseif typ == 'PlainKey' then + typ = typ .. ('(key=%s)'):format( + ffi.string(eastnode.data.var.ident, eastnode.data.var.ident_len)) elseif (typ == 'UnknownFigure' or typ == 'DictLiteral' or typ == 'CurlyBracesIdentifier' or typ == 'Lambda') then typ = typ .. ('(%s)'):format( @@ -128,6 +136,10 @@ local function eastnode2lua(pstate, eastnode, checked_nodes) typ = typ .. ('(type=%s,inv=%u,ccs=%s)'):format( conv_cmp_type(eastnode.data.cmp.type), eastnode.data.cmp.inv and 1 or 0, conv_ccs(eastnode.data.cmp.ccs)) + elseif typ == 'Integer' then + typ = typ .. ('(val=%u)'):format(tonumber(eastnode.data.num.value)) + elseif typ == 'Float' then + typ = typ .. ('(val=%e)'):format(tonumber(eastnode.data.flt.value)) end ret_str = typ .. ':' .. ret_str local can_simplify = true @@ -190,6 +202,8 @@ end) describe('Expressions parser', function() local function check_parsing(str, flags, exp_ast, exp_highlighting_fs) + flags = flags or 0 + local pstate = new_pstate({str}) local east = lib.viml_pexpr_parse(pstate, flags) local ast = east2lua(pstate, east) @@ -3649,4 +3663,284 @@ describe('Expressions parser', function() hl('Identifier', 'b', 1), }) end) + itp('works with concat/subscript', function() + check_parsing('.', 0, { + -- 0 + ast = { + { + 'ConcatOrSubscript:0:0:.', + children = { + 'Missing:0:0:', + }, + }, + }, + err = { + arg = '.', + msg = 'E15: Unexpected dot: %.*s', + }, + }, { + hl('InvalidConcatOrSubscript', '.'), + }) + + check_parsing('a.', 0, { + -- 01 + ast = { + { + 'ConcatOrSubscript:0:1:.', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + }, + }, + }, + err = { + arg = '', + msg = 'E15: Expected value, got EOC: %.*s', + }, + }, { + hl('Identifier', 'a'), + hl('ConcatOrSubscript', '.'), + }) + + check_parsing('a.b', 0, { + -- 012 + ast = { + { + 'ConcatOrSubscript:0:1:.', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainKey(key=b):0:2:b', + }, + }, + }, + }, { + hl('Identifier', 'a'), + hl('ConcatOrSubscript', '.'), + hl('IdentifierKey', 'b'), + }) + + check_parsing('1.2', 0, { + -- 012 + ast = { + 'Float(val=1.200000e+00):0:0:1.2', + }, + }, { + hl('Float', '1.2'), + }) + + check_parsing('1.2 + 1.3e-5', 0, { + -- 012345678901 + -- 0 1 + ast = { + { + 'BinaryPlus:0:3: +', + children = { + 'Float(val=1.200000e+00):0:0:1.2', + 'Float(val=1.300000e-05):0:5: 1.3e-5', + }, + }, + }, + }, { + hl('Float', '1.2'), + hl('BinaryPlus', '+', 1), + hl('Float', '1.3e-5', 1), + }) + + check_parsing('a . 1.2 + 1.3e-5', 0, { + -- 0123456789012345 + -- 0 1 + ast = { + { + 'BinaryPlus:0:7: +', + children = { + { + 'Concat:0:1: .', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'ConcatOrSubscript:0:5:.', + children = { + 'Integer(val=1):0:3: 1', + 'PlainKey(key=2):0:6:2', + }, + }, + }, + }, + 'Float(val=1.300000e-05):0:9: 1.3e-5', + }, + }, + }, + }, { + hl('Identifier', 'a'), + hl('Concat', '.', 1), + hl('Number', '1', 1), + hl('ConcatOrSubscript', '.'), + hl('IdentifierKey', '2'), + hl('BinaryPlus', '+', 1), + hl('Float', '1.3e-5', 1), + }) + + check_parsing('1.3e-5 + 1.2 . a', 0, { + -- 0123456789012345 + -- 0 1 + ast = { + { + 'Concat:0:12: .', + children = { + { + 'BinaryPlus:0:6: +', + children = { + 'Float(val=1.300000e-05):0:0:1.3e-5', + 'Float(val=1.200000e+00):0:8: 1.2', + }, + }, + 'PlainIdentifier(scope=0,ident=a):0:14: a', + }, + }, + }, + }, { + hl('Float', '1.3e-5'), + hl('BinaryPlus', '+', 1), + hl('Float', '1.2', 1), + hl('Concat', '.', 1), + hl('Identifier', 'a', 1), + }) + + check_parsing('1.3e-5 + a . 1.2', 0, { + -- 0123456789012345 + -- 0 1 + ast = { + { + 'Concat:0:10: .', + children = { + { + 'BinaryPlus:0:6: +', + children = { + 'Float(val=1.300000e-05):0:0:1.3e-5', + 'PlainIdentifier(scope=0,ident=a):0:8: a', + }, + }, + { + 'ConcatOrSubscript:0:14:.', + children = { + 'Integer(val=1):0:12: 1', + 'PlainKey(key=2):0:15:2', + }, + }, + }, + }, + }, + }, { + hl('Float', '1.3e-5'), + hl('BinaryPlus', '+', 1), + hl('Identifier', 'a', 1), + hl('Concat', '.', 1), + hl('Number', '1', 1), + hl('ConcatOrSubscript', '.'), + hl('IdentifierKey', '2'), + }) + + check_parsing('1.2.3', 0, { + -- 01234 + ast = { + { + 'ConcatOrSubscript:0:3:.', + children = { + { + 'ConcatOrSubscript:0:1:.', + children = { + 'Integer(val=1):0:0:1', + 'PlainKey(key=2):0:2:2', + }, + }, + 'PlainKey(key=3):0:4:3', + }, + }, + }, + }, { + hl('Number', '1'), + hl('ConcatOrSubscript', '.'), + hl('IdentifierKey', '2'), + hl('ConcatOrSubscript', '.'), + hl('IdentifierKey', '3'), + }) + + check_parsing('a.1.2', 0, { + -- 01234 + ast = { + { + 'ConcatOrSubscript:0:3:.', + children = { + { + 'ConcatOrSubscript:0:1:.', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainKey(key=1):0:2:1', + }, + }, + 'PlainKey(key=2):0:4:2', + }, + }, + }, + }, { + hl('Identifier', 'a'), + hl('ConcatOrSubscript', '.'), + hl('IdentifierKey', '1'), + hl('ConcatOrSubscript', '.'), + hl('IdentifierKey', '2'), + }) + + check_parsing('a . 1.2', 0, { + -- 0123456 + ast = { + { + 'Concat:0:1: .', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'ConcatOrSubscript:0:5:.', + children = { + 'Integer(val=1):0:3: 1', + 'PlainKey(key=2):0:6:2', + }, + }, + }, + }, + }, + }, { + hl('Identifier', 'a'), + hl('Concat', '.', 1), + hl('Number', '1', 1), + hl('ConcatOrSubscript', '.'), + hl('IdentifierKey', '2'), + }) + + check_parsing('+a . +b', 0, { + -- 0123456 + ast = { + { + 'Concat:0:2: .', + children = { + { + 'UnaryPlus:0:0:+', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + }, + }, + { + 'UnaryPlus:0:4: +', + children = { + 'PlainIdentifier(scope=0,ident=b):0:6:b', + }, + }, + }, + }, + }, + }, { + hl('UnaryPlus', '+'), + hl('Identifier', 'a'), + hl('Concat', '.', 1), + hl('UnaryPlus', '+', 1), + hl('Identifier', 'b'), + }) + end) end) -- cgit From e45e519495832e3d1d0fde1e32723d4140c5fc65 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 8 Oct 2017 01:19:58 +0300 Subject: viml/parser/expressions: Error out on multiple colons in a row --- src/nvim/viml/parser/expressions.c | 4 +- test/unit/viml/expressions/parser_spec.lua | 73 ++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 1 deletion(-) diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index 4babf4312c..fffae8833d 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -1691,8 +1691,10 @@ viml_pexpr_parse_invalid_comma: SELECT_FIGURE_BRACE_TYPE(*eastnode_p, DictLiteral, Dict); break; } else if (eastnode_type == kExprNodeDictLiteral - || eastnode_type == kExprNodeComma) { + || eastnode_type == kExprNodeSubscript) { break; + } else if (eastnode_type == kExprNodeColon) { + goto viml_pexpr_parse_invalid_colon; } else if (eastnode_lvl >= kEOpLvlTernaryValue) { // Do nothing } else if (eastnode_lvl > kEOpLvlComma) { diff --git a/test/unit/viml/expressions/parser_spec.lua b/test/unit/viml/expressions/parser_spec.lua index 8d96c29db7..46e3328184 100644 --- a/test/unit/viml/expressions/parser_spec.lua +++ b/test/unit/viml/expressions/parser_spec.lua @@ -2700,6 +2700,42 @@ describe('Expressions parser', function() hl('Identifier', 'b', 1), hl('Curly', '}'), }) + check_parsing('{a : b : c}', 0, { + -- 01234567890 + -- 0 1 + ast = { + { + 'DictLiteral(-di):0:0:{', + children = { + { + 'Colon:0:2: :', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + { + 'Colon:0:6: :', + children = { + 'PlainIdentifier(scope=0,ident=b):0:4: b', + 'PlainIdentifier(scope=0,ident=c):0:8: c', + }, + }, + }, + }, + }, + }, + }, + err = { + arg = ': c}', + msg = 'E15: Colon outside of dictionary or ternary operator: %.*s', + }, + }, { + hl('Dict', '{'), + hl('Identifier', 'a'), + hl('Colon', ':', 1), + hl('Identifier', 'b', 1), + hl('InvalidColon', ':', 1), + hl('Identifier', 'c', 1), + hl('Dict', '}'), + }) end) itp('works with ternary operator', function() check_parsing('a ? b : c', 0, { @@ -3348,6 +3384,43 @@ describe('Expressions parser', function() hl('TernaryColon', ':'), hl('Identifier', 'h'), }) + check_parsing('a ? b : c : d', 0, { + -- 0123456789012 + -- 0 1 + ast = { + { + 'Colon:0:9: :', + children = { + { + 'Ternary:0:1: ?', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'TernaryValue:0:5: :', + children = { + 'PlainIdentifier(scope=0,ident=b):0:3: b', + 'PlainIdentifier(scope=0,ident=c):0:7: c', + }, + }, + }, + }, + 'PlainIdentifier(scope=0,ident=d):0:11: d', + }, + }, + }, + err = { + arg = ': d', + msg = 'E15: Colon outside of dictionary or ternary operator: %.*s', + }, + }, { + hl('Identifier', 'a'), + hl('Ternary', '?', 1), + hl('Identifier', 'b', 1), + hl('TernaryColon', ':', 1), + hl('Identifier', 'c', 1), + hl('InvalidColon', ':', 1), + hl('Identifier', 'd', 1), + }) end) itp('works with comparison operators', function() check_parsing('a == b', 0, { -- cgit From bd3a4166b25a64dbe406be09b3140955cf694477 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 8 Oct 2017 02:17:05 +0300 Subject: viml/parser/expressions: Add support for subscript and list literals --- src/nvim/viml/parser/expressions.c | 164 +++++++-- test/unit/viml/expressions/parser_spec.lua | 533 +++++++++++++++++++++++++++++ 2 files changed, 675 insertions(+), 22 deletions(-) diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index fffae8833d..0613cc66d5 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -905,6 +905,10 @@ static inline void viml_pexpr_debug_print_token( // NVimDict -> Delimiter // NVimCurly -> Delimiter // +// NVimList -> Delimiter +// NVimSubscript -> Delimiter +// NVimSubscriptColon -> NVimSubscript +// // NVimIdentifier -> Identifier // NVimIdentifierScope -> NVimIdentifier // NVimIdentifierScopeDelimiter -> NVimIdentifier @@ -945,6 +949,9 @@ static inline void viml_pexpr_debug_print_token( // NVimInvalidNumber -> NVimInvalidValue // NVimInvalidFloat -> NVimInvalidValue // NVimInvalidIdentifierKey -> NVimInvalidIdentifier +// NVimInvalidList -> NVimInvalidDelimiter +// NVimInvalidSubscript -> NVimInvalidDelimiter +// NVimInvalidSubscriptColon -> NVimInvalidSubscript /// Allocate a new node and set some of the values /// @@ -965,9 +972,10 @@ static const ExprOpLvl node_type_to_op_lvl[] = { [kExprNodeOpMissing] = kEOpLvlMultiplication, [kExprNodeNested] = kEOpLvlParens, - // Note: it is kEOpLvlSubscript for “binary operator” itself, but + // Note: below nodes are kEOpLvlSubscript for “binary operator” itself, but // kEOpLvlParens when it comes to inside the parenthesis. [kExprNodeCall] = kEOpLvlParens, + [kExprNodeSubscript] = kEOpLvlParens, [kExprNodeUnknownFigure] = kEOpLvlParens, [kExprNodeLambda] = kEOpLvlParens, @@ -992,7 +1000,6 @@ static const ExprOpLvl node_type_to_op_lvl[] = { [kExprNodeUnaryPlus] = kEOpLvlUnary, [kExprNodeConcatOrSubscript] = kEOpLvlSubscript, - [kExprNodeSubscript] = kEOpLvlSubscript, [kExprNodeCurlyBracesIdentifier] = kEOpLvlComplexIdentifier, @@ -1010,6 +1017,7 @@ static const ExprOpAssociativity node_type_to_op_ass[] = { [kExprNodeNested] = kEOpAssNo, [kExprNodeCall] = kEOpAssNo, + [kExprNodeSubscript] = kEOpAssNo, [kExprNodeUnknownFigure] = kEOpAssLeft, [kExprNodeLambda] = kEOpAssNo, @@ -1042,7 +1050,6 @@ static const ExprOpAssociativity node_type_to_op_ass[] = { [kExprNodeUnaryPlus] = kEOpAssNo, [kExprNodeConcatOrSubscript] = kEOpAssLeft, - [kExprNodeSubscript] = kEOpAssLeft, [kExprNodeCurlyBracesIdentifier] = kEOpAssLeft, @@ -1105,12 +1112,14 @@ static bool viml_pexpr_handle_bop(const ParserState *const pstate, ExprOpLvl top_node_lvl; ExprOpAssociativity top_node_ass; assert(kv_size(*ast_stack)); - const ExprOpLvl bop_node_lvl = (bop_node->type == kExprNodeCall + const ExprOpLvl bop_node_lvl = ((bop_node->type == kExprNodeCall + || bop_node->type == kExprNodeSubscript) ? kEOpLvlSubscript : node_lvl(*bop_node)); #ifndef NDEBUG const ExprOpAssociativity bop_node_ass = ( - bop_node->type == kExprNodeCall + (bop_node->type == kExprNodeCall + || bop_node->type == kExprNodeSubscript) ? kEOpAssLeft : node_ass(*bop_node)); #endif @@ -1214,8 +1223,8 @@ static inline ParserPosition shifted_pos(const ParserPosition pos, /// @param cur_token Token to set position from. #define POS_FROM_TOKEN(cur_node, cur_token) \ do { \ - cur_node->start = cur_token.start; \ - cur_node->len = cur_token.len; \ + (cur_node)->start = cur_token.start; \ + (cur_node)->len = cur_token.len; \ } while (0) /// Allocate new node and set its position from the current token @@ -1226,11 +1235,11 @@ static inline ParserPosition shifted_pos(const ParserPosition pos, /// @param typ Node type. #define NEW_NODE_WITH_CUR_POS(cur_node, typ) \ do { \ - cur_node = NEW_NODE(typ); \ - POS_FROM_TOKEN(cur_node, cur_token); \ + (cur_node) = NEW_NODE(typ); \ + POS_FROM_TOKEN((cur_node), cur_token); \ if (prev_token.type == kExprLexSpacing) { \ - cur_node->start = prev_token.start; \ - cur_node->len += prev_token.len; \ + (cur_node)->start = prev_token.start; \ + (cur_node)->len += prev_token.len; \ } \ } while (0) @@ -1280,9 +1289,8 @@ static inline ParserPosition shifted_pos(const ParserPosition pos, do { \ if (want_node == kENodeValue) { \ ERROR_FROM_TOKEN_AND_MSG(cur_token, (msg)); \ - NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeMissing); \ - cur_node->len = 0; \ - *top_node_p = cur_node; \ + NEW_NODE_WITH_CUR_POS((*top_node_p), kExprNodeMissing); \ + (*top_node_p)->len = 0; \ want_node = kENodeOperator; \ } \ } while (0) @@ -1658,13 +1666,14 @@ viml_pexpr_parse_invalid_comma: HL_CUR_TOKEN(Comma); break; } +#define EXP_VAL_COLON "E15: Expected value, got colon: %.*s" case kExprLexColon: { - ADD_VALUE_IF_MISSING(_("E15: Expected value, got colon: %.*s")); if (kv_size(ast_stack) < 2) { goto viml_pexpr_parse_invalid_colon; } bool is_ternary = false; bool can_be_ternary = true; + bool is_subscript = false; for (size_t i = 1; i < kv_size(ast_stack); i++) { ExprASTNode *const *const eastnode_p = (ExprASTNode *const *)kv_Z(ast_stack, i); @@ -1683,6 +1692,7 @@ viml_pexpr_parse_invalid_comma: } is_ternary = true; (*eastnode_p)->data.ter.got_colon = true; + ADD_VALUE_IF_MISSING(_(EXP_VAL_COLON)); assert((*eastnode_p)->children != NULL); assert((*eastnode_p)->children->next == NULL); kvi_push(ast_stack, &(*eastnode_p)->children->next); @@ -1690,14 +1700,18 @@ viml_pexpr_parse_invalid_comma: } else if (eastnode_type == kExprNodeUnknownFigure) { SELECT_FIGURE_BRACE_TYPE(*eastnode_p, DictLiteral, Dict); break; - } else if (eastnode_type == kExprNodeDictLiteral - || eastnode_type == kExprNodeSubscript) { + } else if (eastnode_type == kExprNodeDictLiteral) { + break; + } else if (eastnode_type == kExprNodeSubscript) { + is_subscript = true; + can_be_ternary = false; + assert(!is_ternary); break; } else if (eastnode_type == kExprNodeColon) { goto viml_pexpr_parse_invalid_colon; } else if (eastnode_lvl >= kEOpLvlTernaryValue) { // Do nothing - } else if (eastnode_lvl > kEOpLvlComma) { + } else if (eastnode_lvl >= kEOpLvlComma) { can_be_ternary = false; } else { viml_pexpr_parse_invalid_colon: @@ -1711,16 +1725,122 @@ viml_pexpr_parse_invalid_colon: goto viml_pexpr_parse_invalid_colon; } } - if (is_ternary) { - HL_CUR_TOKEN(TernaryColon); - } else { + if (is_subscript) { + assert(kv_size(ast_stack) > 1); + // Colon immediately following subscript start: it is empty subscript + // part like a[:2]. + if (want_node == kENodeValue + && (*kv_Z(ast_stack, 1))->type == kExprNodeSubscript) { + NEW_NODE_WITH_CUR_POS(*top_node_p, kExprNodeMissing); + (*top_node_p)->len = 0; + want_node = kENodeOperator; + } else { + ADD_VALUE_IF_MISSING(_(EXP_VAL_COLON)); + } NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeColon); ADD_OP_NODE(cur_node); - HL_CUR_TOKEN(Colon); + HL_CUR_TOKEN(SubscriptColon); + } else { + ADD_VALUE_IF_MISSING(_(EXP_VAL_COLON)); + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeColon); + if (is_ternary) { + HL_CUR_TOKEN(TernaryColon); + } else { + ADD_OP_NODE(cur_node); + HL_CUR_TOKEN(Colon); + } } want_node = kENodeValue; break; } +#undef EXP_VAL_COLON + case kExprLexBracket: { + if (cur_token.data.brc.closing) { + ExprASTNode **new_top_node_p = NULL; + // Always drop the topmost value: + // + // 1. When want_node != kENodeValue topmost item on stack is + // a *finished* left operand, which may as well be "{@a}" which + // needs not be finished again. + // 2. Otherwise it is pointing to NULL what nobody wants. + kv_drop(ast_stack, 1); + if (!kv_size(ast_stack)) { + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeListLiteral); + cur_node->len = 0; + if (want_node != kENodeValue) { + cur_node->children = *top_node_p; + } + *top_node_p = cur_node; + goto viml_pexpr_parse_bracket_closing_error; + } + if (want_node == kENodeValue) { + // It is OK to want value if + // + // 1. It is empty list literal, in which case top node will be + // ListLiteral. + // 2. It is list literal with trailing comma, in which case top node + // will be that comma. + // 3. It is subscript with colon, but without one of the values: + // e.g. "a[:]", "a[1:]", top node will be colon in this case. + if ((*kv_last(ast_stack))->type != kExprNodeListLiteral + && (*kv_last(ast_stack))->type != kExprNodeComma + && (*kv_last(ast_stack))->type != kExprNodeColon) { + ERROR_FROM_TOKEN_AND_MSG( + cur_token, + _("E15: Expected value, got closing bracket: %.*s")); + } + } else { + if (!kv_size(ast_stack)) { + new_top_node_p = top_node_p; + goto viml_pexpr_parse_bracket_closing_error; + } + } + do { + new_top_node_p = kv_pop(ast_stack); + } while (kv_size(ast_stack) + && (new_top_node_p == NULL + || ((*new_top_node_p)->type != kExprNodeListLiteral + && (*new_top_node_p)->type != kExprNodeSubscript))); + ExprASTNode *new_top_node = *new_top_node_p; + switch (new_top_node->type) { + case kExprNodeListLiteral: { + HL_CUR_TOKEN(List); + break; + } + case kExprNodeSubscript: { + HL_CUR_TOKEN(Subscript); + break; + } + default: { +viml_pexpr_parse_bracket_closing_error: + assert(!kv_size(ast_stack)); + ERROR_FROM_TOKEN_AND_MSG( + cur_token, _("E15: Unexpected closing figure brace: %.*s")); + HL_CUR_TOKEN(List); + break; + } + } + kvi_push(ast_stack, new_top_node_p); + want_node = kENodeOperator; + } else { + if (want_node == kENodeValue) { + // Value means list literal. + HL_CUR_TOKEN(List); + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeListLiteral); + *top_node_p = cur_node; + kvi_push(ast_stack, &cur_node->children); + want_node = kENodeValue; + } else { + if (prev_token.type == kExprLexSpacing) { + OP_MISSING; + } + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeSubscript); + ADD_OP_NODE(cur_node); + HL_CUR_TOKEN(Subscript); + } + } + break; + } case kExprLexFigureBrace: { if (cur_token.data.brc.closing) { ExprASTNode **new_top_node_p = NULL; diff --git a/test/unit/viml/expressions/parser_spec.lua b/test/unit/viml/expressions/parser_spec.lua index 46e3328184..41261cf7c9 100644 --- a/test/unit/viml/expressions/parser_spec.lua +++ b/test/unit/viml/expressions/parser_spec.lua @@ -91,6 +91,8 @@ make_enum_conv_tab(lib, { 'kExprNodeConcatOrSubscript', 'kExprNodeInteger', 'kExprNodeFloat', + 'kExprNodeSingleQuotedString', + 'kExprNodeDoubleQuotedString', }, 'kExprNode', function(ret) east_node_type_tab = ret end) local function conv_east_node_type(typ) @@ -204,6 +206,10 @@ describe('Expressions parser', function() local function check_parsing(str, flags, exp_ast, exp_highlighting_fs) flags = flags or 0 + if os.getenv('NVIM_TEST_PARSER_SPEC_PRINT_TEST_CASE') == '1' then + print(str, flags) + end + local pstate = new_pstate({str}) local east = lib.viml_pexpr_parse(pstate, flags) local ast = east2lua(pstate, east) @@ -4016,4 +4022,531 @@ describe('Expressions parser', function() hl('Identifier', 'b'), }) end) + itp('works with bracket subscripts', function() + check_parsing(':', 0, { + -- 0 + ast = { + { + 'Colon:0:0::', + children = { + 'Missing:0:0:', + }, + }, + }, + err = { + arg = ':', + msg = 'E15: Colon outside of dictionary or ternary operator: %.*s', + }, + }, { + hl('InvalidColon', ':'), + }) + check_parsing('a[]', 0, { + -- 012 + ast = { + { + 'Subscript:0:1:[', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + }, + }, + }, + err = { + arg = ']', + msg = 'E15: Expected value, got closing bracket: %.*s', + }, + }, { + hl('Identifier', 'a'), + hl('Subscript', '['), + hl('InvalidSubscript', ']'), + }) + check_parsing('a[b:]', 0, { + -- 01234 + ast = { + { + 'Subscript:0:1:[', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainIdentifier(scope=b,ident=):0:2:b:', + }, + }, + }, + }, { + hl('Identifier', 'a'), + hl('Subscript', '['), + hl('IdentifierScope', 'b'), + hl('IdentifierScopeDelimiter', ':'), + hl('Subscript', ']'), + }) + + check_parsing('a[b:c]', 0, { + -- 012345 + ast = { + { + 'Subscript:0:1:[', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainIdentifier(scope=b,ident=c):0:2:b:c', + }, + }, + }, + }, { + hl('Identifier', 'a'), + hl('Subscript', '['), + hl('IdentifierScope', 'b'), + hl('IdentifierScopeDelimiter', ':'), + hl('Identifier', 'c'), + hl('Subscript', ']'), + }) + check_parsing('a[b : c]', 0, { + -- 01234567 + ast = { + { + 'Subscript:0:1:[', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'Colon:0:3: :', + children = { + 'PlainIdentifier(scope=0,ident=b):0:2:b', + 'PlainIdentifier(scope=0,ident=c):0:5: c', + }, + }, + }, + }, + }, + }, { + hl('Identifier', 'a'), + hl('Subscript', '['), + hl('Identifier', 'b'), + hl('SubscriptColon', ':', 1), + hl('Identifier', 'c', 1), + hl('Subscript', ']'), + }) + + check_parsing('a[: b]', 0, { + -- 012345 + ast = { + { + 'Subscript:0:1:[', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'Colon:0:2::', + children = { + 'Missing:0:2:', + 'PlainIdentifier(scope=0,ident=b):0:3: b', + }, + }, + }, + }, + }, + }, { + hl('Identifier', 'a'), + hl('Subscript', '['), + hl('SubscriptColon', ':'), + hl('Identifier', 'b', 1), + hl('Subscript', ']'), + }) + + check_parsing('a[b :]', 0, { + -- 012345 + ast = { + { + 'Subscript:0:1:[', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'Colon:0:3: :', + children = { + 'PlainIdentifier(scope=0,ident=b):0:2:b', + }, + }, + }, + }, + }, + }, { + hl('Identifier', 'a'), + hl('Subscript', '['), + hl('Identifier', 'b'), + hl('SubscriptColon', ':', 1), + hl('Subscript', ']'), + }) + check_parsing('a[b][c][d](e)(f)(g)', 0, { + -- 0123456789012345678 + -- 0 1 + ast = { + { + 'Call:0:16:(', + children = { + { + 'Call:0:13:(', + children = { + { + 'Call:0:10:(', + children = { + { + 'Subscript:0:7:[', + children = { + { + 'Subscript:0:4:[', + children = { + { + 'Subscript:0:1:[', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainIdentifier(scope=0,ident=b):0:2:b', + }, + }, + 'PlainIdentifier(scope=0,ident=c):0:5:c', + }, + }, + 'PlainIdentifier(scope=0,ident=d):0:8:d', + }, + }, + 'PlainIdentifier(scope=0,ident=e):0:11:e', + }, + }, + 'PlainIdentifier(scope=0,ident=f):0:14:f', + }, + }, + 'PlainIdentifier(scope=0,ident=g):0:17:g', + }, + }, + }, + }, { + hl('Identifier', 'a'), + hl('Subscript', '['), + hl('Identifier', 'b'), + hl('Subscript', ']'), + hl('Subscript', '['), + hl('Identifier', 'c'), + hl('Subscript', ']'), + hl('Subscript', '['), + hl('Identifier', 'd'), + hl('Subscript', ']'), + hl('CallingParenthesis', '('), + hl('Identifier', 'e'), + hl('CallingParenthesis', ')'), + hl('CallingParenthesis', '('), + hl('Identifier', 'f'), + hl('CallingParenthesis', ')'), + hl('CallingParenthesis', '('), + hl('Identifier', 'g'), + hl('CallingParenthesis', ')'), + }) + check_parsing('{a}{b}{c}[d][e][f]', 0, { + -- 012345678901234567 + -- 0 1 + ast = { + { + 'Subscript:0:15:[', + children = { + { + 'Subscript:0:12:[', + children = { + { + 'Subscript:0:9:[', + children = { + { + 'ComplexIdentifier:0:3:', + children = { + { + 'CurlyBracesIdentifier(-di):0:0:{', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + }, + }, + { + 'ComplexIdentifier:0:6:', + children = { + { + 'CurlyBracesIdentifier(--i):0:3:{', + children = { + 'PlainIdentifier(scope=0,ident=b):0:4:b', + }, + }, + { + 'CurlyBracesIdentifier(--i):0:6:{', + children = { + 'PlainIdentifier(scope=0,ident=c):0:7:c', + }, + }, + }, + }, + }, + }, + 'PlainIdentifier(scope=0,ident=d):0:10:d', + }, + }, + 'PlainIdentifier(scope=0,ident=e):0:13:e', + }, + }, + 'PlainIdentifier(scope=0,ident=f):0:16:f', + }, + }, + }, + }, { + hl('Curly', '{'), + hl('Identifier', 'a'), + hl('Curly', '}'), + hl('Curly', '{'), + hl('Identifier', 'b'), + hl('Curly', '}'), + hl('Curly', '{'), + hl('Identifier', 'c'), + hl('Curly', '}'), + hl('Subscript', '['), + hl('Identifier', 'd'), + hl('Subscript', ']'), + hl('Subscript', '['), + hl('Identifier', 'e'), + hl('Subscript', ']'), + hl('Subscript', '['), + hl('Identifier', 'f'), + hl('Subscript', ']'), + }) + end) + itp('supports list literals', function() + check_parsing('[]', 0, { + -- 01 + ast = { + 'ListLiteral:0:0:[', + }, + }, { + hl('List', '['), + hl('List', ']'), + }) + + check_parsing('[a]', 0, { + -- 012 + ast = { + { + 'ListLiteral:0:0:[', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + }, + }, + }, + }, { + hl('List', '['), + hl('Identifier', 'a'), + hl('List', ']'), + }) + + check_parsing('[a, b]', 0, { + -- 012345 + ast = { + { + 'ListLiteral:0:0:[', + children = { + { + 'Comma:0:2:,', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + 'PlainIdentifier(scope=0,ident=b):0:3: b', + }, + }, + }, + }, + }, + }, { + hl('List', '['), + hl('Identifier', 'a'), + hl('Comma', ','), + hl('Identifier', 'b', 1), + hl('List', ']'), + }) + + check_parsing('[a, b, c]', 0, { + -- 012345678 + ast = { + { + 'ListLiteral:0:0:[', + children = { + { + 'Comma:0:2:,', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + { + 'Comma:0:5:,', + children = { + 'PlainIdentifier(scope=0,ident=b):0:3: b', + 'PlainIdentifier(scope=0,ident=c):0:6: c', + }, + }, + }, + }, + }, + }, + }, + }, { + hl('List', '['), + hl('Identifier', 'a'), + hl('Comma', ','), + hl('Identifier', 'b', 1), + hl('Comma', ','), + hl('Identifier', 'c', 1), + hl('List', ']'), + }) + + check_parsing('[a, b, c, ]', 0, { + -- 01234567890 + -- 0 1 + ast = { + { + 'ListLiteral:0:0:[', + children = { + { + 'Comma:0:2:,', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + { + 'Comma:0:5:,', + children = { + 'PlainIdentifier(scope=0,ident=b):0:3: b', + { + 'Comma:0:8:,', + children = { + 'PlainIdentifier(scope=0,ident=c):0:6: c', + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, { + hl('List', '['), + hl('Identifier', 'a'), + hl('Comma', ','), + hl('Identifier', 'b', 1), + hl('Comma', ','), + hl('Identifier', 'c', 1), + hl('Comma', ','), + hl('List', ']', 1), + }) + + check_parsing('[a : b, c : d]', 0, { + -- 01234567890123 + -- 0 1 + ast = { + { + 'ListLiteral:0:0:[', + children = { + { + 'Comma:0:6:,', + children = { + { + 'Colon:0:2: :', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + 'PlainIdentifier(scope=0,ident=b):0:4: b', + }, + }, + { + 'Colon:0:9: :', + children = { + 'PlainIdentifier(scope=0,ident=c):0:7: c', + 'PlainIdentifier(scope=0,ident=d):0:11: d', + }, + }, + }, + }, + }, + }, + }, + err = { + arg = ': b, c : d]', + msg = 'E15: Colon outside of dictionary or ternary operator: %.*s', + }, + }, { + hl('List', '['), + hl('Identifier', 'a'), + hl('InvalidColon', ':', 1), + hl('Identifier', 'b', 1), + hl('Comma', ','), + hl('Identifier', 'c', 1), + hl('InvalidColon', ':', 1), + hl('Identifier', 'd', 1), + hl('List', ']'), + }) + + check_parsing(']', 0, { + -- 0 + ast = { + 'ListLiteral:0:0:', + }, + err = { + arg = ']', + msg = 'E15: Unexpected closing figure brace: %.*s', + }, + }, { + hl('InvalidList', ']'), + }) + + check_parsing('a]', 0, { + -- 01 + ast = { + { + 'ListLiteral:0:1:', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + }, + }, + }, + err = { + arg = ']', + msg = 'E15: Unexpected closing figure brace: %.*s', + }, + }, { + hl('Identifier', 'a'), + hl('InvalidList', ']'), + }) + + check_parsing('[] []', 0, { + -- 01234 + ast = { + { + 'OpMissing:0:2:', + children = { + 'ListLiteral:0:0:[', + 'ListLiteral:0:2: [', + }, + }, + }, + err = { + arg = '[]', + msg = 'E15: Missing operator: %.*s', + }, + }, { + hl('List', '['), + hl('List', ']'), + hl('InvalidSpacing', ' '), + hl('List', '['), + hl('List', ']'), + }) + + check_parsing('[][]', 0, { + -- 0123 + ast = { + { + 'Subscript:0:2:[', + children = { + 'ListLiteral:0:0:[', + }, + }, + }, + err = { + arg = ']', + msg = 'E15: Expected value, got closing bracket: %.*s', + }, + }, { + hl('List', '['), + hl('List', ']'), + hl('Subscript', '['), + hl('InvalidSubscript', ']'), + }) + end) end) -- cgit From 6f22b5afad23ccc0390a6e2dd61d2e166ac6696a Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 8 Oct 2017 21:19:10 +0300 Subject: mbyte: Lint some functions which are to be copied for symbolic tests --- src/nvim/globals.h | 23 ----- src/nvim/mbyte.c | 248 +++++++++++++++++++++++++++++------------------------ 2 files changed, 137 insertions(+), 134 deletions(-) diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 13ecafcbe3..86fff46737 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -725,29 +725,6 @@ EXTERN int vr_lines_changed INIT(= 0); /* #Lines changed by "gR" so far */ /// Encoding used when 'fencs' is set to "default" EXTERN char_u *fenc_default INIT(= NULL); -// To speed up BYTELEN(); keep a lookup table to quickly get the length in -// bytes of a UTF-8 character from the first byte of a UTF-8 string. Bytes -// which are illegal when used as the first byte have a 1. The NUL byte has -// length 1. -EXTERN char utf8len_tab[256] INIT(= { - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 1, 1, -}); - # if defined(USE_ICONV) && defined(DYNAMIC_ICONV) /* Pointers to functions and variables to be loaded at runtime */ EXTERN size_t (*iconv)(iconv_t cd, const char **inbuf, size_t *inbytesleft, diff --git a/src/nvim/mbyte.c b/src/nvim/mbyte.c index b24770a409..f65d7a6b13 100644 --- a/src/nvim/mbyte.c +++ b/src/nvim/mbyte.c @@ -72,19 +72,41 @@ struct interval { # include "unicode_tables.generated.h" #endif -/* - * Like utf8len_tab above, but using a zero for illegal lead bytes. - */ -const uint8_t utf8len_tab_zero[256] = -{ - 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, - 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, - 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, - 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, - 3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,5,5,5,5,6,6,0,0, +// To speed up BYTELEN(); keep a lookup table to quickly get the length in +// bytes of a UTF-8 character from the first byte of a UTF-8 string. Bytes +// which are illegal when used as the first byte have a 1. The NUL byte has +// length 1. +const uint8_t utf8len_tab[] = { + // ?1 ?2 ?3 ?4 ?5 ?6 ?7 ?8 ?9 ?A ?B ?C ?D ?E ?F + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 1? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 2? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 3? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 4? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 5? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 6? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 7? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 8? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 9? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // A? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // B? + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // C? + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // D? + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, // E? + 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 1, 1, // F? +}; + +// Like utf8len_tab above, but using a zero for illegal lead bytes. +const uint8_t utf8len_tab_zero[] = { + //1 2 3 4 5 6 7 8 9 A B C D E F 0 1 2 3 4 5 6 7 8 9 A B C D E F + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 0 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 2 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 4 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 6 + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 8 + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // A + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, // C + 3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,5,5,5,5,6,6,0,0, // E }; /* @@ -528,45 +550,52 @@ int utf_off2cells(unsigned off, unsigned max_off) return (off + 1 < max_off && ScreenLines[off + 1] == 0) ? 2 : 1; } -/* - * Convert a UTF-8 byte sequence to a wide character. - * If the sequence is illegal or truncated by a NUL the first byte is - * returned. - * Does not include composing characters, of course. - */ -int utf_ptr2char(const char_u *p) +/// Convert a UTF-8 byte sequence to a wide character +/// +/// If the sequence is illegal or truncated by a NUL then the first byte is +/// returned. Does not include composing characters for obvious reasons. +/// +/// @param[in] p String to convert. +/// +/// @return Unicode codepoint or byte value. +int utf_ptr2char(const char_u *const p) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { - uint8_t len; - - if (p[0] < 0x80) /* be quick for ASCII */ + if (p[0] < 0x80) { // Be quick for ASCII. return p[0]; + } - len = utf8len_tab_zero[p[0]]; + const uint8_t len = utf8len_tab_zero[p[0]]; if (len > 1 && (p[1] & 0xc0) == 0x80) { - if (len == 2) + if (len == 2) { return ((p[0] & 0x1f) << 6) + (p[1] & 0x3f); + } if ((p[2] & 0xc0) == 0x80) { - if (len == 3) - return ((p[0] & 0x0f) << 12) + ((p[1] & 0x3f) << 6) - + (p[2] & 0x3f); + if (len == 3) { + return (((p[0] & 0x0f) << 12) + ((p[1] & 0x3f) << 6) + + (p[2] & 0x3f)); + } if ((p[3] & 0xc0) == 0x80) { - if (len == 4) - return ((p[0] & 0x07) << 18) + ((p[1] & 0x3f) << 12) - + ((p[2] & 0x3f) << 6) + (p[3] & 0x3f); + if (len == 4) { + return (((p[0] & 0x07) << 18) + ((p[1] & 0x3f) << 12) + + ((p[2] & 0x3f) << 6) + (p[3] & 0x3f)); + } if ((p[4] & 0xc0) == 0x80) { - if (len == 5) - return ((p[0] & 0x03) << 24) + ((p[1] & 0x3f) << 18) - + ((p[2] & 0x3f) << 12) + ((p[3] & 0x3f) << 6) - + (p[4] & 0x3f); - if ((p[5] & 0xc0) == 0x80 && len == 6) - return ((p[0] & 0x01) << 30) + ((p[1] & 0x3f) << 24) - + ((p[2] & 0x3f) << 18) + ((p[3] & 0x3f) << 12) - + ((p[4] & 0x3f) << 6) + (p[5] & 0x3f); + if (len == 5) { + return (((p[0] & 0x03) << 24) + ((p[1] & 0x3f) << 18) + + ((p[2] & 0x3f) << 12) + ((p[3] & 0x3f) << 6) + + (p[4] & 0x3f)); + } + if ((p[5] & 0xc0) == 0x80 && len == 6) { + return (((p[0] & 0x01) << 30) + ((p[1] & 0x3f) << 24) + + ((p[2] & 0x3f) << 18) + ((p[3] & 0x3f) << 12) + + ((p[4] & 0x3f) << 6) + (p[5] & 0x3f)); + } } } } } - /* Illegal value, just return the first byte */ + // Illegal value: just return the first byte. return p[0]; } @@ -767,23 +796,24 @@ int utfc_char2bytes(int off, char_u *buf) return len; } -/* - * Get the length of a UTF-8 byte sequence, not including any following - * composing characters. - * Returns 0 for "". - * Returns 1 for an illegal byte sequence. - */ -int utf_ptr2len(const char_u *p) +/// Get the length of a UTF-8 byte sequence representing a single codepoint +/// +/// @param[in] p UTF-8 string. +/// +/// @return Sequence length, 0 for empty string and 1 for non-UTF-8 byte +/// sequence. +int utf_ptr2len(const char_u *const p) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { - int len; - int i; - - if (*p == NUL) + if (*p == NUL) { return 0; - len = utf8len_tab[*p]; - for (i = 1; i < len; ++i) - if ((p[i] & 0xc0) != 0x80) + } + const int len = utf8len_tab[*p]; + for (int i = 1; i < len; i++) { + if ((p[i] & 0xc0) != 0x80) { return 1; + } + } return len; } @@ -824,38 +854,38 @@ int utf_ptr2len_len(const char_u *p, int size) return len; } -/* - * Return the number of bytes the UTF-8 encoding of the character at "p" takes. - * This includes following composing characters. - */ -int utfc_ptr2len(const char_u *p) +/// Return the number of bytes occupied by a UTF-8 character in a string +/// +/// This includes following composing characters. +int utfc_ptr2len(const char_u *const p) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { - int len; - int b0 = *p; - int prevlen; + uint8_t b0 = (uint8_t)(*p); - if (b0 == NUL) + if (b0 == NUL) { return 0; - if (b0 < 0x80 && p[1] < 0x80) /* be quick for ASCII */ + } + if (b0 < 0x80 && p[1] < 0x80) { // be quick for ASCII return 1; + } - /* Skip over first UTF-8 char, stopping at a NUL byte. */ - len = utf_ptr2len(p); + // Skip over first UTF-8 char, stopping at a NUL byte. + int len = utf_ptr2len(p); - /* Check for illegal byte. */ - if (len == 1 && b0 >= 0x80) + // Check for illegal byte. + if (len == 1 && b0 >= 0x80) { return 1; + } - /* - * Check for composing characters. We can handle only the first six, but - * skip all of them (otherwise the cursor would get stuck). - */ - prevlen = 0; - for (;; ) { - if (p[len] < 0x80 || !UTF_COMPOSINGLIKE(p + prevlen, p + len)) + // Check for composing characters. We can handle only the first six, but + // skip all of them (otherwise the cursor would get stuck). + int prevlen = 0; + for (;;) { + if (p[len] < 0x80 || !UTF_COMPOSINGLIKE(p + prevlen, p + len)) { return len; + } - /* Skip over composing char */ + // Skip over composing char. prevlen = len; len += utf_ptr2len(p + len); } @@ -913,23 +943,22 @@ int utfc_ptr2len_len(const char_u *p, int size) return len; } -/* - * Return the number of bytes the UTF-8 encoding of character "c" takes. - * This does not include composing characters. - */ -int utf_char2len(int c) +/// Determine how many bytes certain unicode codepoint will occupy +int utf_char2len(const int c) { - if (c < 0x80) + if (c < 0x80) { return 1; - if (c < 0x800) + } else if (c < 0x800) { return 2; - if (c < 0x10000) + } else if (c < 0x10000) { return 3; - if (c < 0x200000) + } else if (c < 0x200000) { return 4; - if (c < 0x4000000) + } else if (c < 0x4000000) { return 5; - return 6; + } else { + return 6; + } } /// Convert Unicode character to UTF-8 string @@ -937,46 +966,42 @@ int utf_char2len(int c) /// @param c character to convert to \p buf /// @param[out] buf UTF-8 string generated from \p c, does not add \0 /// @return Number of bytes (1-6). Does not include composing characters. -int utf_char2bytes(int c, char_u *const buf) +int utf_char2bytes(const int c, char_u *const buf) { - if (c < 0x80) { /* 7 bits */ + if (c < 0x80) { // 7 bits buf[0] = c; return 1; - } - if (c < 0x800) { /* 11 bits */ + } else if (c < 0x800) { // 11 bits buf[0] = 0xc0 + ((unsigned)c >> 6); buf[1] = 0x80 + (c & 0x3f); return 2; - } - if (c < 0x10000) { /* 16 bits */ + } else if (c < 0x10000) { // 16 bits buf[0] = 0xe0 + ((unsigned)c >> 12); buf[1] = 0x80 + (((unsigned)c >> 6) & 0x3f); buf[2] = 0x80 + (c & 0x3f); return 3; - } - if (c < 0x200000) { /* 21 bits */ + } else if (c < 0x200000) { // 21 bits buf[0] = 0xf0 + ((unsigned)c >> 18); buf[1] = 0x80 + (((unsigned)c >> 12) & 0x3f); buf[2] = 0x80 + (((unsigned)c >> 6) & 0x3f); buf[3] = 0x80 + (c & 0x3f); return 4; - } - if (c < 0x4000000) { /* 26 bits */ + } else if (c < 0x4000000) { // 26 bits buf[0] = 0xf8 + ((unsigned)c >> 24); buf[1] = 0x80 + (((unsigned)c >> 18) & 0x3f); buf[2] = 0x80 + (((unsigned)c >> 12) & 0x3f); buf[3] = 0x80 + (((unsigned)c >> 6) & 0x3f); buf[4] = 0x80 + (c & 0x3f); return 5; + } else { // 31 bits + buf[0] = 0xfc + ((unsigned)c >> 30); + buf[1] = 0x80 + (((unsigned)c >> 24) & 0x3f); + buf[2] = 0x80 + (((unsigned)c >> 18) & 0x3f); + buf[3] = 0x80 + (((unsigned)c >> 12) & 0x3f); + buf[4] = 0x80 + (((unsigned)c >> 6) & 0x3f); + buf[5] = 0x80 + (c & 0x3f); + return 6; } - /* 31 bits */ - buf[0] = 0xfc + ((unsigned)c >> 30); - buf[1] = 0x80 + (((unsigned)c >> 24) & 0x3f); - buf[2] = 0x80 + (((unsigned)c >> 18) & 0x3f); - buf[3] = 0x80 + (((unsigned)c >> 12) & 0x3f); - buf[4] = 0x80 + (((unsigned)c >> 6) & 0x3f); - buf[5] = 0x80 + (c & 0x3f); - return 6; } /* @@ -1513,14 +1538,15 @@ int utf_head_off(const char_u *base, const char_u *p) return (int)(p - q); } -/* - * Copy a character from "*fp" to "*tp" and advance the pointers. - */ -void mb_copy_char(const char_u **fp, char_u **tp) +/// Copy a character, advancing the pointers +/// +/// @param[in,out] fp Source of the character to copy. +/// @param[in,out] tp Destination to copy to. +void mb_copy_char(const char_u **const fp, char_u **const tp) { - int l = (*mb_ptr2len)(*fp); + const size_t l = (size_t)utfc_ptr2len(*fp); - memmove(*tp, *fp, (size_t)l); + memmove(*tp, *fp, l); *tp += l; *fp += l; } -- cgit From e423cfe1948d75285dad2df151f53400f3b3be4e Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 8 Oct 2017 21:41:34 +0300 Subject: edit: Lint some functions which are to be copied for symbolic tests --- src/nvim/edit.c | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/src/nvim/edit.c b/src/nvim/edit.c index ca62679fab..e57598fc91 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -6074,27 +6074,30 @@ void free_last_insert(void) #endif -/* - * Add character "c" to buffer "s". Escape the special meaning of K_SPECIAL - * and CSI. Handle multi-byte characters. - * Returns a pointer to after the added bytes. - */ +/// Add character "c" to buffer "s" +/// +/// Escapes the special meaning of K_SPECIAL and CSI, handles multi-byte +/// characters. +/// +/// @param[in] c Character to add. +/// @param[out] s Buffer to add to. Must have at least MB_MAXBYTES + 1 bytes. +/// +/// @return Pointer to after the added bytes. char_u *add_char2buf(int c, char_u *s) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { char_u temp[MB_MAXBYTES + 1]; - int i; - int len; - - len = (*mb_char2bytes)(c, temp); - for (i = 0; i < len; ++i) { + const int len = utf_char2bytes(c, temp); + for (int i = 0; i < len; ++i) { c = temp[i]; - /* Need to escape K_SPECIAL and CSI like in the typeahead buffer. */ + // Need to escape K_SPECIAL and CSI like in the typeahead buffer. if (c == K_SPECIAL) { *s++ = K_SPECIAL; *s++ = KS_SPECIAL; *s++ = KE_FILLER; - } else + } else { *s++ = c; + } } return s; } -- cgit From c484613ce034cf9b10a4185621abdf8d82b570f8 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 8 Oct 2017 21:52:29 +0300 Subject: keymap: Lint some functions to be copied for symbolic tests --- src/nvim/keymap.c | 435 +++++++++++++++++++++++++++--------------------------- 1 file changed, 216 insertions(+), 219 deletions(-) diff --git a/src/nvim/keymap.c b/src/nvim/keymap.c index 3d7ebb6382..f344d65c8d 100644 --- a/src/nvim/keymap.c +++ b/src/nvim/keymap.c @@ -24,12 +24,11 @@ * Some useful tables. */ -static struct modmasktable { - short mod_mask; /* Bit-mask for particular key modifier */ - short mod_flag; /* Bit(s) for particular key modifier */ - char_u name; /* Single letter name of modifier */ -} mod_mask_table[] = -{ +static const struct modmasktable { + short mod_mask; ///< Bit-mask for particular key modifier. + short mod_flag; ///< Bit(s) for particular key modifier. + char_u name; ///< Single letter name of modifier. +} mod_mask_table[] = { {MOD_MASK_ALT, MOD_MASK_ALT, (char_u)'M'}, {MOD_MASK_META, MOD_MASK_META, (char_u)'T'}, {MOD_MASK_CTRL, MOD_MASK_CTRL, (char_u)'C'}, @@ -139,155 +138,154 @@ static char_u modifier_keys_table[] = NUL }; -static struct key_name_entry { - int key; /* Special key code or ascii value */ - char_u *name; /* Name of key */ -} key_names_table[] = -{ - {' ', (char_u *)"Space"}, - {TAB, (char_u *)"Tab"}, - {K_TAB, (char_u *)"Tab"}, - {NL, (char_u *)"NL"}, - {NL, (char_u *)"NewLine"}, /* Alternative name */ - {NL, (char_u *)"LineFeed"}, /* Alternative name */ - {NL, (char_u *)"LF"}, /* Alternative name */ - {CAR, (char_u *)"CR"}, - {CAR, (char_u *)"Return"}, /* Alternative name */ - {CAR, (char_u *)"Enter"}, /* Alternative name */ - {K_BS, (char_u *)"BS"}, - {K_BS, (char_u *)"BackSpace"}, /* Alternative name */ - {ESC, (char_u *)"Esc"}, - {CSI, (char_u *)"CSI"}, - {K_CSI, (char_u *)"xCSI"}, - {'|', (char_u *)"Bar"}, - {'\\', (char_u *)"Bslash"}, - {K_DEL, (char_u *)"Del"}, - {K_DEL, (char_u *)"Delete"}, /* Alternative name */ - {K_KDEL, (char_u *)"kDel"}, - {K_UP, (char_u *)"Up"}, - {K_DOWN, (char_u *)"Down"}, - {K_LEFT, (char_u *)"Left"}, - {K_RIGHT, (char_u *)"Right"}, - {K_XUP, (char_u *)"xUp"}, - {K_XDOWN, (char_u *)"xDown"}, - {K_XLEFT, (char_u *)"xLeft"}, - {K_XRIGHT, (char_u *)"xRight"}, - - {K_F1, (char_u *)"F1"}, - {K_F2, (char_u *)"F2"}, - {K_F3, (char_u *)"F3"}, - {K_F4, (char_u *)"F4"}, - {K_F5, (char_u *)"F5"}, - {K_F6, (char_u *)"F6"}, - {K_F7, (char_u *)"F7"}, - {K_F8, (char_u *)"F8"}, - {K_F9, (char_u *)"F9"}, - {K_F10, (char_u *)"F10"}, - - {K_F11, (char_u *)"F11"}, - {K_F12, (char_u *)"F12"}, - {K_F13, (char_u *)"F13"}, - {K_F14, (char_u *)"F14"}, - {K_F15, (char_u *)"F15"}, - {K_F16, (char_u *)"F16"}, - {K_F17, (char_u *)"F17"}, - {K_F18, (char_u *)"F18"}, - {K_F19, (char_u *)"F19"}, - {K_F20, (char_u *)"F20"}, - - {K_F21, (char_u *)"F21"}, - {K_F22, (char_u *)"F22"}, - {K_F23, (char_u *)"F23"}, - {K_F24, (char_u *)"F24"}, - {K_F25, (char_u *)"F25"}, - {K_F26, (char_u *)"F26"}, - {K_F27, (char_u *)"F27"}, - {K_F28, (char_u *)"F28"}, - {K_F29, (char_u *)"F29"}, - {K_F30, (char_u *)"F30"}, - - {K_F31, (char_u *)"F31"}, - {K_F32, (char_u *)"F32"}, - {K_F33, (char_u *)"F33"}, - {K_F34, (char_u *)"F34"}, - {K_F35, (char_u *)"F35"}, - {K_F36, (char_u *)"F36"}, - {K_F37, (char_u *)"F37"}, - - {K_XF1, (char_u *)"xF1"}, - {K_XF2, (char_u *)"xF2"}, - {K_XF3, (char_u *)"xF3"}, - {K_XF4, (char_u *)"xF4"}, - - {K_HELP, (char_u *)"Help"}, - {K_UNDO, (char_u *)"Undo"}, - {K_INS, (char_u *)"Insert"}, - {K_INS, (char_u *)"Ins"}, /* Alternative name */ - {K_KINS, (char_u *)"kInsert"}, - {K_HOME, (char_u *)"Home"}, - {K_KHOME, (char_u *)"kHome"}, - {K_XHOME, (char_u *)"xHome"}, - {K_ZHOME, (char_u *)"zHome"}, - {K_END, (char_u *)"End"}, - {K_KEND, (char_u *)"kEnd"}, - {K_XEND, (char_u *)"xEnd"}, - {K_ZEND, (char_u *)"zEnd"}, - {K_PAGEUP, (char_u *)"PageUp"}, - {K_PAGEDOWN, (char_u *)"PageDown"}, - {K_KPAGEUP, (char_u *)"kPageUp"}, - {K_KPAGEDOWN, (char_u *)"kPageDown"}, - - {K_KPLUS, (char_u *)"kPlus"}, - {K_KMINUS, (char_u *)"kMinus"}, - {K_KDIVIDE, (char_u *)"kDivide"}, - {K_KMULTIPLY, (char_u *)"kMultiply"}, - {K_KENTER, (char_u *)"kEnter"}, - {K_KPOINT, (char_u *)"kPoint"}, - - {K_K0, (char_u *)"k0"}, - {K_K1, (char_u *)"k1"}, - {K_K2, (char_u *)"k2"}, - {K_K3, (char_u *)"k3"}, - {K_K4, (char_u *)"k4"}, - {K_K5, (char_u *)"k5"}, - {K_K6, (char_u *)"k6"}, - {K_K7, (char_u *)"k7"}, - {K_K8, (char_u *)"k8"}, - {K_K9, (char_u *)"k9"}, - - {'<', (char_u *)"lt"}, - - {K_MOUSE, (char_u *)"Mouse"}, - {K_LEFTMOUSE, (char_u *)"LeftMouse"}, - {K_LEFTMOUSE_NM, (char_u *)"LeftMouseNM"}, - {K_LEFTDRAG, (char_u *)"LeftDrag"}, - {K_LEFTRELEASE, (char_u *)"LeftRelease"}, - {K_LEFTRELEASE_NM, (char_u *)"LeftReleaseNM"}, - {K_MIDDLEMOUSE, (char_u *)"MiddleMouse"}, - {K_MIDDLEDRAG, (char_u *)"MiddleDrag"}, - {K_MIDDLERELEASE, (char_u *)"MiddleRelease"}, - {K_RIGHTMOUSE, (char_u *)"RightMouse"}, - {K_RIGHTDRAG, (char_u *)"RightDrag"}, - {K_RIGHTRELEASE, (char_u *)"RightRelease"}, - {K_MOUSEDOWN, (char_u *)"ScrollWheelUp"}, - {K_MOUSEUP, (char_u *)"ScrollWheelDown"}, - {K_MOUSELEFT, (char_u *)"ScrollWheelRight"}, - {K_MOUSERIGHT, (char_u *)"ScrollWheelLeft"}, - {K_MOUSEDOWN, (char_u *)"MouseDown"}, /* OBSOLETE: Use */ - {K_MOUSEUP, (char_u *)"MouseUp"}, /* ScrollWheelXXX instead */ - {K_X1MOUSE, (char_u *)"X1Mouse"}, - {K_X1DRAG, (char_u *)"X1Drag"}, - {K_X1RELEASE, (char_u *)"X1Release"}, - {K_X2MOUSE, (char_u *)"X2Mouse"}, - {K_X2DRAG, (char_u *)"X2Drag"}, - {K_X2RELEASE, (char_u *)"X2Release"}, - {K_DROP, (char_u *)"Drop"}, - {K_ZERO, (char_u *)"Nul"}, - {K_SNR, (char_u *)"SNR"}, - {K_PLUG, (char_u *)"Plug"}, - {K_PASTE, (char_u *)"Paste"}, - {K_FOCUSGAINED, (char_u *)"FocusGained"}, - {K_FOCUSLOST, (char_u *)"FocusLost"}, +static const struct key_name_entry { + int key; ///< Special key code or ASCII value. + const char *name; ///< Name of the key +} key_names_table[] = { + {' ', "Space"}, + {TAB, "Tab"}, + {K_TAB, "Tab"}, + {NL, "NL"}, + {NL, "NewLine"}, // Alternative name + {NL, "LineFeed"}, // Alternative name + {NL, "LF"}, // Alternative name + {CAR, "CR"}, + {CAR, "Return"}, // Alternative name + {CAR, "Enter"}, // Alternative name + {K_BS, "BS"}, + {K_BS, "BackSpace"}, // Alternative name + {ESC, "Esc"}, + {CSI, "CSI"}, + {K_CSI, "xCSI"}, + {'|', "Bar"}, + {'\\', "Bslash"}, + {K_DEL, "Del"}, + {K_DEL, "Delete"}, // Alternative name + {K_KDEL, "kDel"}, + {K_UP, "Up"}, + {K_DOWN, "Down"}, + {K_LEFT, "Left"}, + {K_RIGHT, "Right"}, + {K_XUP, "xUp"}, + {K_XDOWN, "xDown"}, + {K_XLEFT, "xLeft"}, + {K_XRIGHT, "xRight"}, + + {K_F1, "F1"}, + {K_F2, "F2"}, + {K_F3, "F3"}, + {K_F4, "F4"}, + {K_F5, "F5"}, + {K_F6, "F6"}, + {K_F7, "F7"}, + {K_F8, "F8"}, + {K_F9, "F9"}, + {K_F10, "F10"}, + + {K_F11, "F11"}, + {K_F12, "F12"}, + {K_F13, "F13"}, + {K_F14, "F14"}, + {K_F15, "F15"}, + {K_F16, "F16"}, + {K_F17, "F17"}, + {K_F18, "F18"}, + {K_F19, "F19"}, + {K_F20, "F20"}, + + {K_F21, "F21"}, + {K_F22, "F22"}, + {K_F23, "F23"}, + {K_F24, "F24"}, + {K_F25, "F25"}, + {K_F26, "F26"}, + {K_F27, "F27"}, + {K_F28, "F28"}, + {K_F29, "F29"}, + {K_F30, "F30"}, + + {K_F31, "F31"}, + {K_F32, "F32"}, + {K_F33, "F33"}, + {K_F34, "F34"}, + {K_F35, "F35"}, + {K_F36, "F36"}, + {K_F37, "F37"}, + + {K_XF1, "xF1"}, + {K_XF2, "xF2"}, + {K_XF3, "xF3"}, + {K_XF4, "xF4"}, + + {K_HELP, "Help"}, + {K_UNDO, "Undo"}, + {K_INS, "Insert"}, + {K_INS, "Ins"}, // Alternative name + {K_KINS, "kInsert"}, + {K_HOME, "Home"}, + {K_KHOME, "kHome"}, + {K_XHOME, "xHome"}, + {K_ZHOME, "zHome"}, + {K_END, "End"}, + {K_KEND, "kEnd"}, + {K_XEND, "xEnd"}, + {K_ZEND, "zEnd"}, + {K_PAGEUP, "PageUp"}, + {K_PAGEDOWN, "PageDown"}, + {K_KPAGEUP, "kPageUp"}, + {K_KPAGEDOWN, "kPageDown"}, + + {K_KPLUS, "kPlus"}, + {K_KMINUS, "kMinus"}, + {K_KDIVIDE, "kDivide"}, + {K_KMULTIPLY, "kMultiply"}, + {K_KENTER, "kEnter"}, + {K_KPOINT, "kPoint"}, + + {K_K0, "k0"}, + {K_K1, "k1"}, + {K_K2, "k2"}, + {K_K3, "k3"}, + {K_K4, "k4"}, + {K_K5, "k5"}, + {K_K6, "k6"}, + {K_K7, "k7"}, + {K_K8, "k8"}, + {K_K9, "k9"}, + + {'<', "lt"}, + + {K_MOUSE, "Mouse"}, + {K_LEFTMOUSE, "LeftMouse"}, + {K_LEFTMOUSE_NM, "LeftMouseNM"}, + {K_LEFTDRAG, "LeftDrag"}, + {K_LEFTRELEASE, "LeftRelease"}, + {K_LEFTRELEASE_NM, "LeftReleaseNM"}, + {K_MIDDLEMOUSE, "MiddleMouse"}, + {K_MIDDLEDRAG, "MiddleDrag"}, + {K_MIDDLERELEASE, "MiddleRelease"}, + {K_RIGHTMOUSE, "RightMouse"}, + {K_RIGHTDRAG, "RightDrag"}, + {K_RIGHTRELEASE, "RightRelease"}, + {K_MOUSEDOWN, "ScrollWheelUp"}, + {K_MOUSEUP, "ScrollWheelDown"}, + {K_MOUSELEFT, "ScrollWheelRight"}, + {K_MOUSERIGHT, "ScrollWheelLeft"}, + {K_MOUSEDOWN, "MouseDown"}, // OBSOLETE: Use ScrollWheelXXX instead + {K_MOUSEUP, "MouseUp"}, // Same + {K_X1MOUSE, "X1Mouse"}, + {K_X1DRAG, "X1Drag"}, + {K_X1RELEASE, "X1Release"}, + {K_X2MOUSE, "X2Mouse"}, + {K_X2DRAG, "X2Drag"}, + {K_X2RELEASE, "X2Release"}, + {K_DROP, "Drop"}, + {K_ZERO, "Nul"}, + {K_SNR, "SNR"}, + {K_PLUG, "Plug"}, + {K_PASTE, "Paste"}, + {K_FOCUSGAINED, "FocusGained"}, + {K_FOCUSLOST, "FocusLost"}, {0, NULL} }; @@ -320,73 +318,73 @@ static struct mousetable { {0, 0, 0, 0}, }; -/* - * Return the modifier mask bit (MOD_MASK_*) which corresponds to the given - * modifier name ('S' for Shift, 'C' for Ctrl etc). - */ +/// Return the modifier mask bit (#MOD_MASK_*) corresponding to mod name +/// +/// E.g. 'S' for shift, 'C' for ctrl. int name_to_mod_mask(int c) + FUNC_ATTR_CONST FUNC_ATTR_WARN_UNUSED_RESULT { - int i; - c = TOUPPER_ASC(c); - for (i = 0; mod_mask_table[i].mod_mask != 0; i++) - if (c == mod_mask_table[i].name) + for (size_t i = 0; mod_mask_table[i].mod_mask != 0; i++) { + if (c == mod_mask_table[i].name) { return mod_mask_table[i].mod_flag; + } + } return 0; } -/* - * Check if if there is a special key code for "key" that includes the - * modifiers specified. - */ -int simplify_key(int key, int *modifiers) +/// Check if there is a special key code for "key" with specified modifiers +/// +/// @param[in] key Initial key code. +/// @param[in,out] modifiers Initial modifiers, is adjusted to have simplified +/// modifiers. +/// +/// @return Simplified key code. +int simplify_key(const int key, int *modifiers) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { - int i; - int key0; - int key1; - if (*modifiers & (MOD_MASK_SHIFT | MOD_MASK_CTRL | MOD_MASK_ALT)) { - /* TAB is a special case */ + // TAB is a special case. if (key == TAB && (*modifiers & MOD_MASK_SHIFT)) { *modifiers &= ~MOD_MASK_SHIFT; return K_S_TAB; } - key0 = KEY2TERMCAP0(key); - key1 = KEY2TERMCAP1(key); - for (i = 0; modifier_keys_table[i] != NUL; i += MOD_KEYS_ENTRY_SIZE) + const int key0 = KEY2TERMCAP0(key); + const int key1 = KEY2TERMCAP1(key); + for (int i = 0; modifier_keys_table[i] != NUL; i += MOD_KEYS_ENTRY_SIZE) { if (key0 == modifier_keys_table[i + 3] && key1 == modifier_keys_table[i + 4] && (*modifiers & modifier_keys_table[i])) { *modifiers &= ~modifier_keys_table[i]; return TERMCAP2KEY(modifier_keys_table[i + 1], - modifier_keys_table[i + 2]); + modifier_keys_table[i + 2]); } + } } return key; } -/* - * Change to , to , etc. - */ -int handle_x_keys(int key) +/// Change to +int handle_x_keys(const int key) + FUNC_ATTR_CONST FUNC_ATTR_WARN_UNUSED_RESULT { switch (key) { - case K_XUP: return K_UP; - case K_XDOWN: return K_DOWN; - case K_XLEFT: return K_LEFT; - case K_XRIGHT: return K_RIGHT; - case K_XHOME: return K_HOME; - case K_ZHOME: return K_HOME; - case K_XEND: return K_END; - case K_ZEND: return K_END; - case K_XF1: return K_F1; - case K_XF2: return K_F2; - case K_XF3: return K_F3; - case K_XF4: return K_F4; - case K_S_XF1: return K_S_F1; - case K_S_XF2: return K_S_F2; - case K_S_XF3: return K_S_F3; - case K_S_XF4: return K_S_F4; + case K_XUP: return K_UP; + case K_XDOWN: return K_DOWN; + case K_XLEFT: return K_LEFT; + case K_XRIGHT: return K_RIGHT; + case K_XHOME: return K_HOME; + case K_ZHOME: return K_HOME; + case K_XEND: return K_END; + case K_ZEND: return K_END; + case K_XF1: return K_F1; + case K_XF2: return K_F2; + case K_XF3: return K_F3; + case K_XF4: return K_F4; + case K_S_XF1: return K_S_F1; + case K_S_XF2: return K_S_F2; + case K_S_XF3: return K_S_F3; + case K_S_XF4: return K_S_F4; } return key; } @@ -510,7 +508,7 @@ unsigned int trans_special(const char_u **srcp, const size_t src_len, return 0; } - /* Put the appropriate modifier in a string */ + // Put the appropriate modifier in a string. if (modifiers != 0) { dst[dlen++] = K_SPECIAL; dst[dlen++] = KS_MODIFIER; @@ -575,11 +573,7 @@ int find_special_key(const char_u **srcp, const size_t src_len, int *const modp, if (*bp == '-') { last_dash = bp; if (bp + 1 <= end) { - if (has_mbyte) { - l = mb_ptr2len_len(bp + 1, (int) (end - bp) + 1); - } else { - l = 1; - } + l = utfc_ptr2len_len(bp + 1, (int)(end - bp) + 1); // Anything accepted, like . // or are not special in strings as " is // the string delimiter. With a backslash it works: @@ -705,32 +699,35 @@ int find_special_key_in_table(int c) int i; for (i = 0; key_names_table[i].name != NULL; i++) - if (c == key_names_table[i].key) + if (c == (uint8_t)key_names_table[i].key) break; if (key_names_table[i].name == NULL) i = -1; return i; } -/* - * Find the special key with the given name (the given string does not have to - * end with NUL, the name is assumed to end before the first non-idchar). - * If the name starts with "t_" the next two characters are interpreted as a - * termcap name. - * Return the key code, or 0 if not found. - */ +/// Find the special key with the given name +/// +/// @param[in] name Name of the special. Does not have to end with NUL, it is +/// assumed to end before the first non-idchar. If name starts +/// with "t_" the next two characters are interpreted as +/// a termcap name. +/// +/// @return Key code or 0 if ton found. int get_special_key_code(const char_u *name) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { - char_u *table_name; - int i, j; - - for (i = 0; key_names_table[i].name != NULL; i++) { - table_name = key_names_table[i].name; - for (j = 0; vim_isIDc(name[j]) && table_name[j] != NUL; j++) - if (TOLOWER_ASC(table_name[j]) != TOLOWER_ASC(name[j])) + for (int i = 0; key_names_table[i].name != NULL; i++) { + const char *const table_name = key_names_table[i].name; + int j; + for (j = 0; vim_isIDc(name[j]) && table_name[j] != NUL; j++) { + if (TOLOWER_ASC(table_name[j]) != TOLOWER_ASC(name[j])) { break; - if (!vim_isIDc(name[j]) && table_name[j] == NUL) + } + } + if (!vim_isIDc(name[j]) && table_name[j] == NUL) { return key_names_table[i].key; + } } return 0; -- cgit From af38cea133f5ebb67208cedd289e408cd1dad15a Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 8 Oct 2017 21:52:38 +0300 Subject: viml/parser/expressions: Add support for string parsing --- src/nvim/mbyte.h | 2 + src/nvim/viml/parser/expressions.c | 376 +++++++++++- src/nvim/viml/parser/expressions.h | 7 + test/helpers.lua | 7 +- test/symbolic/klee/nvim/keymap.c | 540 ++++++++++++++++++ test/symbolic/klee/nvim/mbyte.c | 193 ++++++- test/symbolic/klee/viml_expressions_parser.c | 1 + test/unit/viml/expressions/parser_spec.lua | 825 +++++++++++++++++++++++++++ 8 files changed, 1938 insertions(+), 13 deletions(-) create mode 100644 test/symbolic/klee/nvim/keymap.c diff --git a/src/nvim/mbyte.h b/src/nvim/mbyte.h index bf6ccb13a5..fce600d0a9 100644 --- a/src/nvim/mbyte.h +++ b/src/nvim/mbyte.h @@ -73,6 +73,8 @@ typedef struct { extern const uint8_t utf8len_tab_zero[256]; +extern const uint8_t utf8len_tab[256]; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "mbyte.h.generated.h" #endif diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index 0613cc66d5..3f30fe2a0e 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -915,8 +915,6 @@ static inline void viml_pexpr_debug_print_token( // // NVimIdentifierKey -> Identifier // -// NVimFigureBrace -> NVimInternalError -// // NVimUnaryPlus -> NVimUnaryOperator // NVimBinaryPlus -> NVimBinaryOperator // NVimConcat -> NVimBinaryOperator @@ -929,6 +927,18 @@ static inline void viml_pexpr_debug_print_token( // NVimNestingParenthesis -> NVimParenthesis // NVimCallingParenthesis -> NVimParenthesis // +// NVimString -> String +// NVimStringSpecial -> SpecialChar +// NVimSingleQuote -> NVimString +// NVimSingleQuotedBody -> NVimString +// NVimSingleQuotedQuote -> NVimStringSpecial +// NVimDoubleQuote -> NVimString +// NVimDoubleQuotedBody -> NVimString +// NVimDoubleQuotedEscape -> NVimStringSpecial +// NVimDoubleQuotedUnknownEscape -> NVimInvalid +// +// " Note: NVimDoubleQuotedUnknownEscape is not actually invalid +// // NVimInvalidComma -> NVimInvalidDelimiter // NVimInvalidSpacing -> NVimInvalid // NVimInvalidTernary -> NVimInvalidOperator @@ -952,6 +962,19 @@ static inline void viml_pexpr_debug_print_token( // NVimInvalidList -> NVimInvalidDelimiter // NVimInvalidSubscript -> NVimInvalidDelimiter // NVimInvalidSubscriptColon -> NVimInvalidSubscript +// NVimInvalidString -> NVimInvalidValue +// NVimInvalidStringSpecial -> NVimInvalidString +// NVimInvalidSingleQuote -> NVimInvalidString +// NVimInvalidSingleQuotedBody -> NVimInvalidString +// NVimInvalidSingleQuotedQuote -> NVimInvalidStringSpecial +// NVimInvalidDoubleQuote -> NVimInvalidString +// NVimInvalidDoubleQuotedBody -> NVimInvalidString +// NVimInvalidDoubleQuotedEscape -> NVimInvalidStringSpecial +// NVimInvalidDoubleQuotedUnknownEscape -> NVimInvalid +// +// NVimFigureBrace -> NVimInternalError +// NVimInvalidSingleQuotedUnknownEscape -> NVimInternalError +// NVimSingleQuotedUnknownEscape -> NVimInternalError /// Allocate a new node and set some of the values /// @@ -1402,6 +1425,318 @@ static inline void east_set_error(const ParserState *const pstate, } \ } while (0) +/// Structure used to define “string shifts” necessary to map string +/// highlighting to actual strings. +typedef struct { + size_t start; ///< Where special character starts in original string. + size_t orig_len; ///< Length of orininal string (e.g. 4 for "\x80"). + size_t act_len; ///< Length of resulting character(s) (e.g. 1 for "\x80"). + bool escape_not_known; ///< True if escape sequence in original is not known. +} StringShift; + +/// Parse and highlight single- or double-quoted string +/// +/// Function is supposed to detect and highlight regular expressions (but does +/// not do now). +/// +/// @param[out] pstate Parser state which also contains a place where +/// highlighting is saved. +/// @param[out] node Node where string parsing results are saved. +/// @param[in] token Token to highlight. +/// @param[in] ast_stack Parser AST stack, used to detect whether current +/// string is a regex. +/// @param[in] is_invalid Whether currently processed token is not valid. +static void parse_quoted_string(ParserState *const pstate, + ExprASTNode *const node, + const LexExprToken token, + const ExprASTStack ast_stack, + const bool is_invalid) + FUNC_ATTR_NONNULL_ALL +{ + const ParserLine pline = pstate->reader.lines.items[token.start.line]; + const char *const s = pline.data + token.start.col; + const char *const e = s + token.len - token.data.str.closed; + const char *p = s + 1; + const bool is_double = (token.type == kExprLexDoubleQuotedString); + size_t size = token.len - token.data.str.closed - 1; + kvec_withinit_t(StringShift, 16) shifts; + kvi_init(shifts); + if (!is_double) { + viml_parser_highlight(pstate, token.start, 1, HL(SingleQuotedString)); + while (p < e) { + const char *const chunk_e = memchr(p, '\'', (size_t)(e - p)); + if (chunk_e == NULL) { + break; + } + size--; + p = chunk_e + 2; + if (pstate->colors) { + kvi_push(shifts, ((StringShift) { + .start = token.start.col + (size_t)(chunk_e - s), + .orig_len = 2, + .act_len = 1, + .escape_not_known = false, + })); + } + } + node->data.str.size = size; + if (size == 0) { + node->data.str.value = NULL; + } else { + char *v_p; + v_p = node->data.str.value = xmalloc(size); + p = s + 1; + while (p < e) { + const char *const chunk_e = memchr(p, '\'', (size_t)(e - p)); + if (chunk_e == NULL) { + memcpy(v_p, p, (size_t)(e - p)); + break; + } + memcpy(v_p, p, (size_t)(chunk_e - p)); + v_p += (size_t)(chunk_e - p) + 1; + v_p[-1] = '\''; + p = chunk_e + 2; + } + } + } else { + viml_parser_highlight(pstate, token.start, 1, HL(DoubleQuotedString)); + for (p = s + 1; p < e; p++) { + if (*p == '\\' && p + 1 < e) { + p++; + if (p + 1 == e) { + size--; + break; + } + switch (*p) { + // A "\" form occupies at least 4 characters, and produces up to + // 6 characters: reserve space for 2 extra, but do not compute actual + // length just now, it would be costy. + case '<': { + size += 2; + break; + } + // Hexadecimal, always single byte, but at least three bytes each. + case 'x': case 'X': { + size--; + if (ascii_isxdigit(p[1])) { + size--; + if (p + 2 < e && ascii_isxdigit(p[2])) { + size--; + } + } + break; + } + // Unicode + // + // \uF takes 1 byte which is 2 bytes less then escape sequence. + // \uFF: 2 bytes, 2 bytes less. + // \uFFF: 3 bytes, 2 bytes less. + // \uFFFF: 3 bytes, 3 bytes less. + // \UFFFFF: 4 bytes, 3 bytes less. + // \UFFFFFF: 5 bytes, 3 bytes less. + // \UFFFFFFF: 6 bytes, 3 bytes less. + // \U7FFFFFFF: 6 bytes, 4 bytes less. + case 'u': case 'U': { + const char *const esc_start = p; + size_t n = (*p == 'u' ? 4 : 8); + int nr = 0; + p++; + while (n-- && ascii_isxdigit(p[1])) { + p++; + nr = (nr << 4) + hex2nr(*p); + } + // Escape length: (esc_start - 1) points to "\\", esc_start to "u" + // or "U", p to the byte after last byte. So escape sequence + // occupies p - (esc_start - 1), but it stands for a utf_char2len + // bytes. + size -= (size_t)((p - (esc_start - 1)) - utf_char2len(nr)); + p--; + break; + } + // Octal, always single byte, but at least two bytes each. + case '0': case '1': case '2': case '3': case '4': case '5': case '6': + case '7': { + size--; + p++; + if (*p >= '0' && *p <= '7') { + size--; + p++; + if (*p >= '0' && *p <= '7') { + size--; + p++; + } + } + break; + } + default: { + size--; + break; + } + } + } + } + if (size == 0) { + node->data.str.value = NULL; + node->data.str.size = 0; + } else { + char *v_p; + v_p = node->data.str.value = xmalloc(size); + p = s + 1; + while (p < e) { + const char *const chunk_e = memchr(p, '\\', (size_t)(e - p)); + if (chunk_e == NULL) { + memcpy(v_p, p, (size_t)(e - p)); + v_p += e - p; + break; + } + memcpy(v_p, p, (size_t)(chunk_e - p)); + v_p += (size_t)(chunk_e - p); + p = chunk_e + 1; + if (p == e) { + *v_p++ = '\\'; + break; + } + bool is_unknown = false; + const char *const v_p_start = v_p; + switch (*p) { +#define SINGLE_CHAR_ESC(ch, real_ch) \ + case ch: { \ + *v_p++ = real_ch; \ + p++; \ + break; \ + } + SINGLE_CHAR_ESC('b', BS) + SINGLE_CHAR_ESC('e', ESC) + SINGLE_CHAR_ESC('f', FF) + SINGLE_CHAR_ESC('n', NL) + SINGLE_CHAR_ESC('r', CAR) + SINGLE_CHAR_ESC('t', TAB) + SINGLE_CHAR_ESC('"', '"') + SINGLE_CHAR_ESC('\\', '\\') +#undef SINGLE_CHAR_ESC + + // Hexadecimal or unicode. + case 'X': case 'x': case 'u': case 'U': { + if (ascii_isxdigit(p[1])) { + size_t n; + int nr; + bool is_hex = (*p == 'x' || *p == 'X'); + + if (is_hex) { + n = 2; + } else if (*p == 'u') { + n = 4; + } else { + n = 8; + } + nr = 0; + while (n-- && ascii_isxdigit(p[1])) { + p++; + nr = (nr << 4) + hex2nr(*p); + } + p++; + if (is_hex) { + *v_p++ = (char)nr; + } else { + v_p += utf_char2bytes(nr, (char_u *)v_p); + } + } else { + is_unknown = true; + *v_p++ = *p; + p++; + } + break; + } + // Octal: "\1", "\12", "\123". + case '0': case '1': case '2': case '3': case '4': case '5': case '6': + case '7': { + uint8_t ch = (uint8_t)(*p++ - '0'); + if (*p >= '0' && *p <= '7') { + ch = (uint8_t)((ch << 3) + *p++ - '0'); + if (*p >= '0' && *p <= '7') { + ch = (uint8_t)((ch << 3) + *p++ - '0'); + } + } + *v_p++ = (char)ch; + break; + } + // Special key, e.g.: "\" + case '<': { + const size_t special_len = ( + trans_special((const char_u **)&p, (size_t)(e - p), + (char_u *)v_p, true, true)); + if (special_len != 0) { + v_p += special_len; + } else { + is_unknown = true; + mb_copy_char((const char_u **)&p, (char_u **)&v_p); + } + break; + } + default: { + is_unknown = true; + mb_copy_char((const char_u **)&p, (char_u **)&v_p); + break; + } + } + if (pstate->colors) { + kvi_push(shifts, ((StringShift) { + .start = token.start.col + (size_t)(chunk_e - s), + .orig_len = (size_t)(p - chunk_e), + .act_len = (size_t)(v_p - (char *)v_p_start), + .escape_not_known = is_unknown, + })); + } + } + node->data.str.size = (size_t)(v_p - node->data.str.value); + } + } + if (pstate->colors) { + // TODO(ZyX-I): use ast_stack to determine and highlight regular expressions + // TODO(ZyX-I): use ast_stack to determine and highlight printf format str + // TODO(ZyX-I): use ast_stack to determine and highlight expression strings + size_t next_col = 1; + const char *const body_str = (is_double + ? HL(DoubleQuotedBody) + : HL(SingleQuotedBody)); + const char *const esc_str = (is_double + ? HL(DoubleQuotedEscape) + : HL(SingleQuotedQuote)); + const char *const ukn_esc_str = (is_double + ? HL(DoubleQuotedUnknownEscape) + : HL(SingleQuotedUnknownEscape)); + for (size_t i = 0; i < kv_size(shifts); i++) { + const StringShift cur_shift = kv_A(shifts, i); + if (cur_shift.start > next_col) { + viml_parser_highlight(pstate, shifted_pos(token.start, next_col), + cur_shift.start - next_col, + body_str); + } + viml_parser_highlight(pstate, shifted_pos(token.start, cur_shift.start), + cur_shift.orig_len, + (cur_shift.escape_not_known + ? ukn_esc_str + : esc_str)); + next_col = cur_shift.start + cur_shift.orig_len; + } + if (next_col < token.len - token.data.str.closed) { + viml_parser_highlight(pstate, shifted_pos(token.start, next_col), + token.len - token.data.str.closed - next_col, + body_str); + } + } + if (token.data.str.closed) { + if (is_double) { + viml_parser_highlight(pstate, shifted_pos(token.start, token.len - 1), + 1, HL(DoubleQuotedString)); + } else { + viml_parser_highlight(pstate, shifted_pos(token.start, token.len - 1), + 1, HL(SingleQuotedString)); + } + } + kvi_destroy(shifts); +} + /// Parse one VimL expression /// /// @param pstate Parser state. @@ -1714,12 +2049,7 @@ viml_pexpr_parse_invalid_comma: } else if (eastnode_lvl >= kEOpLvlComma) { can_be_ternary = false; } else { -viml_pexpr_parse_invalid_colon: - ERROR_FROM_TOKEN_AND_MSG( - cur_token, - _("E15: Colon outside of dictionary or ternary operator: " - "%.*s")); - break; + goto viml_pexpr_parse_invalid_colon; } if (i == kv_size(ast_stack) - 1) { goto viml_pexpr_parse_invalid_colon; @@ -1741,6 +2071,12 @@ viml_pexpr_parse_invalid_colon: ADD_OP_NODE(cur_node); HL_CUR_TOKEN(SubscriptColon); } else { + goto viml_pexpr_parse_valid_colon; +viml_pexpr_parse_invalid_colon: + ERROR_FROM_TOKEN_AND_MSG( + cur_token, + _("E15: Colon outside of dictionary or ternary operator: %.*s")); +viml_pexpr_parse_valid_colon: ADD_VALUE_IF_MISSING(_(EXP_VAL_COLON)); NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeColon); if (is_ternary) { @@ -2201,6 +2537,30 @@ viml_pexpr_parse_no_paren_closing_error: {} kvi_push(ast_stack, &ter_val_node->children); break; } + case kExprLexDoubleQuotedString: + case kExprLexSingleQuotedString: { + const bool is_double = (tok_type == kExprLexDoubleQuotedString); + if (!cur_token.data.str.closed) { + // It is weird, but Vim has two identical errors messages with + // different error numbers: "E114: Missing quote" and + // "E115: Missing quote". + ERROR_FROM_TOKEN_AND_MSG( + cur_token, (is_double + ? _("E114: Missing double quote: %.*s") + : _("E115: Missing single quote: %.*s"))); + } + if (want_node == kENodeOperator) { + OP_MISSING; + } + NEW_NODE_WITH_CUR_POS( + cur_node, (is_double + ? kExprNodeDoubleQuotedString + : kExprNodeSingleQuotedString)); + *top_node_p = cur_node; + parse_quoted_string(pstate, cur_node, cur_token, ast_stack, is_invalid); + want_node = kENodeOperator; + break; + } } viml_pexpr_parse_cycle_end: prev_token = cur_token; diff --git a/src/nvim/viml/parser/expressions.h b/src/nvim/viml/parser/expressions.h index 0d496c87ba..a09cdde4c0 100644 --- a/src/nvim/viml/parser/expressions.h +++ b/src/nvim/viml/parser/expressions.h @@ -195,6 +195,8 @@ typedef enum { kExprNodeConcatOrSubscript = 'S', kExprNodeInteger = '0', ///< Integral number. kExprNodeFloat = '1', ///< Floating-point number. + kExprNodeSingleQuotedString = '\'', + kExprNodeDoubleQuotedString = '"', } ExprASTNodeType; typedef struct expr_ast_node ExprASTNode; @@ -249,6 +251,11 @@ struct expr_ast_node { struct { float_T value; } flt; ///< For kExprNodeFloat. + struct { + char *value; + size_t size; + } str; ///< For kExprNodeSingleQuotedString and + ///< kExprNodeDoubleQuotedString. } data; }; diff --git a/test/helpers.lua b/test/helpers.lua index d5356416af..e7d8c185ba 100644 --- a/test/helpers.lua +++ b/test/helpers.lua @@ -330,9 +330,10 @@ format_luav = function(v, indent) end local ret = '' if type(v) == 'string' then - ret = '\'' .. tostring(v):gsub('[\'\\]', '\\%0'):gsub('[%z\1-\31]', function(match) - return SUBTBL[match:byte()] - end) .. '\'' + ret = tostring(v):gsub('[\'\\]', '\\%0'):gsub('[%z\1-\31]', function(match) + return SUBTBL[match:byte() + 1] + end) + ret = '\'' .. ret .. '\'' elseif type(v) == 'table' then local processed_keys = {} ret = '{' .. linesep diff --git a/test/symbolic/klee/nvim/keymap.c b/test/symbolic/klee/nvim/keymap.c new file mode 100644 index 0000000000..c59d8d4d6e --- /dev/null +++ b/test/symbolic/klee/nvim/keymap.c @@ -0,0 +1,540 @@ +#include + +#include "nvim/types.h" +#include "nvim/keymap.h" +#include "nvim/eval/typval.h" + +#define MOD_KEYS_ENTRY_SIZE 5 + +static char_u modifier_keys_table[] = +{ + MOD_MASK_SHIFT, '&', '9', '@', '1', + MOD_MASK_SHIFT, '&', '0', '@', '2', + MOD_MASK_SHIFT, '*', '1', '@', '4', + MOD_MASK_SHIFT, '*', '2', '@', '5', + MOD_MASK_SHIFT, '*', '3', '@', '6', + MOD_MASK_SHIFT, '*', '4', 'k', 'D', + MOD_MASK_SHIFT, '*', '5', 'k', 'L', + MOD_MASK_SHIFT, '*', '7', '@', '7', + MOD_MASK_CTRL, KS_EXTRA, (int)KE_C_END, '@', '7', + MOD_MASK_SHIFT, '*', '9', '@', '9', + MOD_MASK_SHIFT, '*', '0', '@', '0', + MOD_MASK_SHIFT, '#', '1', '%', '1', + MOD_MASK_SHIFT, '#', '2', 'k', 'h', + MOD_MASK_CTRL, KS_EXTRA, (int)KE_C_HOME, 'k', 'h', + MOD_MASK_SHIFT, '#', '3', 'k', 'I', + MOD_MASK_SHIFT, '#', '4', 'k', 'l', + MOD_MASK_CTRL, KS_EXTRA, (int)KE_C_LEFT, 'k', 'l', + MOD_MASK_SHIFT, '%', 'a', '%', '3', + MOD_MASK_SHIFT, '%', 'b', '%', '4', + MOD_MASK_SHIFT, '%', 'c', '%', '5', + MOD_MASK_SHIFT, '%', 'd', '%', '7', + MOD_MASK_SHIFT, '%', 'e', '%', '8', + MOD_MASK_SHIFT, '%', 'f', '%', '9', + MOD_MASK_SHIFT, '%', 'g', '%', '0', + MOD_MASK_SHIFT, '%', 'h', '&', '3', + MOD_MASK_SHIFT, '%', 'i', 'k', 'r', + MOD_MASK_CTRL, KS_EXTRA, (int)KE_C_RIGHT, 'k', 'r', + MOD_MASK_SHIFT, '%', 'j', '&', '5', + MOD_MASK_SHIFT, '!', '1', '&', '6', + MOD_MASK_SHIFT, '!', '2', '&', '7', + MOD_MASK_SHIFT, '!', '3', '&', '8', + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_UP, 'k', 'u', + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_DOWN, 'k', 'd', + + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_XF1, KS_EXTRA, (int)KE_XF1, + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_XF2, KS_EXTRA, (int)KE_XF2, + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_XF3, KS_EXTRA, (int)KE_XF3, + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_XF4, KS_EXTRA, (int)KE_XF4, + + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F1, 'k', '1', + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F2, 'k', '2', + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F3, 'k', '3', + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F4, 'k', '4', + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F5, 'k', '5', + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F6, 'k', '6', + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F7, 'k', '7', + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F8, 'k', '8', + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F9, 'k', '9', + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F10, 'k', ';', + + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F11, 'F', '1', + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F12, 'F', '2', + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F13, 'F', '3', + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F14, 'F', '4', + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F15, 'F', '5', + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F16, 'F', '6', + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F17, 'F', '7', + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F18, 'F', '8', + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F19, 'F', '9', + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F20, 'F', 'A', + + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F21, 'F', 'B', + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F22, 'F', 'C', + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F23, 'F', 'D', + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F24, 'F', 'E', + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F25, 'F', 'F', + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F26, 'F', 'G', + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F27, 'F', 'H', + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F28, 'F', 'I', + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F29, 'F', 'J', + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F30, 'F', 'K', + + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F31, 'F', 'L', + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F32, 'F', 'M', + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F33, 'F', 'N', + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F34, 'F', 'O', + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F35, 'F', 'P', + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F36, 'F', 'Q', + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F37, 'F', 'R', + + MOD_MASK_SHIFT, 'k', 'B', KS_EXTRA, (int)KE_TAB, + + NUL +}; + +int simplify_key(const int key, int *modifiers) +{ + if (*modifiers & (MOD_MASK_SHIFT | MOD_MASK_CTRL | MOD_MASK_ALT)) { + // TAB is a special case. + if (key == TAB && (*modifiers & MOD_MASK_SHIFT)) { + *modifiers &= ~MOD_MASK_SHIFT; + return K_S_TAB; + } + const int key0 = KEY2TERMCAP0(key); + const int key1 = KEY2TERMCAP1(key); + for (int i = 0; modifier_keys_table[i] != NUL; i += MOD_KEYS_ENTRY_SIZE) { + if (key0 == modifier_keys_table[i + 3] + && key1 == modifier_keys_table[i + 4] + && (*modifiers & modifier_keys_table[i])) { + *modifiers &= ~modifier_keys_table[i]; + return TERMCAP2KEY(modifier_keys_table[i + 1], + modifier_keys_table[i + 2]); + } + } + } + return key; +} + +int handle_x_keys(const int key) + FUNC_ATTR_CONST FUNC_ATTR_WARN_UNUSED_RESULT +{ + switch (key) { + case K_XUP: return K_UP; + case K_XDOWN: return K_DOWN; + case K_XLEFT: return K_LEFT; + case K_XRIGHT: return K_RIGHT; + case K_XHOME: return K_HOME; + case K_ZHOME: return K_HOME; + case K_XEND: return K_END; + case K_ZEND: return K_END; + case K_XF1: return K_F1; + case K_XF2: return K_F2; + case K_XF3: return K_F3; + case K_XF4: return K_F4; + case K_S_XF1: return K_S_F1; + case K_S_XF2: return K_S_F2; + case K_S_XF3: return K_S_F3; + case K_S_XF4: return K_S_F4; + } + return key; +} + +static const struct key_name_entry { + int key; ///< Special key code or ASCII value. + const char *name; ///< Name of the key +} key_names_table[] = { + {' ', "Space"}, + {TAB, "Tab"}, + {K_TAB, "Tab"}, + {NL, "NL"}, + {NL, "NewLine"}, // Alternative name + {NL, "LineFeed"}, // Alternative name + {NL, "LF"}, // Alternative name + {CAR, "CR"}, + {CAR, "Return"}, // Alternative name + {CAR, "Enter"}, // Alternative name + {K_BS, "BS"}, + {K_BS, "BackSpace"}, // Alternative name + {ESC, "Esc"}, + {CSI, "CSI"}, + {K_CSI, "xCSI"}, + {'|', "Bar"}, + {'\\', "Bslash"}, + {K_DEL, "Del"}, + {K_DEL, "Delete"}, // Alternative name + {K_KDEL, "kDel"}, + {K_UP, "Up"}, + {K_DOWN, "Down"}, + {K_LEFT, "Left"}, + {K_RIGHT, "Right"}, + {K_XUP, "xUp"}, + {K_XDOWN, "xDown"}, + {K_XLEFT, "xLeft"}, + {K_XRIGHT, "xRight"}, + + {K_F1, "F1"}, + {K_F2, "F2"}, + {K_F3, "F3"}, + {K_F4, "F4"}, + {K_F5, "F5"}, + {K_F6, "F6"}, + {K_F7, "F7"}, + {K_F8, "F8"}, + {K_F9, "F9"}, + {K_F10, "F10"}, + + {K_F11, "F11"}, + {K_F12, "F12"}, + {K_F13, "F13"}, + {K_F14, "F14"}, + {K_F15, "F15"}, + {K_F16, "F16"}, + {K_F17, "F17"}, + {K_F18, "F18"}, + {K_F19, "F19"}, + {K_F20, "F20"}, + + {K_F21, "F21"}, + {K_F22, "F22"}, + {K_F23, "F23"}, + {K_F24, "F24"}, + {K_F25, "F25"}, + {K_F26, "F26"}, + {K_F27, "F27"}, + {K_F28, "F28"}, + {K_F29, "F29"}, + {K_F30, "F30"}, + + {K_F31, "F31"}, + {K_F32, "F32"}, + {K_F33, "F33"}, + {K_F34, "F34"}, + {K_F35, "F35"}, + {K_F36, "F36"}, + {K_F37, "F37"}, + + {K_XF1, "xF1"}, + {K_XF2, "xF2"}, + {K_XF3, "xF3"}, + {K_XF4, "xF4"}, + + {K_HELP, "Help"}, + {K_UNDO, "Undo"}, + {K_INS, "Insert"}, + {K_INS, "Ins"}, // Alternative name + {K_KINS, "kInsert"}, + {K_HOME, "Home"}, + {K_KHOME, "kHome"}, + {K_XHOME, "xHome"}, + {K_ZHOME, "zHome"}, + {K_END, "End"}, + {K_KEND, "kEnd"}, + {K_XEND, "xEnd"}, + {K_ZEND, "zEnd"}, + {K_PAGEUP, "PageUp"}, + {K_PAGEDOWN, "PageDown"}, + {K_KPAGEUP, "kPageUp"}, + {K_KPAGEDOWN, "kPageDown"}, + + {K_KPLUS, "kPlus"}, + {K_KMINUS, "kMinus"}, + {K_KDIVIDE, "kDivide"}, + {K_KMULTIPLY, "kMultiply"}, + {K_KENTER, "kEnter"}, + {K_KPOINT, "kPoint"}, + + {K_K0, "k0"}, + {K_K1, "k1"}, + {K_K2, "k2"}, + {K_K3, "k3"}, + {K_K4, "k4"}, + {K_K5, "k5"}, + {K_K6, "k6"}, + {K_K7, "k7"}, + {K_K8, "k8"}, + {K_K9, "k9"}, + + {'<', "lt"}, + + {K_MOUSE, "Mouse"}, + {K_LEFTMOUSE, "LeftMouse"}, + {K_LEFTMOUSE_NM, "LeftMouseNM"}, + {K_LEFTDRAG, "LeftDrag"}, + {K_LEFTRELEASE, "LeftRelease"}, + {K_LEFTRELEASE_NM, "LeftReleaseNM"}, + {K_MIDDLEMOUSE, "MiddleMouse"}, + {K_MIDDLEDRAG, "MiddleDrag"}, + {K_MIDDLERELEASE, "MiddleRelease"}, + {K_RIGHTMOUSE, "RightMouse"}, + {K_RIGHTDRAG, "RightDrag"}, + {K_RIGHTRELEASE, "RightRelease"}, + {K_MOUSEDOWN, "ScrollWheelUp"}, + {K_MOUSEUP, "ScrollWheelDown"}, + {K_MOUSELEFT, "ScrollWheelRight"}, + {K_MOUSERIGHT, "ScrollWheelLeft"}, + {K_MOUSEDOWN, "MouseDown"}, // OBSOLETE: Use ScrollWheelXXX instead + {K_MOUSEUP, "MouseUp"}, // Same + {K_X1MOUSE, "X1Mouse"}, + {K_X1DRAG, "X1Drag"}, + {K_X1RELEASE, "X1Release"}, + {K_X2MOUSE, "X2Mouse"}, + {K_X2DRAG, "X2Drag"}, + {K_X2RELEASE, "X2Release"}, + {K_DROP, "Drop"}, + {K_ZERO, "Nul"}, + {K_SNR, "SNR"}, + {K_PLUG, "Plug"}, + {K_PASTE, "Paste"}, + {K_FOCUSGAINED, "FocusGained"}, + {K_FOCUSLOST, "FocusLost"}, + {0, NULL} +}; + +int get_special_key_code(const char_u *name) +{ + for (int i = 0; key_names_table[i].name != NULL; i++) { + const char *const table_name = key_names_table[i].name; + int j; + for (j = 0; vim_isIDc(name[j]) && table_name[j] != NUL; j++) { + if (TOLOWER_ASC(table_name[j]) != TOLOWER_ASC(name[j])) { + break; + } + } + if (!vim_isIDc(name[j]) && table_name[j] == NUL) { + return key_names_table[i].key; + } + } + + return 0; +} + + +static const struct modmasktable { + short mod_mask; ///< Bit-mask for particular key modifier. + short mod_flag; ///< Bit(s) for particular key modifier. + char_u name; ///< Single letter name of modifier. +} mod_mask_table[] = { + {MOD_MASK_ALT, MOD_MASK_ALT, (char_u)'M'}, + {MOD_MASK_META, MOD_MASK_META, (char_u)'T'}, + {MOD_MASK_CTRL, MOD_MASK_CTRL, (char_u)'C'}, + {MOD_MASK_SHIFT, MOD_MASK_SHIFT, (char_u)'S'}, + {MOD_MASK_MULTI_CLICK, MOD_MASK_2CLICK, (char_u)'2'}, + {MOD_MASK_MULTI_CLICK, MOD_MASK_3CLICK, (char_u)'3'}, + {MOD_MASK_MULTI_CLICK, MOD_MASK_4CLICK, (char_u)'4'}, + {MOD_MASK_CMD, MOD_MASK_CMD, (char_u)'D'}, + // 'A' must be the last one + {MOD_MASK_ALT, MOD_MASK_ALT, (char_u)'A'}, + {0, 0, NUL} +}; + +int name_to_mod_mask(int c) +{ + c = TOUPPER_ASC(c); + for (size_t i = 0; mod_mask_table[i].mod_mask != 0; i++) { + if (c == mod_mask_table[i].name) { + return mod_mask_table[i].mod_flag; + } + } + return 0; +} + +static int extract_modifiers(int key, int *modp) +{ + int modifiers = *modp; + + if (!(modifiers & MOD_MASK_CMD)) { // Command-key is special + if ((modifiers & MOD_MASK_SHIFT) && ASCII_ISALPHA(key)) { + key = TOUPPER_ASC(key); + modifiers &= ~MOD_MASK_SHIFT; + } + } + if ((modifiers & MOD_MASK_CTRL) + && ((key >= '?' && key <= '_') || ASCII_ISALPHA(key))) { + key = Ctrl_chr(key); + modifiers &= ~MOD_MASK_CTRL; + if (key == 0) { // is + key = K_ZERO; + } + } + + *modp = modifiers; + return key; +} + +int find_special_key(const char_u **srcp, const size_t src_len, int *const modp, + const bool keycode, const bool keep_x_key, + const bool in_string) +{ + const char_u *last_dash; + const char_u *end_of_name; + const char_u *src; + const char_u *bp; + const char_u *const end = *srcp + src_len - 1; + int modifiers; + int bit; + int key; + uvarnumber_T n; + int l; + + if (src_len == 0) { + return 0; + } + + src = *srcp; + if (src[0] != '<') { + return 0; + } + + // Find end of modifier list + last_dash = src; + for (bp = src + 1; bp <= end && (*bp == '-' || vim_isIDc(*bp)); bp++) { + if (*bp == '-') { + last_dash = bp; + if (bp + 1 <= end) { + l = utfc_ptr2len_len(bp + 1, (int)(end - bp) + 1); + // Anything accepted, like . + // or are not special in strings as " is + // the string delimiter. With a backslash it works: + if (end - bp > l && !(in_string && bp[1] == '"') && bp[2] == '>') { + bp += l; + } else if (end - bp > 2 && in_string && bp[1] == '\\' + && bp[2] == '"' && bp[3] == '>') { + bp += 2; + } + } + } + if (end - bp > 3 && bp[0] == 't' && bp[1] == '_') { + bp += 3; // skip t_xx, xx may be '-' or '>' + } else if (end - bp > 4 && STRNICMP(bp, "char-", 5) == 0) { + vim_str2nr(bp + 5, NULL, &l, STR2NR_ALL, NULL, NULL, 0); + bp += l + 5; + break; + } + } + + if (bp <= end && *bp == '>') { // found matching '>' + end_of_name = bp + 1; + + /* Which modifiers are given? */ + modifiers = 0x0; + for (bp = src + 1; bp < last_dash; bp++) { + if (*bp != '-') { + bit = name_to_mod_mask(*bp); + if (bit == 0x0) { + break; // Illegal modifier name + } + modifiers |= bit; + } + } + + // Legal modifier name. + if (bp >= last_dash) { + if (STRNICMP(last_dash + 1, "char-", 5) == 0 + && ascii_isdigit(last_dash[6])) { + // or or + vim_str2nr(last_dash + 6, NULL, NULL, STR2NR_ALL, NULL, &n, 0); + key = (int)n; + } else { + int off = 1; + + // Modifier with single letter, or special key name. + if (in_string && last_dash[1] == '\\' && last_dash[2] == '"') { + off = 2; + } + l = mb_ptr2len(last_dash + 1); + if (modifiers != 0 && last_dash[l + 1] == '>') { + key = PTR2CHAR(last_dash + off); + } else { + key = get_special_key_code(last_dash + off); + if (!keep_x_key) { + key = handle_x_keys(key); + } + } + } + + // get_special_key_code() may return NUL for invalid + // special key name. + if (key != NUL) { + // Only use a modifier when there is no special key code that + // includes the modifier. + key = simplify_key(key, &modifiers); + + if (!keycode) { + // don't want keycode, use single byte code + if (key == K_BS) { + key = BS; + } else if (key == K_DEL || key == K_KDEL) { + key = DEL; + } + } + + // Normal Key with modifier: + // Try to make a single byte code (except for Alt/Meta modifiers). + if (!IS_SPECIAL(key)) { + key = extract_modifiers(key, &modifiers); + } + + *modp = modifiers; + *srcp = end_of_name; + return key; + } + } + } + return 0; +} + +char_u *add_char2buf(int c, char_u *s) +{ + char_u temp[MB_MAXBYTES + 1]; + const int len = utf_char2bytes(c, temp); + for (int i = 0; i < len; ++i) { + c = temp[i]; + // Need to escape K_SPECIAL and CSI like in the typeahead buffer. + if (c == K_SPECIAL) { + *s++ = K_SPECIAL; + *s++ = KS_SPECIAL; + *s++ = KE_FILLER; + } else { + *s++ = c; + } + } + return s; +} + +unsigned int trans_special(const char_u **srcp, const size_t src_len, + char_u *const dst, const bool keycode, + const bool in_string) +{ + int modifiers = 0; + int key; + unsigned int dlen = 0; + + key = find_special_key(srcp, src_len, &modifiers, keycode, false, in_string); + if (key == 0) { + return 0; + } + + // Put the appropriate modifier in a string. + if (modifiers != 0) { + dst[dlen++] = K_SPECIAL; + dst[dlen++] = KS_MODIFIER; + dst[dlen++] = (char_u)modifiers; + } + + if (IS_SPECIAL(key)) { + dst[dlen++] = K_SPECIAL; + dst[dlen++] = (char_u)KEY2TERMCAP0(key); + dst[dlen++] = KEY2TERMCAP1(key); + } else if (has_mbyte && !keycode) { + dlen += (unsigned int)(*mb_char2bytes)(key, dst + dlen); + } else if (keycode) { + char_u *after = add_char2buf(key, dst + dlen); + assert(after >= dst && (uintmax_t)(after - dst) <= UINT_MAX); + dlen = (unsigned int)(after - dst); + } else { + dst[dlen++] = (char_u)key; + } + + return dlen; +} diff --git a/test/symbolic/klee/nvim/mbyte.c b/test/symbolic/klee/nvim/mbyte.c index 394d17b700..bfc191b1b7 100644 --- a/test/symbolic/klee/nvim/mbyte.c +++ b/test/symbolic/klee/nvim/mbyte.c @@ -1,9 +1,89 @@ #include +#include +#include +#include #include "nvim/types.h" #include "nvim/mbyte.h" #include "nvim/ascii.h" +const uint8_t utf8len_tab_zero[] = { + //1 2 3 4 5 6 7 8 9 A B C D E F 0 1 2 3 4 5 6 7 8 9 A B C D E F + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 0 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 2 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 4 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 6 + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 8 + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // A + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, // C + 3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,5,5,5,5,6,6,0,0, // E +}; + +const uint8_t utf8len_tab[] = { + // ?1 ?2 ?3 ?4 ?5 ?6 ?7 ?8 ?9 ?A ?B ?C ?D ?E ?F + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 1? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 2? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 3? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 4? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 5? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 6? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 7? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 8? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 9? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // A? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // B? + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // C? + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // D? + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, // E? + 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 1, 1, // F? +}; + +int utf_ptr2char(const char_u *const p) +{ + if (p[0] < 0x80) { // Be quick for ASCII. + return p[0]; + } + + const uint8_t len = utf8len_tab_zero[p[0]]; + if (len > 1 && (p[1] & 0xc0) == 0x80) { + if (len == 2) { + return ((p[0] & 0x1f) << 6) + (p[1] & 0x3f); + } + if ((p[2] & 0xc0) == 0x80) { + if (len == 3) { + return (((p[0] & 0x0f) << 12) + ((p[1] & 0x3f) << 6) + + (p[2] & 0x3f)); + } + if ((p[3] & 0xc0) == 0x80) { + if (len == 4) { + return (((p[0] & 0x07) << 18) + ((p[1] & 0x3f) << 12) + + ((p[2] & 0x3f) << 6) + (p[3] & 0x3f)); + } + if ((p[4] & 0xc0) == 0x80) { + if (len == 5) { + return (((p[0] & 0x03) << 24) + ((p[1] & 0x3f) << 18) + + ((p[2] & 0x3f) << 12) + ((p[3] & 0x3f) << 6) + + (p[4] & 0x3f)); + } + if ((p[5] & 0xc0) == 0x80 && len == 6) { + return (((p[0] & 0x01) << 30) + ((p[1] & 0x3f) << 24) + + ((p[2] & 0x3f) << 18) + ((p[3] & 0x3f) << 12) + + ((p[4] & 0x3f) << 6) + (p[5] & 0x3f)); + } + } + } + } + } + // Illegal value: just return the first byte. + return p[0]; +} + +bool utf_composinglike(const char_u *p1, const char_u *p2) +{ + return false; +} + char_u *string_convert(const vimconv_T *conv, char_u *data, size_t *size) { return NULL; @@ -11,8 +91,117 @@ char_u *string_convert(const vimconv_T *conv, char_u *data, size_t *size) int utfc_ptr2len_len(const char_u *p, int size) { - if (size < 1 || *p == NUL) { + assert(false); + return 0; +} + +int utf_char2len(const int c) +{ + if (c < 0x80) { + return 1; + } else if (c < 0x800) { + return 2; + } else if (c < 0x10000) { + return 3; + } else if (c < 0x200000) { + return 4; + } else if (c < 0x4000000) { + return 5; + } else { + return 6; + } +} + +int utf_char2bytes(const int c, char_u *const buf) +{ + if (c < 0x80) { // 7 bits + buf[0] = c; + return 1; + } else if (c < 0x800) { // 11 bits + buf[0] = 0xc0 + ((unsigned)c >> 6); + buf[1] = 0x80 + (c & 0x3f); + return 2; + } else if (c < 0x10000) { // 16 bits + buf[0] = 0xe0 + ((unsigned)c >> 12); + buf[1] = 0x80 + (((unsigned)c >> 6) & 0x3f); + buf[2] = 0x80 + (c & 0x3f); + return 3; + } else if (c < 0x200000) { // 21 bits + buf[0] = 0xf0 + ((unsigned)c >> 18); + buf[1] = 0x80 + (((unsigned)c >> 12) & 0x3f); + buf[2] = 0x80 + (((unsigned)c >> 6) & 0x3f); + buf[3] = 0x80 + (c & 0x3f); + return 4; + } else if (c < 0x4000000) { // 26 bits + buf[0] = 0xf8 + ((unsigned)c >> 24); + buf[1] = 0x80 + (((unsigned)c >> 18) & 0x3f); + buf[2] = 0x80 + (((unsigned)c >> 12) & 0x3f); + buf[3] = 0x80 + (((unsigned)c >> 6) & 0x3f); + buf[4] = 0x80 + (c & 0x3f); + return 5; + } else { // 31 bits + buf[0] = 0xfc + ((unsigned)c >> 30); + buf[1] = 0x80 + (((unsigned)c >> 24) & 0x3f); + buf[2] = 0x80 + (((unsigned)c >> 18) & 0x3f); + buf[3] = 0x80 + (((unsigned)c >> 12) & 0x3f); + buf[4] = 0x80 + (((unsigned)c >> 6) & 0x3f); + buf[5] = 0x80 + (c & 0x3f); + return 6; + } +} + +int utf_ptr2len(const char_u *const p) +{ + if (*p == NUL) { + return 0; + } + const int len = utf8len_tab[*p]; + for (int i = 1; i < len; i++) { + if ((p[i] & 0xc0) != 0x80) { + return 1; + } + } + return len; +} + +int utfc_ptr2len(const char_u *const p) +{ + uint8_t b0 = (uint8_t)(*p); + + if (b0 == NUL) { return 0; } - return 1; + if (b0 < 0x80 && p[1] < 0x80) { // be quick for ASCII + return 1; + } + + // Skip over first UTF-8 char, stopping at a NUL byte. + int len = utf_ptr2len(p); + + // Check for illegal byte. + if (len == 1 && b0 >= 0x80) { + return 1; + } + + // Check for composing characters. We can handle only the first six, but + // skip all of them (otherwise the cursor would get stuck). + int prevlen = 0; + for (;;) { + if (p[len] < 0x80 || !UTF_COMPOSINGLIKE(p + prevlen, p + len)) { + return len; + } + + // Skip over composing char. + prevlen = len; + len += utf_ptr2len(p + len); + } +} + +void mb_copy_char(const char_u **fp, char_u **tp) +{ + const size_t l = utfc_ptr2len(*fp); + + memmove(*tp, *fp, (size_t)l); + *tp += l; + *fp += l; } diff --git a/test/symbolic/klee/viml_expressions_parser.c b/test/symbolic/klee/viml_expressions_parser.c index 5ad592b99f..e1cea2d990 100644 --- a/test/symbolic/klee/viml_expressions_parser.c +++ b/test/symbolic/klee/viml_expressions_parser.c @@ -18,6 +18,7 @@ #include "nvim/garray.c" #include "nvim/gettext.c" #include "nvim/viml/parser/expressions.c" +#include "nvim/keymap.c" #define INPUT_SIZE 50 diff --git a/test/unit/viml/expressions/parser_spec.lua b/test/unit/viml/expressions/parser_spec.lua index 41261cf7c9..ed77a7cba4 100644 --- a/test/unit/viml/expressions/parser_spec.lua +++ b/test/unit/viml/expressions/parser_spec.lua @@ -142,6 +142,13 @@ local function eastnode2lua(pstate, eastnode, checked_nodes) typ = typ .. ('(val=%u)'):format(tonumber(eastnode.data.num.value)) elseif typ == 'Float' then typ = typ .. ('(val=%e)'):format(tonumber(eastnode.data.flt.value)) + elseif typ == 'SingleQuotedString' or typ == 'DoubleQuotedString' then + if eastnode.data.str.value == nil then + typ = typ .. '(val=NULL)' + else + local s = ffi.string(eastnode.data.str.value, eastnode.data.str.size) + typ = format_string('%s(val=%q)', typ, s) + end end ret_str = typ .. ':' .. ret_str local can_simplify = true @@ -4549,4 +4556,822 @@ describe('Expressions parser', function() hl('InvalidSubscript', ']'), }) end) + itp('works with strings', function() + check_parsing('\'abc\'', 0, { + -- 01234 + ast = { + 'SingleQuotedString(val="abc"):0:0:\'abc\'', + }, + }, { + hl('SingleQuotedString', '\''), + hl('SingleQuotedBody', 'abc'), + hl('SingleQuotedString', '\''), + }) + check_parsing('"abc"', 0, { + -- 01234 + ast = { + 'DoubleQuotedString(val="abc"):0:0:"abc"', + }, + }, { + hl('DoubleQuotedString', '"'), + hl('DoubleQuotedBody', 'abc'), + hl('DoubleQuotedString', '"'), + }) + check_parsing('\'\'', 0, { + -- 01 + ast = { + 'SingleQuotedString(val=NULL):0:0:\'\'', + }, + }, { + hl('SingleQuotedString', '\''), + hl('SingleQuotedString', '\''), + }) + check_parsing('""', 0, { + -- 01 + ast = { + 'DoubleQuotedString(val=NULL):0:0:""', + }, + }, { + hl('DoubleQuotedString', '"'), + hl('DoubleQuotedString', '"'), + }) + check_parsing('"', 0, { + -- 0 + ast = { + 'DoubleQuotedString(val=NULL):0:0:"', + }, + err = { + arg = '"', + msg = 'E114: Missing double quote: %.*s', + }, + }, { + hl('InvalidDoubleQuotedString', '"'), + }) + check_parsing('\'', 0, { + -- 0 + ast = { + 'SingleQuotedString(val=NULL):0:0:\'', + }, + err = { + arg = '\'', + msg = 'E115: Missing single quote: %.*s', + }, + }, { + hl('InvalidSingleQuotedString', '\''), + }) + check_parsing('"a', 0, { + -- 01 + ast = { + 'DoubleQuotedString(val="a"):0:0:"a', + }, + err = { + arg = '"a', + msg = 'E114: Missing double quote: %.*s', + }, + }, { + hl('InvalidDoubleQuotedString', '"'), + hl('InvalidDoubleQuotedBody', 'a'), + }) + check_parsing('\'a', 0, { + -- 01 + ast = { + 'SingleQuotedString(val="a"):0:0:\'a', + }, + err = { + arg = '\'a', + msg = 'E115: Missing single quote: %.*s', + }, + }, { + hl('InvalidSingleQuotedString', '\''), + hl('InvalidSingleQuotedBody', 'a'), + }) + check_parsing('\'abc\'\'def\'', 0, { + -- 0123456789 + ast = { + 'SingleQuotedString(val="abc\'def"):0:0:\'abc\'\'def\'', + }, + }, { + hl('SingleQuotedString', '\''), + hl('SingleQuotedBody', 'abc'), + hl('SingleQuotedQuote', '\'\''), + hl('SingleQuotedBody', 'def'), + hl('SingleQuotedString', '\''), + }) + check_parsing('\'abc\'\'', 0, { + -- 012345 + ast = { + 'SingleQuotedString(val="abc\'"):0:0:\'abc\'\'', + }, + err = { + arg = '\'abc\'\'', + msg = 'E115: Missing single quote: %.*s', + }, + }, { + hl('InvalidSingleQuotedString', '\''), + hl('InvalidSingleQuotedBody', 'abc'), + hl('InvalidSingleQuotedQuote', '\'\''), + }) + check_parsing('\'\'\'\'\'\'\'\'', 0, { + -- 01234567 + ast = { + 'SingleQuotedString(val="\'\'\'"):0:0:\'\'\'\'\'\'\'\'', + }, + }, { + hl('SingleQuotedString', '\''), + hl('SingleQuotedQuote', '\'\''), + hl('SingleQuotedQuote', '\'\''), + hl('SingleQuotedQuote', '\'\''), + hl('SingleQuotedString', '\''), + }) + check_parsing('\'\'\'a\'\'\'\'bc\'', 0, { + -- 01234567890 + -- 0 1 + ast = { + 'SingleQuotedString(val="\'a\'\'bc"):0:0:\'\'\'a\'\'\'\'bc\'', + }, + }, { + hl('SingleQuotedString', '\''), + hl('SingleQuotedQuote', '\'\''), + hl('SingleQuotedBody', 'a'), + hl('SingleQuotedQuote', '\'\''), + hl('SingleQuotedQuote', '\'\''), + hl('SingleQuotedBody', 'bc'), + hl('SingleQuotedString', '\''), + }) + check_parsing('"\\"\\"\\"\\""', 0, { + -- 0123456789 + ast = { + 'DoubleQuotedString(val="\\"\\"\\"\\""):0:0:"\\"\\"\\"\\""', + }, + }, { + hl('DoubleQuotedString', '"'), + hl('DoubleQuotedEscape', '\\"'), + hl('DoubleQuotedEscape', '\\"'), + hl('DoubleQuotedEscape', '\\"'), + hl('DoubleQuotedEscape', '\\"'), + hl('DoubleQuotedString', '"'), + }) + check_parsing('"abc\\"def\\"ghi\\"jkl\\"mno"', 0, { + -- 0123456789012345678901234 + -- 0 1 2 + ast = { + 'DoubleQuotedString(val="abc\\"def\\"ghi\\"jkl\\"mno"):0:0:"abc\\"def\\"ghi\\"jkl\\"mno"', + }, + }, { + hl('DoubleQuotedString', '"'), + hl('DoubleQuotedBody', 'abc'), + hl('DoubleQuotedEscape', '\\"'), + hl('DoubleQuotedBody', 'def'), + hl('DoubleQuotedEscape', '\\"'), + hl('DoubleQuotedBody', 'ghi'), + hl('DoubleQuotedEscape', '\\"'), + hl('DoubleQuotedBody', 'jkl'), + hl('DoubleQuotedEscape', '\\"'), + hl('DoubleQuotedBody', 'mno'), + hl('DoubleQuotedString', '"'), + }) + check_parsing('"\\b\\e\\f\\r\\t\\\\"', 0, { + -- 0123456789012345 + -- 0 1 + ast = { + [[DoubleQuotedString(val="\8\27\12\13\9\\"):0:0:"\b\e\f\r\t\\"]], + }, + }, { + hl('DoubleQuotedString', '"'), + hl('DoubleQuotedEscape', '\\b'), + hl('DoubleQuotedEscape', '\\e'), + hl('DoubleQuotedEscape', '\\f'), + hl('DoubleQuotedEscape', '\\r'), + hl('DoubleQuotedEscape', '\\t'), + hl('DoubleQuotedEscape', '\\\\'), + hl('DoubleQuotedString', '"'), + }) + check_parsing('"\\n\n"', 0, { + -- 01234 + ast = { + 'DoubleQuotedString(val="\\\n\\\n"):0:0:"\\n\n"', + }, + }, { + hl('DoubleQuotedString', '"'), + hl('DoubleQuotedEscape', '\\n'), + hl('DoubleQuotedBody', '\n'), + hl('DoubleQuotedString', '"'), + }) + check_parsing('"\\x00"', 0, { + -- 012345 + ast = { + 'DoubleQuotedString(val="\\0"):0:0:"\\x00"', + }, + }, { + hl('DoubleQuotedString', '"'), + hl('DoubleQuotedEscape', '\\x00'), + hl('DoubleQuotedString', '"'), + }) + check_parsing('"\\xFF"', 0, { + -- 012345 + ast = { + 'DoubleQuotedString(val="\255"):0:0:"\\xFF"', + }, + }, { + hl('DoubleQuotedString', '"'), + hl('DoubleQuotedEscape', '\\xFF'), + hl('DoubleQuotedString', '"'), + }) + check_parsing('"\\xF"', 0, { + -- 012345 + ast = { + 'DoubleQuotedString(val="\\15"):0:0:"\\xF"', + }, + }, { + hl('DoubleQuotedString', '"'), + hl('DoubleQuotedEscape', '\\xF'), + hl('DoubleQuotedString', '"'), + }) + check_parsing('"\\u00AB"', 0, { + -- 01234567 + ast = { + 'DoubleQuotedString(val="«"):0:0:"\\u00AB"', + }, + }, { + hl('DoubleQuotedString', '"'), + hl('DoubleQuotedEscape', '\\u00AB'), + hl('DoubleQuotedString', '"'), + }) + check_parsing('"\\U000000AB"', 0, { + -- 01234567 + ast = { + 'DoubleQuotedString(val="«"):0:0:"\\U000000AB"', + }, + }, { + hl('DoubleQuotedString', '"'), + hl('DoubleQuotedEscape', '\\U000000AB'), + hl('DoubleQuotedString', '"'), + }) + check_parsing('"\\x"', 0, { + -- 0123 + ast = { + 'DoubleQuotedString(val="x"):0:0:"\\x"', + }, + }, { + hl('DoubleQuotedString', '"'), + hl('DoubleQuotedUnknownEscape', '\\x'), + hl('DoubleQuotedString', '"'), + }) + + check_parsing('"\\x', 0, { + -- 012 + ast = { + 'DoubleQuotedString(val="x"):0:0:"\\x', + }, + err = { + arg = '"\\x', + msg = 'E114: Missing double quote: %.*s', + }, + }, { + hl('InvalidDoubleQuotedString', '"'), + hl('InvalidDoubleQuotedUnknownEscape', '\\x'), + }) + + check_parsing('"\\xF', 0, { + -- 0123 + ast = { + 'DoubleQuotedString(val="\\15"):0:0:"\\xF', + }, + err = { + arg = '"\\xF', + msg = 'E114: Missing double quote: %.*s', + }, + }, { + hl('InvalidDoubleQuotedString', '"'), + hl('InvalidDoubleQuotedEscape', '\\xF'), + }) + + check_parsing('"\\u"', 0, { + -- 0123 + ast = { + 'DoubleQuotedString(val="u"):0:0:"\\u"', + }, + }, { + hl('DoubleQuotedString', '"'), + hl('DoubleQuotedUnknownEscape', '\\u'), + hl('DoubleQuotedString', '"'), + }) + + check_parsing('"\\u', 0, { + -- 012 + ast = { + 'DoubleQuotedString(val="u"):0:0:"\\u', + }, + err = { + arg = '"\\u', + msg = 'E114: Missing double quote: %.*s', + }, + }, { + hl('InvalidDoubleQuotedString', '"'), + hl('InvalidDoubleQuotedUnknownEscape', '\\u'), + }) + + check_parsing('"\\U', 0, { + -- 012 + ast = { + 'DoubleQuotedString(val="U"):0:0:"\\U', + }, + err = { + arg = '"\\U', + msg = 'E114: Missing double quote: %.*s', + }, + }, { + hl('InvalidDoubleQuotedString', '"'), + hl('InvalidDoubleQuotedUnknownEscape', '\\U'), + }) + + check_parsing('"\\U"', 0, { + -- 0123 + ast = { + 'DoubleQuotedString(val="U"):0:0:"\\U"', + }, + }, { + hl('DoubleQuotedString', '"'), + hl('DoubleQuotedUnknownEscape', '\\U'), + hl('DoubleQuotedString', '"'), + }) + + check_parsing('"\\xFX"', 0, { + -- 012345 + ast = { + 'DoubleQuotedString(val="\\15X"):0:0:"\\xFX"', + }, + }, { + hl('DoubleQuotedString', '"'), + hl('DoubleQuotedEscape', '\\xF'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuotedString', '"'), + }) + + check_parsing('"\\XFX"', 0, { + -- 012345 + ast = { + 'DoubleQuotedString(val="\\15X"):0:0:"\\XFX"', + }, + }, { + hl('DoubleQuotedString', '"'), + hl('DoubleQuotedEscape', '\\XF'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuotedString', '"'), + }) + + check_parsing('"\\xX"', 0, { + -- 01234 + ast = { + 'DoubleQuotedString(val="xX"):0:0:"\\xX"', + }, + }, { + hl('DoubleQuotedString', '"'), + hl('DoubleQuotedUnknownEscape', '\\x'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuotedString', '"'), + }) + + check_parsing('"\\XX"', 0, { + -- 01234 + ast = { + 'DoubleQuotedString(val="XX"):0:0:"\\XX"', + }, + }, { + hl('DoubleQuotedString', '"'), + hl('DoubleQuotedUnknownEscape', '\\X'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuotedString', '"'), + }) + + check_parsing('"\\uX"', 0, { + -- 01234 + ast = { + 'DoubleQuotedString(val="uX"):0:0:"\\uX"', + }, + }, { + hl('DoubleQuotedString', '"'), + hl('DoubleQuotedUnknownEscape', '\\u'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuotedString', '"'), + }) + + check_parsing('"\\UX"', 0, { + -- 01234 + ast = { + 'DoubleQuotedString(val="UX"):0:0:"\\UX"', + }, + }, { + hl('DoubleQuotedString', '"'), + hl('DoubleQuotedUnknownEscape', '\\U'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuotedString', '"'), + }) + + check_parsing('"\\x0X"', 0, { + -- 012345 + ast = { + 'DoubleQuotedString(val="\\0X"):0:0:"\\x0X"', + }, + }, { + hl('DoubleQuotedString', '"'), + hl('DoubleQuotedEscape', '\\x0'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuotedString', '"'), + }) + + check_parsing('"\\X0X"', 0, { + -- 012345 + ast = { + 'DoubleQuotedString(val="\\0X"):0:0:"\\X0X"', + }, + }, { + hl('DoubleQuotedString', '"'), + hl('DoubleQuotedEscape', '\\X0'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuotedString', '"'), + }) + + check_parsing('"\\u0X"', 0, { + -- 012345 + ast = { + 'DoubleQuotedString(val="\\0X"):0:0:"\\u0X"', + }, + }, { + hl('DoubleQuotedString', '"'), + hl('DoubleQuotedEscape', '\\u0'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuotedString', '"'), + }) + + check_parsing('"\\U0X"', 0, { + -- 012345 + ast = { + 'DoubleQuotedString(val="\\0X"):0:0:"\\U0X"', + }, + }, { + hl('DoubleQuotedString', '"'), + hl('DoubleQuotedEscape', '\\U0'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuotedString', '"'), + }) + + check_parsing('"\\x00X"', 0, { + -- 0123456 + ast = { + 'DoubleQuotedString(val="\\0X"):0:0:"\\x00X"', + }, + }, { + hl('DoubleQuotedString', '"'), + hl('DoubleQuotedEscape', '\\x00'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuotedString', '"'), + }) + + check_parsing('"\\X00X"', 0, { + -- 0123456 + ast = { + 'DoubleQuotedString(val="\\0X"):0:0:"\\X00X"', + }, + }, { + hl('DoubleQuotedString', '"'), + hl('DoubleQuotedEscape', '\\X00'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuotedString', '"'), + }) + + check_parsing('"\\u00X"', 0, { + -- 0123456 + ast = { + 'DoubleQuotedString(val="\\0X"):0:0:"\\u00X"', + }, + }, { + hl('DoubleQuotedString', '"'), + hl('DoubleQuotedEscape', '\\u00'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuotedString', '"'), + }) + + check_parsing('"\\U00X"', 0, { + -- 0123456 + ast = { + 'DoubleQuotedString(val="\\0X"):0:0:"\\U00X"', + }, + }, { + hl('DoubleQuotedString', '"'), + hl('DoubleQuotedEscape', '\\U00'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuotedString', '"'), + }) + + check_parsing('"\\u000X"', 0, { + -- 01234567 + ast = { + 'DoubleQuotedString(val="\\0X"):0:0:"\\u000X"', + }, + }, { + hl('DoubleQuotedString', '"'), + hl('DoubleQuotedEscape', '\\u000'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuotedString', '"'), + }) + + check_parsing('"\\U000X"', 0, { + -- 01234567 + ast = { + 'DoubleQuotedString(val="\\0X"):0:0:"\\U000X"', + }, + }, { + hl('DoubleQuotedString', '"'), + hl('DoubleQuotedEscape', '\\U000'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuotedString', '"'), + }) + + check_parsing('"\\u0000X"', 0, { + -- 012345678 + ast = { + 'DoubleQuotedString(val="\\0X"):0:0:"\\u0000X"', + }, + }, { + hl('DoubleQuotedString', '"'), + hl('DoubleQuotedEscape', '\\u0000'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuotedString', '"'), + }) + + check_parsing('"\\U0000X"', 0, { + -- 012345678 + ast = { + 'DoubleQuotedString(val="\\0X"):0:0:"\\U0000X"', + }, + }, { + hl('DoubleQuotedString', '"'), + hl('DoubleQuotedEscape', '\\U0000'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuotedString', '"'), + }) + + check_parsing('"\\U00000X"', 0, { + -- 0123456789 + ast = { + 'DoubleQuotedString(val="\\0X"):0:0:"\\U00000X"', + }, + }, { + hl('DoubleQuotedString', '"'), + hl('DoubleQuotedEscape', '\\U00000'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuotedString', '"'), + }) + + check_parsing('"\\U000000X"', 0, { + -- 01234567890 + -- 0 1 + ast = { + 'DoubleQuotedString(val="\\0X"):0:0:"\\U000000X"', + }, + }, { + hl('DoubleQuotedString', '"'), + hl('DoubleQuotedEscape', '\\U000000'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuotedString', '"'), + }) + + check_parsing('"\\U0000000X"', 0, { + -- 012345678901 + -- 0 1 + ast = { + 'DoubleQuotedString(val="\\0X"):0:0:"\\U0000000X"', + }, + }, { + hl('DoubleQuotedString', '"'), + hl('DoubleQuotedEscape', '\\U0000000'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuotedString', '"'), + }) + + check_parsing('"\\U00000000X"', 0, { + -- 0123456789012 + -- 0 1 + ast = { + 'DoubleQuotedString(val="\\0X"):0:0:"\\U00000000X"', + }, + }, { + hl('DoubleQuotedString', '"'), + hl('DoubleQuotedEscape', '\\U00000000'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuotedString', '"'), + }) + + check_parsing('"\\x000X"', 0, { + -- 01234567 + ast = { + 'DoubleQuotedString(val="\\0000X"):0:0:"\\x000X"', + }, + }, { + hl('DoubleQuotedString', '"'), + hl('DoubleQuotedEscape', '\\x00'), + hl('DoubleQuotedBody', '0X'), + hl('DoubleQuotedString', '"'), + }) + + check_parsing('"\\X000X"', 0, { + -- 01234567 + ast = { + 'DoubleQuotedString(val="\\0000X"):0:0:"\\X000X"', + }, + }, { + hl('DoubleQuotedString', '"'), + hl('DoubleQuotedEscape', '\\X00'), + hl('DoubleQuotedBody', '0X'), + hl('DoubleQuotedString', '"'), + }) + + check_parsing('"\\u00000X"', 0, { + -- 0123456789 + ast = { + 'DoubleQuotedString(val="\\0000X"):0:0:"\\u00000X"', + }, + }, { + hl('DoubleQuotedString', '"'), + hl('DoubleQuotedEscape', '\\u0000'), + hl('DoubleQuotedBody', '0X'), + hl('DoubleQuotedString', '"'), + }) + + check_parsing('"\\U000000000X"', 0, { + -- 01234567890123 + -- 0 1 + ast = { + 'DoubleQuotedString(val="\\0000X"):0:0:"\\U000000000X"', + }, + }, { + hl('DoubleQuotedString', '"'), + hl('DoubleQuotedEscape', '\\U00000000'), + hl('DoubleQuotedBody', '0X'), + hl('DoubleQuotedString', '"'), + }) + + check_parsing('"\\0"', 0, { + -- 0123 + ast = { + 'DoubleQuotedString(val="\\0"):0:0:"\\0"', + }, + }, { + hl('DoubleQuotedString', '"'), + hl('DoubleQuotedEscape', '\\0'), + hl('DoubleQuotedString', '"'), + }) + + check_parsing('"\\00"', 0, { + -- 01234 + ast = { + 'DoubleQuotedString(val="\\0"):0:0:"\\00"', + }, + }, { + hl('DoubleQuotedString', '"'), + hl('DoubleQuotedEscape', '\\00'), + hl('DoubleQuotedString', '"'), + }) + + check_parsing('"\\000"', 0, { + -- 012345 + ast = { + 'DoubleQuotedString(val="\\0"):0:0:"\\000"', + }, + }, { + hl('DoubleQuotedString', '"'), + hl('DoubleQuotedEscape', '\\000'), + hl('DoubleQuotedString', '"'), + }) + + check_parsing('"\\0000"', 0, { + -- 0123456 + ast = { + 'DoubleQuotedString(val="\\0000"):0:0:"\\0000"', + }, + }, { + hl('DoubleQuotedString', '"'), + hl('DoubleQuotedEscape', '\\000'), + hl('DoubleQuotedBody', '0'), + hl('DoubleQuotedString', '"'), + }) + + check_parsing('"\\8"', 0, { + -- 0123 + ast = { + 'DoubleQuotedString(val="8"):0:0:"\\8"', + }, + }, { + hl('DoubleQuotedString', '"'), + hl('DoubleQuotedUnknownEscape', '\\8'), + hl('DoubleQuotedString', '"'), + }) + + check_parsing('"\\08"', 0, { + -- 01234 + ast = { + 'DoubleQuotedString(val="\\0008"):0:0:"\\08"', + }, + }, { + hl('DoubleQuotedString', '"'), + hl('DoubleQuotedEscape', '\\0'), + hl('DoubleQuotedBody', '8'), + hl('DoubleQuotedString', '"'), + }) + + check_parsing('"\\008"', 0, { + -- 012345 + ast = { + 'DoubleQuotedString(val="\\0008"):0:0:"\\008"', + }, + }, { + hl('DoubleQuotedString', '"'), + hl('DoubleQuotedEscape', '\\00'), + hl('DoubleQuotedBody', '8'), + hl('DoubleQuotedString', '"'), + }) + + check_parsing('"\\0008"', 0, { + -- 0123456 + ast = { + 'DoubleQuotedString(val="\\0008"):0:0:"\\0008"', + }, + }, { + hl('DoubleQuotedString', '"'), + hl('DoubleQuotedEscape', '\\000'), + hl('DoubleQuotedBody', '8'), + hl('DoubleQuotedString', '"'), + }) + + check_parsing('"\\777"', 0, { + -- 012345 + ast = { + 'DoubleQuotedString(val="\255"):0:0:"\\777"', + }, + }, { + hl('DoubleQuotedString', '"'), + hl('DoubleQuotedEscape', '\\777'), + hl('DoubleQuotedString', '"'), + }) + + check_parsing('"\\050"', 0, { + -- 012345 + ast = { + 'DoubleQuotedString(val="\40"):0:0:"\\050"', + }, + }, { + hl('DoubleQuotedString', '"'), + hl('DoubleQuotedEscape', '\\050'), + hl('DoubleQuotedString', '"'), + }) + + check_parsing('"\\"', 0, { + -- 012345 + ast = { + 'DoubleQuotedString(val="\\21"):0:0:"\\"', + }, + }, { + hl('DoubleQuotedString', '"'), + hl('DoubleQuotedEscape', '\\'), + hl('DoubleQuotedString', '"'), + }) + + check_parsing('"\\<', 0, { + -- 012 + ast = { + 'DoubleQuotedString(val="<"):0:0:"\\<', + }, + err = { + arg = '"\\<', + msg = 'E114: Missing double quote: %.*s', + }, + }, { + hl('InvalidDoubleQuotedString', '"'), + hl('InvalidDoubleQuotedUnknownEscape', '\\<'), + }) + + check_parsing('"\\<"', 0, { + -- 0123 + ast = { + 'DoubleQuotedString(val="<"):0:0:"\\<"', + }, + }, { + hl('DoubleQuotedString', '"'), + hl('DoubleQuotedUnknownEscape', '\\<'), + hl('DoubleQuotedString', '"'), + }) + + check_parsing('"\\ Date: Mon, 9 Oct 2017 02:55:56 +0300 Subject: viml/parser/expressions: Finish parser MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Note: formatc.lua was unable to swallow some newer additions to ExprASTNodeType (specifically `kExprNodeOr = '|'` and probably something else), so all `= …` were dropped: in any case they only were there in order to not bother updating viml_pexpr_debug_print_ast_node and since it is now known all nodes which will be present it is not much of an issue. --- src/nvim/viml/parser/expressions.c | 369 +++++++++++++++++++--------- src/nvim/viml/parser/expressions.h | 115 ++++++--- src/nvim/viml/parser/parser.h | 2 +- test/symbolic/klee/viml_expressions_lexer.c | 1 + test/unit/viml/expressions/lexer_spec.lua | 6 +- test/unit/viml/expressions/parser_spec.lua | 19 ++ test/unit/viml/helpers.lua | 1 + 7 files changed, 355 insertions(+), 158 deletions(-) diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index 3f30fe2a0e..75fcb17bf6 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -361,11 +361,12 @@ LexExprToken viml_pexpr_next_token(ParserState *const pstate, const int flags) // Scope: `s:`, etc. } else if (ret.len == 1 && pline.size > 1 - && strchr("sgvbwtla", schar) != NULL + && memchr(EXPR_VAR_SCOPE_LIST, schar, + sizeof(EXPR_VAR_SCOPE_LIST)) != NULL && pline.data[ret.len] == ':' && !(flags & kELFlagForbidScope)) { ret.len++; - ret.data.var.scope = schar; + ret.data.var.scope = (ExprVarScope)schar; CHARREG(kExprLexPlainIdentifier, ISWORD_OR_AUTOLOAD); ret.data.var.autoload = ( memchr(pline.data + 2, AUTOLOAD_CHAR, ret.len - 2) @@ -408,14 +409,13 @@ LexExprToken viml_pexpr_next_token(ParserState *const pstate, const int flags) ret.type = kExprLexOption; if (pline.size > 2 && pline.data[2] == ':' - && strchr("gl", pline.data[1]) != NULL) { + && memchr(EXPR_OPT_SCOPE_LIST, pline.data[1], + sizeof(EXPR_OPT_SCOPE_LIST)) != NULL) { ret.len += 2; - ret.data.opt.scope = (pline.data[1] == 'g' - ? kExprLexOptGlobal - : kExprLexOptLocal); + ret.data.opt.scope = (ExprOptScope)pline.data[1]; ret.data.opt.name = pline.data + 3; } else { - ret.data.opt.scope = kExprLexOptUnspecified; + ret.data.opt.scope = kExprOptScopeUnspecified; ret.data.opt.name = pline.data + 1; } const char *p = ret.data.opt.name; @@ -637,9 +637,9 @@ static const char *const eltkn_mul_type_tab[] = { }; static const char *const eltkn_opt_scope_tab[] = { - [kExprLexOptUnspecified] = "Unspecified", - [kExprLexOptGlobal] = "Global", - [kExprLexOptLocal] = "Local", + [kExprOptScopeUnspecified] = "Unspecified", + [kExprOptScopeGlobal] = "Global", + [kExprOptScopeLocal] = "Local", }; /// Represent `int` character as a string @@ -990,67 +990,25 @@ static inline ExprASTNode *viml_pexpr_new_node(const ExprASTNodeType type) return ret; } -static const ExprOpLvl node_type_to_op_lvl[] = { - [kExprNodeMissing] = kEOpLvlInvalid, - [kExprNodeOpMissing] = kEOpLvlMultiplication, +static struct { + ExprOpLvl lvl; + ExprOpAssociativity ass; +} node_type_to_node_props[] = { + [kExprNodeMissing] = { kEOpLvlInvalid, kEOpAssNo, }, + [kExprNodeOpMissing] = { kEOpLvlMultiplication, kEOpAssNo }, - [kExprNodeNested] = kEOpLvlParens, + [kExprNodeNested] = { kEOpLvlParens, kEOpAssNo }, // Note: below nodes are kEOpLvlSubscript for “binary operator” itself, but // kEOpLvlParens when it comes to inside the parenthesis. - [kExprNodeCall] = kEOpLvlParens, - [kExprNodeSubscript] = kEOpLvlParens, + [kExprNodeCall] = { kEOpLvlParens, kEOpAssNo }, + [kExprNodeSubscript] = { kEOpLvlParens, kEOpAssNo }, - [kExprNodeUnknownFigure] = kEOpLvlParens, - [kExprNodeLambda] = kEOpLvlParens, - [kExprNodeDictLiteral] = kEOpLvlParens, - [kExprNodeListLiteral] = kEOpLvlParens, + [kExprNodeUnknownFigure] = { kEOpLvlParens, kEOpAssLeft }, + [kExprNodeLambda] = { kEOpLvlParens, kEOpAssNo }, + [kExprNodeDictLiteral] = { kEOpLvlParens, kEOpAssNo }, + [kExprNodeListLiteral] = { kEOpLvlParens, kEOpAssNo }, - [kExprNodeArrow] = kEOpLvlArrow, - - [kExprNodeComma] = kEOpLvlComma, - - [kExprNodeColon] = kEOpLvlColon, - - [kExprNodeTernary] = kEOpLvlTernary, - - [kExprNodeTernaryValue] = kEOpLvlTernaryValue, - - [kExprNodeComparison] = kEOpLvlComparison, - - [kExprNodeBinaryPlus] = kEOpLvlAddition, - [kExprNodeConcat] = kEOpLvlAddition, - - [kExprNodeUnaryPlus] = kEOpLvlUnary, - - [kExprNodeConcatOrSubscript] = kEOpLvlSubscript, - - [kExprNodeCurlyBracesIdentifier] = kEOpLvlComplexIdentifier, - - [kExprNodeComplexIdentifier] = kEOpLvlValue, - [kExprNodePlainIdentifier] = kEOpLvlValue, - [kExprNodePlainKey] = kEOpLvlValue, - [kExprNodeRegister] = kEOpLvlValue, - [kExprNodeInteger] = kEOpLvlValue, - [kExprNodeFloat] = kEOpLvlValue, -}; - -static const ExprOpAssociativity node_type_to_op_ass[] = { - [kExprNodeMissing] = kEOpAssNo, - [kExprNodeOpMissing] = kEOpAssNo, - - [kExprNodeNested] = kEOpAssNo, - [kExprNodeCall] = kEOpAssNo, - [kExprNodeSubscript] = kEOpAssNo, - - [kExprNodeUnknownFigure] = kEOpAssLeft, - [kExprNodeLambda] = kEOpAssNo, - [kExprNodeDictLiteral] = kEOpAssNo, - [kExprNodeListLiteral] = kEOpAssNo, - - // Does not really matter. - [kExprNodeArrow] = kEOpAssNo, - - [kExprNodeColon] = kEOpAssNo, + [kExprNodeArrow] = { kEOpLvlArrow, kEOpAssNo }, // Right associativity for comma because this means easier access to arguments // list, etc: for "[a, b, c, d]" you can access "a" in one step if it is @@ -1059,29 +1017,48 @@ static const ExprOpAssociativity node_type_to_op_ass[] = { // traverse all three comma() structures. And with comma operator (including // actual comma operator from C which is not present in VimL) nobody cares // about associativity, only about order of execution. - [kExprNodeComma] = kEOpAssRight, + [kExprNodeComma] = { kEOpLvlComma, kEOpAssRight }, + + // Colons are not eligible for chaining, so nobody cares about associativity. + [kExprNodeColon] = { kEOpLvlColon, kEOpAssNo }, + + [kExprNodeTernary] = { kEOpLvlTernary, kEOpAssRight }, - [kExprNodeTernary] = kEOpAssRight, + [kExprNodeOr] = { kEOpLvlOr, kEOpAssLeft }, - [kExprNodeTernaryValue] = kEOpAssRight, + [kExprNodeAnd] = { kEOpLvlAnd, kEOpAssLeft }, - [kExprNodeComparison] = kEOpAssRight, + [kExprNodeTernaryValue] = { kEOpLvlTernaryValue, kEOpAssRight }, - [kExprNodeBinaryPlus] = kEOpAssLeft, - [kExprNodeConcat] = kEOpAssLeft, + [kExprNodeComparison] = { kEOpLvlComparison, kEOpAssRight }, - [kExprNodeUnaryPlus] = kEOpAssNo, + [kExprNodeBinaryPlus] = { kEOpLvlAddition, kEOpAssLeft }, + [kExprNodeBinaryMinus] = { kEOpLvlAddition, kEOpAssLeft }, + [kExprNodeConcat] = { kEOpLvlAddition, kEOpAssLeft }, - [kExprNodeConcatOrSubscript] = kEOpAssLeft, + [kExprNodeMultiplication] = { kEOpLvlMultiplication, kEOpAssLeft }, + [kExprNodeDivision] = { kEOpLvlMultiplication, kEOpAssLeft }, + [kExprNodeMod] = { kEOpLvlMultiplication, kEOpAssLeft }, - [kExprNodeCurlyBracesIdentifier] = kEOpAssLeft, + [kExprNodeUnaryPlus] = { kEOpLvlUnary, kEOpAssNo }, + [kExprNodeUnaryMinus] = { kEOpLvlUnary, kEOpAssNo }, + [kExprNodeNot] = { kEOpLvlUnary, kEOpAssNo }, - [kExprNodeComplexIdentifier] = kEOpAssLeft, - [kExprNodePlainIdentifier] = kEOpAssNo, - [kExprNodePlainKey] = kEOpAssNo, - [kExprNodeRegister] = kEOpAssNo, - [kExprNodeInteger] = kEOpAssNo, - [kExprNodeFloat] = kEOpAssNo, + [kExprNodeConcatOrSubscript] = { kEOpLvlSubscript, kEOpAssLeft }, + + [kExprNodeCurlyBracesIdentifier] = { kEOpLvlComplexIdentifier, kEOpAssLeft }, + + [kExprNodeComplexIdentifier] = { kEOpLvlValue, kEOpAssLeft }, + + [kExprNodePlainIdentifier] = { kEOpLvlValue, kEOpAssNo }, + [kExprNodePlainKey] = { kEOpLvlValue, kEOpAssNo }, + [kExprNodeRegister] = { kEOpLvlValue, kEOpAssNo }, + [kExprNodeInteger] = { kEOpLvlValue, kEOpAssNo }, + [kExprNodeFloat] = { kEOpLvlValue, kEOpAssNo }, + [kExprNodeDoubleQuotedString] = { kEOpLvlValue, kEOpAssNo }, + [kExprNodeSingleQuotedString] = { kEOpLvlValue, kEOpAssNo }, + [kExprNodeOption] = { kEOpLvlValue, kEOpAssNo }, + [kExprNodeEnvironment] = { kEOpLvlValue, kEOpAssNo }, }; /// Get AST node priority level @@ -1094,7 +1071,7 @@ static const ExprOpAssociativity node_type_to_op_ass[] = { static inline ExprOpLvl node_lvl(const ExprASTNode node) FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_CONST FUNC_ATTR_WARN_UNUSED_RESULT { - return node_type_to_op_lvl[node.type]; + return node_type_to_node_props[node.type].lvl; } /// Get AST node associativity, to be used for operator nodes primary @@ -1107,7 +1084,7 @@ static inline ExprOpLvl node_lvl(const ExprASTNode node) static inline ExprOpAssociativity node_ass(const ExprASTNode node) FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_CONST FUNC_ATTR_WARN_UNUSED_RESULT { - return node_type_to_op_ass[node.type]; + return node_type_to_node_props[node.type].ass; } /// Handle binary operator @@ -1837,7 +1814,7 @@ viml_pexpr_parse_process_token: is_concat_or_subscript && (cur_token.type == kExprLexPlainIdentifier ? (!cur_token.data.var.autoload - && cur_token.data.var.scope == 0) + && cur_token.data.var.scope == kExprVarScopeMissing) : (cur_token.type == kExprLexNumber)) && prev_token.type != kExprLexSpacing); if (is_concat_or_subscript && !node_is_key) { @@ -1856,7 +1833,7 @@ viml_pexpr_parse_process_token: && tok_type != kExprLexArrow) || (want_node == kENodeArgument && !(cur_token.type == kExprLexPlainIdentifier - && cur_token.data.var.scope == 0 + && cur_token.data.var.scope == kExprVarScopeMissing && !cur_token.data.var.autoload) && tok_type != kExprLexArrow)) { lambda_node->data.fig.type_guesses.allow_lambda = false; @@ -1885,6 +1862,8 @@ viml_pexpr_parse_process_token: || want_node == kENodeArgumentSeparator || want_node == kENodeArgument); switch (tok_type) { + case kExprLexMissing: + case kExprLexSpacing: case kExprLexEOC: { assert(false); } @@ -1894,31 +1873,111 @@ viml_pexpr_parse_process_token: goto viml_pexpr_parse_process_token; } case kExprLexRegister: { - if (want_node == kENodeValue) { - NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeRegister); - cur_node->data.reg.name = cur_token.data.reg.name; - *top_node_p = cur_node; - want_node = kENodeOperator; - HL_CUR_TOKEN(Register); - } else { + if (want_node == kENodeOperator) { // Register in operator position: e.g. @a @a OP_MISSING; } + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeRegister); + cur_node->data.reg.name = cur_token.data.reg.name; + *top_node_p = cur_node; + want_node = kENodeOperator; + HL_CUR_TOKEN(Register); break; } - case kExprLexPlus: { - if (want_node == kENodeValue) { - // Value level: assume unary plus - NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeUnaryPlus); - *top_node_p = cur_node; - kvi_push(ast_stack, &cur_node->children); - HL_CUR_TOKEN(UnaryPlus); - } else { - NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeBinaryPlus); - ADD_OP_NODE(cur_node); - HL_CUR_TOKEN(BinaryPlus); +#define SIMPLE_UB_OP(op) \ + case kExprLex##op: { \ + if (want_node == kENodeValue) { \ + /* Value level: assume unary operator. */ \ + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeUnary##op); \ + *top_node_p = cur_node; \ + kvi_push(ast_stack, &cur_node->children); \ + HL_CUR_TOKEN(Unary##op); \ + } else { \ + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeBinary##op); \ + ADD_OP_NODE(cur_node); \ + HL_CUR_TOKEN(Binary##op); \ + } \ + want_node = kENodeValue; \ + break; \ + } + SIMPLE_UB_OP(Plus) + SIMPLE_UB_OP(Minus) +#undef SIMPLE_UB_OP +#define SIMPLE_B_OP(op, msg) \ + case kExprLex##op: { \ + ADD_VALUE_IF_MISSING(_("E15: Unexpected " msg ": %.*s")); \ + NEW_NODE_WITH_CUR_POS(cur_node, kExprNode##op); \ + HL_CUR_TOKEN(op); \ + ADD_OP_NODE(cur_node); \ + break; \ + } + SIMPLE_B_OP(Or, "or operator") + SIMPLE_B_OP(And, "and operator") +#undef SIMPLE_B_OP + case kExprLexMultiplication: { + ADD_VALUE_IF_MISSING( + _("E15: Unexpected multiplication-like operator: %.*s")); + switch (cur_token.data.mul.type) { +#define MUL_OP(lex_op_tail, node_op_tail) \ + case kExprLexMul##lex_op_tail: { \ + NEW_NODE_WITH_CUR_POS(cur_node, kExprNode##node_op_tail); \ + HL_CUR_TOKEN(node_op_tail); \ + break; \ + } + MUL_OP(Mul, Multiplication) + MUL_OP(Div, Division) + MUL_OP(Mod, Mod) +#undef MUL_OP } - want_node = kENodeValue; + ADD_OP_NODE(cur_node); + break; + } + case kExprLexOption: { + if (want_node == kENodeOperator) { + OP_MISSING; + } + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeOption); + cur_node->data.opt.ident = cur_token.data.opt.name; + cur_node->data.opt.ident_len = cur_token.data.opt.len; + cur_node->data.opt.scope = cur_token.data.opt.scope; + *top_node_p = cur_node; + want_node = kENodeOperator; + viml_parser_highlight(pstate, cur_token.start, 1, HL(OptionSigil)); + const size_t scope_shift = ( + cur_token.data.opt.scope == kExprOptScopeUnspecified ? 0 : 2); + if (scope_shift) { + viml_parser_highlight(pstate, shifted_pos(cur_token.start, 1), 1, + HL(OptionScope)); + viml_parser_highlight(pstate, shifted_pos(cur_token.start, 2), 1, + HL(OptionScopeDelimiter)); + } + viml_parser_highlight( + pstate, shifted_pos(cur_token.start, scope_shift + 1), + cur_token.len - scope_shift + 1, HL(Option)); + break; + } + case kExprLexEnv: { + if (want_node == kENodeOperator) { + OP_MISSING; + } + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeEnvironment); + cur_node->data.env.ident = pline.data + cur_token.start.col + 1; + cur_node->data.env.ident_len = cur_token.len - 1; + *top_node_p = cur_node; + want_node = kENodeOperator; + viml_parser_highlight(pstate, cur_token.start, 1, HL(EnvironmentSigil)); + viml_parser_highlight(pstate, shifted_pos(cur_token.start, 1), + cur_token.len - 1, HL(Environment)); + break; + } + case kExprLexNot: { + if (want_node == kENodeOperator) { + OP_MISSING; + } + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeNot); + *top_node_p = cur_node; + kvi_push(ast_stack, &cur_node->children); + HL_CUR_TOKEN(Not); break; } case kExprLexComparison: { @@ -2359,9 +2418,8 @@ viml_pexpr_parse_figure_brace_closing_error: ? kExprNodePlainKey : kExprNodePlainIdentifier)); cur_node->data.var.scope = cur_token.data.var.scope; - const size_t scope_shift = (cur_token.data.var.scope == 0 - ? 0 - : 2); + const size_t scope_shift = ( + cur_token.data.var.scope == kExprVarScopeMissing ? 0 : 2); cur_node->data.var.ident = (pline.data + cur_token.start.col + scope_shift); cur_node->data.var.ident_len = cur_token.len - scope_shift; @@ -2373,16 +2431,14 @@ viml_pexpr_parse_figure_brace_closing_error: viml_parser_highlight(pstate, shifted_pos(cur_token.start, 1), 1, HL(IdentifierScopeDelimiter)); } - if (scope_shift < cur_token.len) { - viml_parser_highlight(pstate, shifted_pos(cur_token.start, - scope_shift), - cur_token.len - scope_shift, - (node_is_key - ? HL(IdentifierKey) - : HL(Identifier))); - } + viml_parser_highlight(pstate, shifted_pos(cur_token.start, + scope_shift), + cur_token.len - scope_shift, + (node_is_key + ? HL(IdentifierKey) + : HL(Identifier))); } else { - if (cur_token.data.var.scope == 0) { + if (cur_token.data.var.scope == kExprVarScopeMissing) { ADD_IDENT( do { NEW_NODE_WITH_CUR_POS(cur_node, kExprNodePlainIdentifier); @@ -2606,9 +2662,85 @@ viml_pexpr_parse_end: cur_node->start); break; } - case kExprNodeBinaryPlus: + case kExprNodeListLiteral: { + // For whatever reason "[1" yields "E696: Missing comma in list" error + // in Vim while "[1," yields E697. + east_set_error( + pstate, &ast.err, + _("E697: Missing end of List ']': %.*s"), + cur_node->start); + break; + } + case kExprNodeDictLiteral: { + // Same problem like with list literal with E722 (missing comma) vs + // E723, but additionally just "{" yields only E15. + east_set_error( + pstate, &ast.err, + _("E723: Missing end of Dictionary '}': %.*s"), + cur_node->start); + break; + } + case kExprNodeUnknownFigure: { + east_set_error( + pstate, &ast.err, + _("E15: Missing closing figure brace: %.*s"), + cur_node->start); + break; + } + case kExprNodeLambda: { + east_set_error( + pstate, &ast.err, + _("E15: Missing closing figure brace for lambda: %.*s"), + cur_node->start); + break; + } + case kExprNodeCurlyBracesIdentifier: { + // Until trailing "}" it is impossible to distinguish curly braces + // identifier and dictionary, so it must not appear in the stack like + // this. + assert(false); + } + case kExprNodeInteger: + case kExprNodeFloat: + case kExprNodeSingleQuotedString: + case kExprNodeDoubleQuotedString: + case kExprNodeOption: + case kExprNodeEnvironment: + case kExprNodeRegister: + case kExprNodePlainIdentifier: + case kExprNodePlainKey: { + // These are plain values and not containers, for them it should only + // be possible to show up in the topmost stack element, but it was + // unconditionally popped at the start. + assert(false); + } + case kExprNodeComma: + case kExprNodeColon: + case kExprNodeArrow: { + // It is actually only valid inside something else, but everything + // where one of the above is valid requires to be closed and thus is + // to be caught later. + break; + } + case kExprNodeConcatOrSubscript: + case kExprNodeComplexIdentifier: + case kExprNodeSubscript: { + // FIXME: Investigate whether above are OK to be present in the stack. + break; + } + case kExprNodeMod: + case kExprNodeDivision: + case kExprNodeMultiplication: + case kExprNodeNot: + case kExprNodeAnd: + case kExprNodeOr: + case kExprNodeConcat: + case kExprNodeComparison: + case kExprNodeUnaryMinus: case kExprNodeUnaryPlus: - case kExprNodeRegister: { + case kExprNodeBinaryMinus: + case kExprNodeTernary: + case kExprNodeBinaryPlus: { // It is OK to see these in the stack. break; } @@ -2621,7 +2753,6 @@ viml_pexpr_parse_end: } break; } - // TODO(ZyX-I): handle other values } } } diff --git a/src/nvim/viml/parser/expressions.h b/src/nvim/viml/parser/expressions.h index a09cdde4c0..0198852bed 100644 --- a/src/nvim/viml/parser/expressions.h +++ b/src/nvim/viml/parser/expressions.h @@ -61,6 +61,36 @@ typedef enum { kExprCmpIdentical, ///< `is` or `isnot` } ExprComparisonType; +/// All possible option scopes +typedef enum { + kExprOptScopeUnspecified = 0, + kExprOptScopeGlobal = 'g', + kExprOptScopeLocal = 'l', +} ExprOptScope; + +#define EXPR_OPT_SCOPE_LIST \ + ((char *)(char[]){ kExprOptScopeGlobal, kExprOptScopeLocal }) + +/// All possible variable scopes +typedef enum { + kExprVarScopeMissing = 0, + kExprVarScopeScript = 's', + kExprVarScopeGlobal = 'g', + kExprVarScopeVim = 'v', + kExprVarScopeBuffer = 'b', + kExprVarScopeWindow = 'w', + kExprVarScopeTabpage = 't', + kExprVarScopeLocal = 'l', + kExprVarScopeArguments = 'a', +} ExprVarScope; + +#define EXPR_VAR_SCOPE_LIST \ + ((char[]) { \ + kExprVarScopeScript, kExprVarScopeGlobal, kExprVarScopeVim, \ + kExprVarScopeBuffer, kExprVarScopeWindow, kExprVarScopeTabpage, \ + kExprVarScopeLocal, kExprVarScopeBuffer, kExprVarScopeArguments, \ + }) + /// Lexer token typedef struct { ParserPosition start; @@ -96,15 +126,11 @@ typedef struct { struct { const char *name; ///< Option name start. size_t len; ///< Option name length. - enum { - kExprLexOptUnspecified = 0, - kExprLexOptGlobal = 1, - kExprLexOptLocal = 2, - } scope; ///< Option scope: &l:, &g: or not specified. + ExprOptScope scope; ///< Option scope: &l:, &g: or not specified. } opt; ///< Option properties. struct { - int scope; ///< Scope character or 0 if not present. + ExprVarScope scope; ///< Scope character or 0 if not present. bool autoload; ///< Has autoload characters. } var; ///< For kExprLexPlainIdentifier @@ -150,53 +176,63 @@ typedef enum { /// Expression AST node type typedef enum { - kExprNodeMissing = 'X', - kExprNodeOpMissing = '_', - kExprNodeTernary = '?', ///< Ternary operator. - kExprNodeTernaryValue = 'C', ///< Ternary operator, colon. - kExprNodeRegister = '@', ///< Register. - kExprNodeSubscript = 's', ///< Subscript. - kExprNodeListLiteral = 'l', ///< List literal. - kExprNodeUnaryPlus = 'p', - kExprNodeBinaryPlus = '+', - kExprNodeNested = 'e', ///< Nested parenthesised expression. - kExprNodeCall = 'c', ///< Function call. + kExprNodeMissing = 0, + kExprNodeOpMissing, + kExprNodeTernary, ///< Ternary operator. + kExprNodeTernaryValue, ///< Ternary operator, colon. + kExprNodeRegister, ///< Register. + kExprNodeSubscript, ///< Subscript. + kExprNodeListLiteral, ///< List literal. + kExprNodeUnaryPlus, + kExprNodeBinaryPlus, + kExprNodeNested, ///< Nested parenthesised expression. + kExprNodeCall, ///< Function call. /// Plain identifier: simple variable/function name /// /// Looks like "string", "g:Foo", etc: consists from a single /// kExprLexPlainIdentifier token. - kExprNodePlainIdentifier = 'i', + kExprNodePlainIdentifier, /// Plain dictionary key, for use with kExprNodeConcatOrSubscript - kExprNodePlainKey = 'k', + kExprNodePlainKey, /// Complex identifier: variable/function name with curly braces - kExprNodeComplexIdentifier = 'I', + kExprNodeComplexIdentifier, /// Figure brace expression which is not yet known /// /// May resolve to any of kExprNodeDictLiteral, kExprNodeLambda or /// kExprNodeCurlyBracesIdentifier. - kExprNodeUnknownFigure = '{', - kExprNodeLambda = '\\', ///< Lambda. - kExprNodeDictLiteral = 'd', ///< Dictionary literal. - kExprNodeCurlyBracesIdentifier= '}', ///< Part of the curly braces name. - kExprNodeComma = ',', ///< Comma “operator”. - kExprNodeColon = ':', ///< Colon “operator”. - kExprNodeArrow = '>', ///< Arrow “operator”. - kExprNodeComparison = '=', ///< Various comparison operators. + kExprNodeUnknownFigure, + kExprNodeLambda, ///< Lambda. + kExprNodeDictLiteral, ///< Dictionary literal. + kExprNodeCurlyBracesIdentifier, ///< Part of the curly braces name. + kExprNodeComma, ///< Comma “operator”. + kExprNodeColon, ///< Colon “operator”. + kExprNodeArrow, ///< Arrow “operator”. + kExprNodeComparison, ///< Various comparison operators. /// Concat operator /// /// To be only used in cases when it is known for sure it is not a subscript. - kExprNodeConcat = '.', + kExprNodeConcat, /// Concat or subscript operator /// /// For cases when it is not obvious whether expression is a concat or /// a subscript. May only have either number or plain identifier as the second /// child. To make it easier to avoid curly braces in place of /// kExprNodePlainIdentifier node kExprNodePlainKey is used. - kExprNodeConcatOrSubscript = 'S', - kExprNodeInteger = '0', ///< Integral number. - kExprNodeFloat = '1', ///< Floating-point number. - kExprNodeSingleQuotedString = '\'', - kExprNodeDoubleQuotedString = '"', + kExprNodeConcatOrSubscript, + kExprNodeInteger, ///< Integral number. + kExprNodeFloat, ///< Floating-point number. + kExprNodeSingleQuotedString, + kExprNodeDoubleQuotedString, + kExprNodeOr, + kExprNodeAnd, + kExprNodeUnaryMinus, + kExprNodeBinaryMinus, + kExprNodeNot, + kExprNodeMultiplication, + kExprNodeDivision, + kExprNodeMod, + kExprNodeOption, + kExprNodeEnvironment, } ExprASTNodeType; typedef struct expr_ast_node ExprASTNode; @@ -230,7 +266,7 @@ struct expr_ast_node { size_t opening_hl_idx; } fig; ///< For kExprNodeUnknownFigure. struct { - int scope; ///< Scope character or 0 if not present. + ExprVarScope scope; ///< Scope character or 0 if not present. /// Actual identifier without scope. /// /// Points to inside parser reader state. @@ -256,6 +292,15 @@ struct expr_ast_node { size_t size; } str; ///< For kExprNodeSingleQuotedString and ///< kExprNodeDoubleQuotedString. + struct { + const char *ident; ///< Option name start. + size_t ident_len; ///< Option name length. + ExprOptScope scope; ///< Option scope: &l:, &g: or not specified. + } opt; ///< For kExprNodeOption. + struct { + const char *ident; ///< Environment variable name start. + size_t ident_len; ///< Environment variable name length. + } env; ///< For kExprNodeEnvironment. } data; }; diff --git a/src/nvim/viml/parser/parser.h b/src/nvim/viml/parser/parser.h index a17edac403..10ced57977 100644 --- a/src/nvim/viml/parser/parser.h +++ b/src/nvim/viml/parser/parser.h @@ -173,7 +173,7 @@ static inline void viml_parser_highlight(ParserState *const pstate, const size_t len, const char *const group) { - if (pstate->colors == NULL) { + if (pstate->colors == NULL || len == 0) { return; } // TODO(ZyX-I): May do some assert() sanitizing here. diff --git a/test/symbolic/klee/viml_expressions_lexer.c b/test/symbolic/klee/viml_expressions_lexer.c index cddc1cb2f1..ee7dc312e9 100644 --- a/test/symbolic/klee/viml_expressions_lexer.c +++ b/test/symbolic/klee/viml_expressions_lexer.c @@ -17,6 +17,7 @@ #include "nvim/charset.c" #include "nvim/garray.c" #include "nvim/gettext.c" +#include "nvim/keymap.c" #include "nvim/viml/parser/expressions.c" #define INPUT_SIZE 7 diff --git a/test/unit/viml/expressions/lexer_spec.lua b/test/unit/viml/expressions/lexer_spec.lua index f180d8ceff..674b1b37db 100644 --- a/test/unit/viml/expressions/lexer_spec.lua +++ b/test/unit/viml/expressions/lexer_spec.lua @@ -62,9 +62,9 @@ child_call_once(function() } eltkn_opt_scope_tab = { - [tonumber(lib.kExprLexOptUnspecified)] = 'Unspecified', - [tonumber(lib.kExprLexOptGlobal)] = 'Global', - [tonumber(lib.kExprLexOptLocal)] = 'Local', + [tonumber(lib.kExprOptScopeUnspecified)] = 'Unspecified', + [tonumber(lib.kExprOptScopeGlobal)] = 'Global', + [tonumber(lib.kExprOptScopeLocal)] = 'Local', } end) diff --git a/test/unit/viml/expressions/parser_spec.lua b/test/unit/viml/expressions/parser_spec.lua index ed77a7cba4..5041708a3e 100644 --- a/test/unit/viml/expressions/parser_spec.lua +++ b/test/unit/viml/expressions/parser_spec.lua @@ -93,6 +93,16 @@ make_enum_conv_tab(lib, { 'kExprNodeFloat', 'kExprNodeSingleQuotedString', 'kExprNodeDoubleQuotedString', + 'kExprNodeOr', + 'kExprNodeAnd', + 'kExprNodeUnaryMinus', + 'kExprNodeBinaryMinus', + 'kExprNodeNot', + 'kExprNodeMultiplication', + 'kExprNodeDivision', + 'kExprNodeMod', + 'kExprNodeOption', + 'kExprNodeEnvironment', }, 'kExprNode', function(ret) east_node_type_tab = ret end) local function conv_east_node_type(typ) @@ -149,6 +159,15 @@ local function eastnode2lua(pstate, eastnode, checked_nodes) local s = ffi.string(eastnode.data.str.value, eastnode.data.str.size) typ = format_string('%s(val=%q)', typ, s) end + elseif typ == 'Option' then + typ = ('%s(scope=%s,ident=%s)'):format( + typ, + tostring(intchar2lua(eastnode.data.opt.scope)), + ffi.string(eastnode.data.opt.ident, eastnode.data.opt.ident_len)) + elseif typ == 'Environment' then + typ = ('%s(ident=%s)'):format( + typ, + ffi.string(eastnode.data.env.ident, eastnode.data.env.ident_len)) end ret_str = typ .. ':' .. ret_str local can_simplify = true diff --git a/test/unit/viml/helpers.lua b/test/unit/viml/helpers.lua index 0b92be2654..c965cacb29 100644 --- a/test/unit/viml/helpers.lua +++ b/test/unit/viml/helpers.lua @@ -5,6 +5,7 @@ local cimport = helpers.cimport local kvi_new = helpers.kvi_new local kvi_init = helpers.kvi_init local conv_enum = helpers.conv_enum +local child_call_once = helpers.child_call_once local make_enum_conv_tab = helpers.make_enum_conv_tab local lib = cimport('./src/nvim/viml/parser/expressions.h') -- cgit From 8178ba2871bb427e03419a2f68c0fb119e44e717 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sat, 14 Oct 2017 00:26:52 +0300 Subject: klee: Fix some errors made in …parser.c MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/symbolic/klee/viml_expressions_parser.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/symbolic/klee/viml_expressions_parser.c b/test/symbolic/klee/viml_expressions_parser.c index e1cea2d990..a4fbdb6bf4 100644 --- a/test/symbolic/klee/viml_expressions_parser.c +++ b/test/symbolic/klee/viml_expressions_parser.c @@ -1,5 +1,4 @@ #ifdef USE_KLEE -# error UNFINISHED # include #else # include @@ -47,10 +46,10 @@ int main(const int argc, const char *const *const argv, #ifdef USE_KLEE klee_make_symbolic(input, sizeof(input), "input"); klee_make_symbolic(&shift, sizeof(shift), "shift"); - klee_make_symbolic(&flags, sizeof{flags}, "flags"); + klee_make_symbolic(&flags, sizeof(flags), "flags"); klee_assume(shift < INPUT_SIZE); klee_assume( - flags <= kExprFlagsMulti|kExprFlagsDisallowEOC|kExprFlagsPrintError); + flags <= (kExprFlagsMulti|kExprFlagsDisallowEOC|kExprFlagsPrintError)); #endif ParserLine plines[] = { @@ -93,5 +92,6 @@ int main(const int argc, const char *const *const argv, assert(ast.root != NULL || plines[0].size == 0); assert(ast.root != NULL || ast.err.msg); + // FIXME: check for AST recursiveness // FIXME: free memory and assert no memory leaks } -- cgit From c286155bfa53c828ebe5479fd81a544740a92403 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 15 Oct 2017 19:06:41 +0300 Subject: viml/parser/expressions: Create tests for latest additions --- src/nvim/viml/parser/expressions.c | 25 +- test/helpers.lua | 7 + test/symbolic/klee/nvim/mbyte.c | 63 +- test/unit/viml/expressions/parser_spec.lua | 1430 ++++++++++++++++++++++++++++ 4 files changed, 1519 insertions(+), 6 deletions(-) diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index 75fcb17bf6..8928179349 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -1937,9 +1937,22 @@ viml_pexpr_parse_process_token: OP_MISSING; } NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeOption); - cur_node->data.opt.ident = cur_token.data.opt.name; - cur_node->data.opt.ident_len = cur_token.data.opt.len; - cur_node->data.opt.scope = cur_token.data.opt.scope; + if (cur_token.type == kExprLexInvalid) { + assert(cur_token.len == 1 + || (cur_token.len == 3 + && pline.data[cur_token.start.col + 2] == ':')); + cur_node->data.opt.ident = ( + pline.data + cur_token.start.col + cur_token.len); + cur_node->data.opt.ident_len = 0; + cur_node->data.opt.scope = ( + cur_token.len == 3 + ? (ExprOptScope)pline.data[cur_token.start.col + 1] + : kExprOptScopeUnspecified); + } else { + cur_node->data.opt.ident = cur_token.data.opt.name; + cur_node->data.opt.ident_len = cur_token.data.opt.len; + cur_node->data.opt.scope = cur_token.data.opt.scope; + } *top_node_p = cur_node; want_node = kENodeOperator; viml_parser_highlight(pstate, cur_token.start, 1, HL(OptionSigil)); @@ -1953,7 +1966,7 @@ viml_pexpr_parse_process_token: } viml_parser_highlight( pstate, shifted_pos(cur_token.start, scope_shift + 1), - cur_token.len - scope_shift + 1, HL(Option)); + cur_token.len - (scope_shift + 1), HL(Option)); break; } case kExprLexEnv: { @@ -1963,6 +1976,10 @@ viml_pexpr_parse_process_token: NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeEnvironment); cur_node->data.env.ident = pline.data + cur_token.start.col + 1; cur_node->data.env.ident_len = cur_token.len - 1; + if (cur_node->data.env.ident_len == 0) { + ERROR_FROM_TOKEN_AND_MSG(cur_token, + _("E15: Environment variable name missing")); + } *top_node_p = cur_node; want_node = kENodeOperator; viml_parser_highlight(pstate, cur_token.start, 1, HL(EnvironmentSigil)); diff --git a/test/helpers.lua b/test/helpers.lua index e7d8c185ba..6a42963d7f 100644 --- a/test/helpers.lua +++ b/test/helpers.lua @@ -352,7 +352,14 @@ format_luav = function(v, indent) end end ret = ret .. indent .. '}' + elseif type(v) == 'number' then + if v % 1 == 0 then + ret = ('%d'):format(v) + else + ret = ('%e'):format(v) + end else + print(type(v)) -- Not implemented yet assert(false) end diff --git a/test/symbolic/klee/nvim/mbyte.c b/test/symbolic/klee/nvim/mbyte.c index bfc191b1b7..f98a531206 100644 --- a/test/symbolic/klee/nvim/mbyte.c +++ b/test/symbolic/klee/nvim/mbyte.c @@ -89,10 +89,69 @@ char_u *string_convert(const vimconv_T *conv, char_u *data, size_t *size) return NULL; } +int utf_ptr2len_len(const char_u *p, int size) +{ + int len; + int i; + int m; + + len = utf8len_tab[*p]; + if (len == 1) + return 1; /* NUL, ascii or illegal lead byte */ + if (len > size) + m = size; /* incomplete byte sequence. */ + else + m = len; + for (i = 1; i < m; ++i) + if ((p[i] & 0xc0) != 0x80) + return 1; + return len; +} + int utfc_ptr2len_len(const char_u *p, int size) { - assert(false); - return 0; + int len; + int prevlen; + + if (size < 1 || *p == NUL) + return 0; + if (p[0] < 0x80 && (size == 1 || p[1] < 0x80)) /* be quick for ASCII */ + return 1; + + /* Skip over first UTF-8 char, stopping at a NUL byte. */ + len = utf_ptr2len_len(p, size); + + /* Check for illegal byte and incomplete byte sequence. */ + if ((len == 1 && p[0] >= 0x80) || len > size) + return 1; + + /* + * Check for composing characters. We can handle only the first six, but + * skip all of them (otherwise the cursor would get stuck). + */ + prevlen = 0; + while (len < size) { + int len_next_char; + + if (p[len] < 0x80) + break; + + /* + * Next character length should not go beyond size to ensure that + * UTF_COMPOSINGLIKE(...) does not read beyond size. + */ + len_next_char = utf_ptr2len_len(p + len, size - len); + if (len_next_char > size - len) + break; + + if (!UTF_COMPOSINGLIKE(p + prevlen, p + len)) + break; + + /* Skip over composing char */ + prevlen = len; + len += len_next_char; + } + return len; } int utf_char2len(const int c) diff --git a/test/unit/viml/expressions/parser_spec.lua b/test/unit/viml/expressions/parser_spec.lua index 5041708a3e..d95eaca79b 100644 --- a/test/unit/viml/expressions/parser_spec.lua +++ b/test/unit/viml/expressions/parser_spec.lua @@ -1453,6 +1453,24 @@ describe('Expressions parser', function() hl('CallingParenthesis', '(', 1), hl('CallingParenthesis', ')'), }) + check_parsing('{@a', 0, { + -- 012 + ast = { + { + 'UnknownFigure(-di):0:0:{', + children = { + 'Register(name=a):0:1:@a', + }, + }, + }, + err = { + arg = '{@a', + msg = 'E15: Missing closing figure brace: %.*s', + }, + }, { + hl('FigureBrace', '{'), + hl('Register', '@a'), + }) end) itp('works with lambdas and dictionaries', function() check_parsing('{}', 0, { @@ -2768,6 +2786,182 @@ describe('Expressions parser', function() hl('Identifier', 'c', 1), hl('Dict', '}'), }) + check_parsing('{', 0, { + -- 0 + ast = { + 'UnknownFigure(\\di):0:0:{', + }, + err = { + arg = '{', + msg = 'E15: Missing closing figure brace: %.*s', + }, + }, { + hl('FigureBrace', '{'), + }) + check_parsing('{a', 0, { + -- 01 + ast = { + { + 'UnknownFigure(\\di):0:0:{', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + }, + }, + }, + err = { + arg = '{a', + msg = 'E15: Missing closing figure brace: %.*s', + }, + }, { + hl('FigureBrace', '{'), + hl('Identifier', 'a'), + }) + check_parsing('{a,b', 0, { + -- 0123 + ast = { + { + 'Lambda(\\di):0:0:{', + children = { + { + 'Comma:0:2:,', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + 'PlainIdentifier(scope=0,ident=b):0:3:b', + }, + }, + }, + }, + }, + err = { + arg = '{a,b', + msg = 'E15: Missing closing figure brace for lambda: %.*s', + }, + }, { + hl('Lambda', '{'), + hl('Identifier', 'a'), + hl('Comma', ','), + hl('Identifier', 'b'), + }) + check_parsing('{a,b->', 0, { + -- 012345 + ast = { + { + 'Lambda(\\di):0:0:{', + children = { + { + 'Comma:0:2:,', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + 'PlainIdentifier(scope=0,ident=b):0:3:b', + }, + }, + 'Arrow:0:4:->', + }, + }, + }, + err = { + arg = '', + msg = 'E15: Expected value, got EOC: %.*s', + }, + }, { + hl('Lambda', '{'), + hl('Identifier', 'a'), + hl('Comma', ','), + hl('Identifier', 'b'), + hl('Arrow', '->'), + }) + check_parsing('{a,b->c', 0, { + -- 0123456 + ast = { + { + 'Lambda(\\di):0:0:{', + children = { + { + 'Comma:0:2:,', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + 'PlainIdentifier(scope=0,ident=b):0:3:b', + }, + }, + { + 'Arrow:0:4:->', + children = { + 'PlainIdentifier(scope=0,ident=c):0:6:c', + }, + }, + }, + }, + }, + err = { + arg = '{a,b->c', + msg = 'E15: Missing closing figure brace for lambda: %.*s', + }, + }, { + hl('Lambda', '{'), + hl('Identifier', 'a'), + hl('Comma', ','), + hl('Identifier', 'b'), + hl('Arrow', '->'), + hl('Identifier', 'c'), + }) + check_parsing('{a : b', 0, { + -- 012345 + ast = { + { + 'DictLiteral(-di):0:0:{', + children = { + { + 'Colon:0:2: :', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + 'PlainIdentifier(scope=0,ident=b):0:4: b', + }, + }, + }, + }, + }, + err = { + arg = '{a : b', + msg = 'E723: Missing end of Dictionary \'}\': %.*s', + }, + }, { + hl('Dict', '{'), + hl('Identifier', 'a'), + hl('Colon', ':', 1), + hl('Identifier', 'b', 1), + }) + check_parsing('{a : b,', 0, { + -- 0123456 + ast = { + { + 'DictLiteral(-di):0:0:{', + children = { + { + 'Comma:0:6:,', + children = { + { + 'Colon:0:2: :', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + 'PlainIdentifier(scope=0,ident=b):0:4: b', + }, + }, + }, + }, + }, + }, + }, + err = { + arg = '', + msg = 'E15: Expected value, got EOC: %.*s', + }, + }, { + hl('Dict', '{'), + hl('Identifier', 'a'), + hl('Colon', ':', 1), + hl('Identifier', 'b', 1), + hl('Comma', ','), + }) end) itp('works with ternary operator', function() check_parsing('a ? b : c', 0, { @@ -4574,6 +4768,38 @@ describe('Expressions parser', function() hl('Subscript', '['), hl('InvalidSubscript', ']'), }) + + check_parsing('[', 0, { + -- 0 + ast = { + 'ListLiteral:0:0:[', + }, + err = { + arg = '', + msg = 'E15: Expected value, got EOC: %.*s', + }, + }, { + hl('List', '['), + }) + + check_parsing('[1', 0, { + -- 01 + ast = { + { + 'ListLiteral:0:0:[', + children = { + 'Integer(val=1):0:1:1', + }, + }, + }, + err = { + arg = '[1', + msg = 'E697: Missing end of List \']\': %.*s', + }, + }, { + hl('List', '['), + hl('Number', '1'), + }) end) itp('works with strings', function() check_parsing('\'abc\'', 0, { @@ -5393,4 +5619,1208 @@ describe('Expressions parser', function() hl('DoubleQuotedString', '"'), }) end) + itp('works with multiplication-like operators', function() + check_parsing('2+2*2', 0, { + -- 01234 + ast = { + { + 'BinaryPlus:0:1:+', + children = { + 'Integer(val=2):0:0:2', + { + 'Multiplication:0:3:*', + children = { + 'Integer(val=2):0:2:2', + 'Integer(val=2):0:4:2', + }, + }, + }, + }, + }, + }, { + hl('Number', '2'), + hl('BinaryPlus', '+'), + hl('Number', '2'), + hl('Multiplication', '*'), + hl('Number', '2'), + }) + + check_parsing('2+2*', 0, { + -- 0123 + ast = { + { + 'BinaryPlus:0:1:+', + children = { + 'Integer(val=2):0:0:2', + { + 'Multiplication:0:3:*', + children = { + 'Integer(val=2):0:2:2', + }, + }, + }, + }, + }, + err = { + arg = '', + msg = 'E15: Expected value, got EOC: %.*s', + }, + }, { + hl('Number', '2'), + hl('BinaryPlus', '+'), + hl('Number', '2'), + hl('Multiplication', '*'), + }) + + check_parsing('2+*2', 0, { + -- 0123 + ast = { + { + 'BinaryPlus:0:1:+', + children = { + 'Integer(val=2):0:0:2', + { + 'Multiplication:0:2:*', + children = { + 'Missing:0:2:', + 'Integer(val=2):0:3:2', + }, + }, + }, + }, + }, + err = { + arg = '*2', + msg = 'E15: Unexpected multiplication-like operator: %.*s', + }, + }, { + hl('Number', '2'), + hl('BinaryPlus', '+'), + hl('InvalidMultiplication', '*'), + hl('Number', '2'), + }) + + check_parsing('2+2/2', 0, { + -- 01234 + ast = { + { + 'BinaryPlus:0:1:+', + children = { + 'Integer(val=2):0:0:2', + { + 'Division:0:3:/', + children = { + 'Integer(val=2):0:2:2', + 'Integer(val=2):0:4:2', + }, + }, + }, + }, + }, + }, { + hl('Number', '2'), + hl('BinaryPlus', '+'), + hl('Number', '2'), + hl('Division', '/'), + hl('Number', '2'), + }) + + check_parsing('2+2/', 0, { + -- 0123 + ast = { + { + 'BinaryPlus:0:1:+', + children = { + 'Integer(val=2):0:0:2', + { + 'Division:0:3:/', + children = { + 'Integer(val=2):0:2:2', + }, + }, + }, + }, + }, + err = { + arg = '', + msg = 'E15: Expected value, got EOC: %.*s', + }, + }, { + hl('Number', '2'), + hl('BinaryPlus', '+'), + hl('Number', '2'), + hl('Division', '/'), + }) + + check_parsing('2+/2', 0, { + -- 0123 + ast = { + { + 'BinaryPlus:0:1:+', + children = { + 'Integer(val=2):0:0:2', + { + 'Division:0:2:/', + children = { + 'Missing:0:2:', + 'Integer(val=2):0:3:2', + }, + }, + }, + }, + }, + err = { + arg = '/2', + msg = 'E15: Unexpected multiplication-like operator: %.*s', + }, + }, { + hl('Number', '2'), + hl('BinaryPlus', '+'), + hl('InvalidDivision', '/'), + hl('Number', '2'), + }) + + check_parsing('2+2%2', 0, { + -- 01234 + ast = { + { + 'BinaryPlus:0:1:+', + children = { + 'Integer(val=2):0:0:2', + { + 'Mod:0:3:%', + children = { + 'Integer(val=2):0:2:2', + 'Integer(val=2):0:4:2', + }, + }, + }, + }, + }, + }, { + hl('Number', '2'), + hl('BinaryPlus', '+'), + hl('Number', '2'), + hl('Mod', '%'), + hl('Number', '2'), + }) + + check_parsing('2+2%', 0, { + -- 0123 + ast = { + { + 'BinaryPlus:0:1:+', + children = { + 'Integer(val=2):0:0:2', + { + 'Mod:0:3:%', + children = { + 'Integer(val=2):0:2:2', + }, + }, + }, + }, + }, + err = { + arg = '', + msg = 'E15: Expected value, got EOC: %.*s', + }, + }, { + hl('Number', '2'), + hl('BinaryPlus', '+'), + hl('Number', '2'), + hl('Mod', '%'), + }) + + check_parsing('2+%2', 0, { + -- 0123 + ast = { + { + 'BinaryPlus:0:1:+', + children = { + 'Integer(val=2):0:0:2', + { + 'Mod:0:2:%', + children = { + 'Missing:0:2:', + 'Integer(val=2):0:3:2', + }, + }, + }, + }, + }, + err = { + arg = '%2', + msg = 'E15: Unexpected multiplication-like operator: %.*s', + }, + }, { + hl('Number', '2'), + hl('BinaryPlus', '+'), + hl('InvalidMod', '%'), + hl('Number', '2'), + }) + end) + itp('works with -', function() + check_parsing('@a', 0, { + ast = { + 'Register(name=a):0:0:@a', + }, + }, { + hl('Register', '@a'), + }) + check_parsing('-@a', 0, { + ast = { + { + 'UnaryMinus:0:0:-', + children = { + 'Register(name=a):0:1:@a', + }, + }, + }, + }, { + hl('UnaryMinus', '-'), + hl('Register', '@a'), + }) + check_parsing('@a-@b', 0, { + ast = { + { + 'BinaryMinus:0:2:-', + children = { + 'Register(name=a):0:0:@a', + 'Register(name=b):0:3:@b', + }, + }, + }, + }, { + hl('Register', '@a'), + hl('BinaryMinus', '-'), + hl('Register', '@b'), + }) + check_parsing('@a-@b-@c', 0, { + ast = { + { + 'BinaryMinus:0:5:-', + children = { + { + 'BinaryMinus:0:2:-', + children = { + 'Register(name=a):0:0:@a', + 'Register(name=b):0:3:@b', + }, + }, + 'Register(name=c):0:6:@c', + }, + }, + }, + }, { + hl('Register', '@a'), + hl('BinaryMinus', '-'), + hl('Register', '@b'), + hl('BinaryMinus', '-'), + hl('Register', '@c'), + }) + check_parsing('-@a-@b', 0, { + ast = { + { + 'BinaryMinus:0:3:-', + children = { + { + 'UnaryMinus:0:0:-', + children = { + 'Register(name=a):0:1:@a', + }, + }, + 'Register(name=b):0:4:@b', + }, + }, + }, + }, { + hl('UnaryMinus', '-'), + hl('Register', '@a'), + hl('BinaryMinus', '-'), + hl('Register', '@b'), + }) + check_parsing('-@a--@b', 0, { + ast = { + { + 'BinaryMinus:0:3:-', + children = { + { + 'UnaryMinus:0:0:-', + children = { + 'Register(name=a):0:1:@a', + }, + }, + { + 'UnaryMinus:0:4:-', + children = { + 'Register(name=b):0:5:@b', + }, + }, + }, + }, + }, + }, { + hl('UnaryMinus', '-'), + hl('Register', '@a'), + hl('BinaryMinus', '-'), + hl('UnaryMinus', '-'), + hl('Register', '@b'), + }) + check_parsing('@a@b', 0, { + ast = { + { + 'OpMissing:0:2:', + children = { + 'Register(name=a):0:0:@a', + 'Register(name=b):0:2:@b', + }, + }, + }, + err = { + arg = '@b', + msg = 'E15: Missing operator: %.*s', + }, + }, { + hl('Register', '@a'), + hl('InvalidRegister', '@b'), + }) + check_parsing(' @a \t @b', 0, { + ast = { + { + 'OpMissing:0:3:', + children = { + 'Register(name=a):0:0: @a', + 'Register(name=b):0:3: \t @b', + }, + }, + }, + err = { + arg = '@b', + msg = 'E15: Missing operator: %.*s', + }, + }, { + hl('Register', '@a', 1), + hl('InvalidSpacing', ' \t '), + hl('Register', '@b'), + }) + check_parsing('-', 0, { + ast = { + 'UnaryMinus:0:0:-', + }, + err = { + arg = '', + msg = 'E15: Expected value, got EOC: %.*s', + }, + }, { + hl('UnaryMinus', '-'), + }) + check_parsing(' -', 0, { + ast = { + 'UnaryMinus:0:0: -', + }, + err = { + arg = '', + msg = 'E15: Expected value, got EOC: %.*s', + }, + }, { + hl('UnaryMinus', '-', 1), + }) + check_parsing('@a- ', 0, { + ast = { + { + 'BinaryMinus:0:2:-', + children = { + 'Register(name=a):0:0:@a', + }, + }, + }, + err = { + arg = '', + msg = 'E15: Expected value, got EOC: %.*s', + }, + }, { + hl('Register', '@a'), + hl('BinaryMinus', '-'), + }) + end) + itp('works with logical operators', function() + check_parsing('a && b || c && d', 0, { + -- 0123456789012345 + -- 0 1 + ast = { + { + 'Or:0:6: ||', + children = { + { + 'And:0:1: &&', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainIdentifier(scope=0,ident=b):0:4: b', + }, + }, + { + 'And:0:11: &&', + children = { + 'PlainIdentifier(scope=0,ident=c):0:9: c', + 'PlainIdentifier(scope=0,ident=d):0:14: d', + }, + }, + }, + }, + }, + }, { + hl('Identifier', 'a'), + hl('And', '&&', 1), + hl('Identifier', 'b', 1), + hl('Or', '||', 1), + hl('Identifier', 'c', 1), + hl('And', '&&', 1), + hl('Identifier', 'd', 1), + }) + + check_parsing('&& a', 0, { + -- 0123 + ast = { + { + 'And:0:0:&&', + children = { + 'Missing:0:0:', + 'PlainIdentifier(scope=0,ident=a):0:2: a', + }, + }, + }, + err = { + arg = '&& a', + msg = 'E15: Unexpected and operator: %.*s', + }, + }, { + hl('InvalidAnd', '&&'), + hl('Identifier', 'a', 1), + }) + + check_parsing('|| a', 0, { + -- 0123 + ast = { + { + 'Or:0:0:||', + children = { + 'Missing:0:0:', + 'PlainIdentifier(scope=0,ident=a):0:2: a', + }, + }, + }, + err = { + arg = '|| a', + msg = 'E15: Unexpected or operator: %.*s', + }, + }, { + hl('InvalidOr', '||'), + hl('Identifier', 'a', 1), + }) + + check_parsing('a||', 0, { + -- 012 + ast = { + { + 'Or:0:1:||', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + }, + }, + }, + err = { + arg = '', + msg = 'E15: Expected value, got EOC: %.*s', + }, + }, { + hl('Identifier', 'a'), + hl('Or', '||'), + }) + + check_parsing('a&&', 0, { + -- 012 + ast = { + { + 'And:0:1:&&', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + }, + }, + }, + err = { + arg = '', + msg = 'E15: Expected value, got EOC: %.*s', + }, + }, { + hl('Identifier', 'a'), + hl('And', '&&'), + }) + + check_parsing('(&&)', 0, { + -- 0123 + ast = { + { + 'Nested:0:0:(', + children = { + { + 'And:0:1:&&', + children = { + 'Missing:0:1:', + 'Missing:0:3:', + }, + }, + }, + }, + }, + err = { + arg = '&&)', + msg = 'E15: Unexpected and operator: %.*s', + }, + }, { + hl('NestingParenthesis', '('), + hl('InvalidAnd', '&&'), + hl('InvalidNestingParenthesis', ')'), + }) + + check_parsing('(||)', 0, { + -- 0123 + ast = { + { + 'Nested:0:0:(', + children = { + { + 'Or:0:1:||', + children = { + 'Missing:0:1:', + 'Missing:0:3:', + }, + }, + }, + }, + }, + err = { + arg = '||)', + msg = 'E15: Unexpected or operator: %.*s', + }, + }, { + hl('NestingParenthesis', '('), + hl('InvalidOr', '||'), + hl('InvalidNestingParenthesis', ')'), + }) + + check_parsing('(a||)', 0, { + -- 01234 + ast = { + { + 'Nested:0:0:(', + children = { + { + 'Or:0:2:||', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + 'Missing:0:4:', + }, + }, + }, + }, + }, + err = { + arg = ')', + msg = 'E15: Expected value, got parenthesis: %.*s', + }, + }, { + hl('NestingParenthesis', '('), + hl('Identifier', 'a'), + hl('Or', '||'), + hl('InvalidNestingParenthesis', ')'), + }) + + check_parsing('(a&&)', 0, { + -- 01234 + ast = { + { + 'Nested:0:0:(', + children = { + { + 'And:0:2:&&', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + 'Missing:0:4:', + }, + }, + }, + }, + }, + err = { + arg = ')', + msg = 'E15: Expected value, got parenthesis: %.*s', + }, + }, { + hl('NestingParenthesis', '('), + hl('Identifier', 'a'), + hl('And', '&&'), + hl('InvalidNestingParenthesis', ')'), + }) + + check_parsing('(&&a)', 0, { + -- 01234 + ast = { + { + 'Nested:0:0:(', + children = { + { + 'And:0:1:&&', + children = { + 'Missing:0:1:', + 'PlainIdentifier(scope=0,ident=a):0:3:a', + }, + }, + }, + }, + }, + err = { + arg = '&&a)', + msg = 'E15: Unexpected and operator: %.*s', + }, + }, { + hl('NestingParenthesis', '('), + hl('InvalidAnd', '&&'), + hl('Identifier', 'a'), + hl('NestingParenthesis', ')'), + }) + + check_parsing('(||a)', 0, { + -- 01234 + ast = { + { + 'Nested:0:0:(', + children = { + { + 'Or:0:1:||', + children = { + 'Missing:0:1:', + 'PlainIdentifier(scope=0,ident=a):0:3:a', + }, + }, + }, + }, + }, + err = { + arg = '||a)', + msg = 'E15: Unexpected or operator: %.*s', + }, + }, { + hl('NestingParenthesis', '('), + hl('InvalidOr', '||'), + hl('Identifier', 'a'), + hl('NestingParenthesis', ')'), + }) + end) + itp('works with &opt', function() + check_parsing('&', 0, { + -- 0 + ast = { + 'Option(scope=0,ident=):0:0:&', + }, + err = { + arg = '&', + msg = 'E112: Option name missing: %.*s', + }, + }, { + hl('InvalidOptionSigil', '&'), + }) + + check_parsing('&opt', 0, { + -- 0123 + ast = { + 'Option(scope=0,ident=opt):0:0:&opt', + }, + }, { + hl('OptionSigil', '&'), + hl('Option', 'opt'), + }) + + check_parsing('&l:opt', 0, { + -- 012345 + ast = { + 'Option(scope=l,ident=opt):0:0:&l:opt', + }, + }, { + hl('OptionSigil', '&'), + hl('OptionScope', 'l'), + hl('OptionScopeDelimiter', ':'), + hl('Option', 'opt'), + }) + + check_parsing('&g:opt', 0, { + -- 012345 + ast = { + 'Option(scope=g,ident=opt):0:0:&g:opt', + }, + }, { + hl('OptionSigil', '&'), + hl('OptionScope', 'g'), + hl('OptionScopeDelimiter', ':'), + hl('Option', 'opt'), + }) + + check_parsing('&s:opt', 0, { + -- 012345 + ast = { + { + 'Colon:0:2::', + children = { + 'Option(scope=0,ident=s):0:0:&s', + 'PlainIdentifier(scope=0,ident=opt):0:3:opt', + }, + }, + }, + err = { + arg = ':opt', + msg = 'E15: Colon outside of dictionary or ternary operator: %.*s', + }, + }, { + hl('OptionSigil', '&'), + hl('Option', 's'), + hl('InvalidColon', ':'), + hl('Identifier', 'opt'), + }) + + check_parsing('& ', 0, { + -- 01 + ast = { + 'Option(scope=0,ident=):0:0:&', + }, + err = { + arg = '& ', + msg = 'E112: Option name missing: %.*s', + }, + }, { + hl('InvalidOptionSigil', '&'), + }) + + check_parsing('&-', 0, { + -- 01 + ast = { + { + 'BinaryMinus:0:1:-', + children = { + 'Option(scope=0,ident=):0:0:&', + }, + }, + }, + err = { + arg = '&-', + msg = 'E112: Option name missing: %.*s', + }, + }, { + hl('InvalidOptionSigil', '&'), + hl('BinaryMinus', '-'), + }) + + check_parsing('&A', 0, { + -- 01 + ast = { + 'Option(scope=0,ident=A):0:0:&A', + }, + }, { + hl('OptionSigil', '&'), + hl('Option', 'A'), + }) + + check_parsing('&xxx_yyy', 0, { + -- 01234567 + ast = { + { + 'OpMissing:0:4:', + children = { + 'Option(scope=0,ident=xxx):0:0:&xxx', + 'PlainIdentifier(scope=0,ident=_yyy):0:4:_yyy', + }, + }, + }, + err = { + arg = '_yyy', + msg = 'E15: Missing operator: %.*s', + }, + }, { + hl('OptionSigil', '&'), + hl('Option', 'xxx'), + hl('InvalidIdentifier', '_yyy'), + }) + + check_parsing('(1+&)', 0, { + -- 01234 + ast = { + { + 'Nested:0:0:(', + children = { + { + 'BinaryPlus:0:2:+', + children = { + 'Integer(val=1):0:1:1', + 'Option(scope=0,ident=):0:3:&', + }, + }, + }, + }, + }, + err = { + arg = '&)', + msg = 'E112: Option name missing: %.*s', + }, + }, { + hl('NestingParenthesis', '('), + hl('Number', '1'), + hl('BinaryPlus', '+'), + hl('InvalidOptionSigil', '&'), + hl('NestingParenthesis', ')'), + }) + + check_parsing('(&+1)', 0, { + -- 01234 + ast = { + { + 'Nested:0:0:(', + children = { + { + 'BinaryPlus:0:2:+', + children = { + 'Option(scope=0,ident=):0:1:&', + 'Integer(val=1):0:3:1', + }, + }, + }, + }, + }, + err = { + arg = '&+1)', + msg = 'E112: Option name missing: %.*s', + }, + }, { + hl('NestingParenthesis', '('), + hl('InvalidOptionSigil', '&'), + hl('BinaryPlus', '+'), + hl('Number', '1'), + hl('NestingParenthesis', ')'), + }) + end) + itp('works with $ENV', function() + check_parsing('$', 0, { + -- 0 + ast = { + 'Environment(ident=):0:0:$', + }, + err = { + arg = '$', + msg = 'E15: Environment variable name missing', + }, + }, { + hl('InvalidEnvironmentSigil', '$'), + }) + + check_parsing('$g:A', 0, { + -- 0123 + ast = { + { + 'Colon:0:2::', + children = { + 'Environment(ident=g):0:0:$g', + 'PlainIdentifier(scope=0,ident=A):0:3:A', + }, + }, + }, + err = { + arg = ':A', + msg = 'E15: Colon outside of dictionary or ternary operator: %.*s', + }, + }, { + hl('EnvironmentSigil', '$'), + hl('Environment', 'g'), + hl('InvalidColon', ':'), + hl('Identifier', 'A'), + }) + + check_parsing('$A', 0, { + -- 01 + ast = { + 'Environment(ident=A):0:0:$A', + }, + }, { + hl('EnvironmentSigil', '$'), + hl('Environment', 'A'), + }) + + check_parsing('$ABC', 0, { + -- 0123 + ast = { + 'Environment(ident=ABC):0:0:$ABC', + }, + }, { + hl('EnvironmentSigil', '$'), + hl('Environment', 'ABC'), + }) + + check_parsing('(1+$)', 0, { + -- 01234 + ast = { + { + 'Nested:0:0:(', + children = { + { + 'BinaryPlus:0:2:+', + children = { + 'Integer(val=1):0:1:1', + 'Environment(ident=):0:3:$', + }, + }, + }, + }, + }, + err = { + arg = '$)', + msg = 'E15: Environment variable name missing', + }, + }, { + hl('NestingParenthesis', '('), + hl('Number', '1'), + hl('BinaryPlus', '+'), + hl('InvalidEnvironmentSigil', '$'), + hl('NestingParenthesis', ')'), + }) + + check_parsing('($+1)', 0, { + -- 01234 + ast = { + { + 'Nested:0:0:(', + children = { + { + 'BinaryPlus:0:2:+', + children = { + 'Environment(ident=):0:1:$', + 'Integer(val=1):0:3:1', + }, + }, + }, + }, + }, + err = { + arg = '$+1)', + msg = 'E15: Environment variable name missing', + }, + }, { + hl('NestingParenthesis', '('), + hl('InvalidEnvironmentSigil', '$'), + hl('BinaryPlus', '+'), + hl('Number', '1'), + hl('NestingParenthesis', ')'), + }) + + check_parsing('$_ABC', 0, { + -- 01234 + ast = { + 'Environment(ident=_ABC):0:0:$_ABC', + }, + }, { + hl('EnvironmentSigil', '$'), + hl('Environment', '_ABC'), + }) + + check_parsing('$_', 0, { + -- 01 + ast = { + 'Environment(ident=_):0:0:$_', + }, + }, { + hl('EnvironmentSigil', '$'), + hl('Environment', '_'), + }) + + check_parsing('$ABC_DEF', 0, { + -- 01234567 + ast = { + 'Environment(ident=ABC_DEF):0:0:$ABC_DEF', + }, + }, { + hl('EnvironmentSigil', '$'), + hl('Environment', 'ABC_DEF'), + }) + end) + itp('works with unary !', function() + check_parsing('!', 0, { + -- 0 + ast = { + 'Not:0:0:!', + }, + err = { + arg = '', + msg = 'E15: Expected value, got EOC: %.*s', + }, + }, { + hl('Not', '!'), + }) + + check_parsing('!!', 0, { + -- 01 + ast = { + { + 'Not:0:0:!', + children = { + 'Not:0:1:!', + }, + }, + }, + err = { + arg = '', + msg = 'E15: Expected value, got EOC: %.*s', + }, + }, { + hl('Not', '!'), + hl('Not', '!'), + }) + + check_parsing('!!1', 0, { + -- 012 + ast = { + { + 'Not:0:0:!', + children = { + { + 'Not:0:1:!', + children = { + 'Integer(val=1):0:2:1', + }, + }, + }, + }, + }, + }, { + hl('Not', '!'), + hl('Not', '!'), + hl('Number', '1'), + }) + + check_parsing('!1', 0, { + -- 01 + ast = { + { + 'Not:0:0:!', + children = { + 'Integer(val=1):0:1:1', + }, + }, + }, + }, { + hl('Not', '!'), + hl('Number', '1'), + }) + + check_parsing('(!1)', 0, { + -- 0123 + ast = { + { + 'Nested:0:0:(', + children = { + { + 'Not:0:1:!', + children = { + 'Integer(val=1):0:2:1', + }, + }, + }, + }, + }, + }, { + hl('NestingParenthesis', '('), + hl('Not', '!'), + hl('Number', '1'), + hl('NestingParenthesis', ')'), + }) + + check_parsing('(!)', 0, { + -- 012 + ast = { + { + 'Nested:0:0:(', + children = { + { + 'Not:0:1:!', + children = { + 'Missing:0:2:', + }, + }, + }, + }, + }, + err = { + arg = ')', + msg = 'E15: Expected value, got parenthesis: %.*s', + }, + }, { + hl('NestingParenthesis', '('), + hl('Not', '!'), + hl('InvalidNestingParenthesis', ')'), + }) + + check_parsing('(1!2)', 0, { + -- 01234 + ast = { + { + 'Nested:0:0:(', + children = { + { + 'OpMissing:0:2:', + children = { + 'Integer(val=1):0:1:1', + { + 'Not:0:2:!', + children = { + 'Integer(val=2):0:3:2', + }, + }, + }, + }, + }, + }, + }, + err = { + arg = '!2)', + msg = 'E15: Missing operator: %.*s', + }, + }, { + hl('NestingParenthesis', '('), + hl('Number', '1'), + hl('InvalidNot', '!'), + hl('Number', '2'), + hl('NestingParenthesis', ')'), + }) + + check_parsing('1!2', 0, { + -- 012 + ast = { + { + 'OpMissing:0:1:', + children = { + 'Integer(val=1):0:0:1', + { + 'Not:0:1:!', + children = { + 'Integer(val=2):0:2:2', + }, + }, + }, + }, + }, + err = { + arg = '!2', + msg = 'E15: Missing operator: %.*s', + }, + }, { + hl('Number', '1'), + hl('InvalidNot', '!'), + hl('Number', '2'), + }) + end) end) -- cgit From 206f7ae76a4a06c6bac10402649e515dd3fb2365 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 15 Oct 2017 19:18:17 +0300 Subject: unittests: Test some edge cases --- test/unit/viml/expressions/parser_spec.lua | 44 ++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/test/unit/viml/expressions/parser_spec.lua b/test/unit/viml/expressions/parser_spec.lua index d95eaca79b..407114ff33 100644 --- a/test/unit/viml/expressions/parser_spec.lua +++ b/test/unit/viml/expressions/parser_spec.lua @@ -4241,6 +4241,40 @@ describe('Expressions parser', function() hl('UnaryPlus', '+', 1), hl('Identifier', 'b'), }) + + check_parsing('a. b', 0, { + -- 0123 + ast = { + { + 'Concat:0:1:.', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainIdentifier(scope=0,ident=b):0:2: b', + }, + }, + }, + }, { + hl('Identifier', 'a'), + hl('ConcatOrSubscript', '.'), + hl('Identifier', 'b', 1), + }) + + check_parsing('a. 1', 0, { + -- 0123 + ast = { + { + 'Concat:0:1:.', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'Integer(val=1):0:2: 1', + }, + }, + }, + }, { + hl('Identifier', 'a'), + hl('ConcatOrSubscript', '.'), + hl('Number', '1', 1), + }) end) itp('works with bracket subscripts', function() check_parsing(':', 0, { @@ -6823,4 +6857,14 @@ describe('Expressions parser', function() hl('Number', '2'), }) end) + itp('works (KLEE tests)', function() + check_parsing('\0002&A:\000', 0, { + ast = nil, + err = { + arg = '', + msg = 'E15: Expected value, got EOC: %.*s', + }, + }, { + }) + end) end) -- cgit From 6c19cbef2611c389da6f3e06d8c8eb635d065774 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 15 Oct 2017 20:05:35 +0300 Subject: viml/parser/expressions,tests: Add AST freeing, with sanity checks --- src/nvim/viml/parser/expressions.c | 239 +++++++++++++++++++++++---- test/helpers.lua | 2 + test/symbolic/klee/viml_expressions_parser.c | 4 +- test/unit/viml/expressions/parser_spec.lua | 1 + 4 files changed, 210 insertions(+), 36 deletions(-) diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index 8928179349..2f7ec6bcca 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -642,37 +642,6 @@ static const char *const eltkn_opt_scope_tab[] = { [kExprOptScopeLocal] = "Local", }; -/// Represent `int` character as a string -/// -/// Converts -/// - ASCII digits into '{digit}' -/// - ASCII printable characters into a single-character strings -/// - everything else to numbers. -/// -/// @param[in] ch Character to convert. -/// -/// @return Converted string, stored in a static buffer (overriden after each -/// call). -static const char *intchar2str(const int ch) - FUNC_ATTR_WARN_UNUSED_RESULT -{ - static char buf[sizeof(int) * 3 + 1]; - if (' ' <= ch && ch < 0x7f) { - if (ascii_isdigit(ch)) { - buf[0] = '\''; - buf[1] = (char)ch; - buf[2] = '\''; - buf[3] = NUL; - } else { - buf[0] = (char)ch; - buf[1] = NUL; - } - } else { - snprintf(buf, sizeof(buf), "%i", ch); - } - return buf; -} - /// Represent token as a string /// /// Intended for testing and debugging purposes. @@ -756,6 +725,78 @@ viml_pexpr_repr_token_end: return ret; } +static const char *const east_node_type_tab[] = { + [kExprNodeMissing] = "Missing", + [kExprNodeOpMissing] = "OpMissing", + [kExprNodeTernary] = "Ternary", + [kExprNodeTernaryValue] = "TernaryValue", + [kExprNodeRegister] = "Register", + [kExprNodeSubscript] = "Subscript", + [kExprNodeListLiteral] = "ListLiteral", + [kExprNodeUnaryPlus] = "UnaryPlus", + [kExprNodeBinaryPlus] = "BinaryPlus", + [kExprNodeNested] = "Nested", + [kExprNodeCall] = "Call", + [kExprNodePlainIdentifier] = "PlainIdentifier", + [kExprNodePlainKey] = "PlainKey", + [kExprNodeComplexIdentifier] = "ComplexIdentifier", + [kExprNodeUnknownFigure] = "UnknownFigure", + [kExprNodeLambda] = "Lambda", + [kExprNodeDictLiteral] = "DictLiteral", + [kExprNodeCurlyBracesIdentifier] = "CurlyBracesIdentifier", + [kExprNodeComma] = "Comma", + [kExprNodeColon] = "Colon", + [kExprNodeArrow] = "Arrow", + [kExprNodeComparison] = "Comparison", + [kExprNodeConcat] = "Concat", + [kExprNodeConcatOrSubscript] = "ConcatOrSubscript", + [kExprNodeInteger] = "Integer", + [kExprNodeFloat] = "Float", + [kExprNodeSingleQuotedString] = "SingleQuotedString", + [kExprNodeDoubleQuotedString] = "DoubleQuotedString", + [kExprNodeOr] = "Or", + [kExprNodeAnd] = "And", + [kExprNodeUnaryMinus] = "UnaryMinus", + [kExprNodeBinaryMinus] = "BinaryMinus", + [kExprNodeNot] = "Not", + [kExprNodeMultiplication] = "Multiplication", + [kExprNodeDivision] = "Division", + [kExprNodeMod] = "Mod", + [kExprNodeOption] = "Option", + [kExprNodeEnvironment] = "Environment", +}; + +/// Represent `int` character as a string +/// +/// Converts +/// - ASCII digits into '{digit}' +/// - ASCII printable characters into a single-character strings +/// - everything else to numbers. +/// +/// @param[in] ch Character to convert. +/// +/// @return Converted string, stored in a static buffer (overriden after each +/// call). +static const char *intchar2str(const int ch) + FUNC_ATTR_WARN_UNUSED_RESULT +{ + static char buf[sizeof(int) * 3 + 1]; + if (' ' <= ch && ch < 0x7f) { + if (ascii_isdigit(ch)) { + buf[0] = '\''; + buf[1] = (char)ch; + buf[2] = '\''; + buf[3] = NUL; + } else { + buf[0] = (char)ch; + buf[1] = NUL; + } + } else { + snprintf(buf, sizeof(buf), "%i", ch); + } + return buf; +} + #ifdef UNIT_TESTING #include @@ -767,9 +808,9 @@ static inline void viml_pexpr_debug_print_ast_node( if (*eastnode_p == NULL) { fprintf(stderr, "%s %p : NULL\n", prefix, (void *)eastnode_p); } else { - fprintf(stderr, "%s %p : %p : %c : %zu:%zu:%zu\n", + fprintf(stderr, "%s %p : %p : %s : %zu:%zu:%zu\n", prefix, (void *)eastnode_p, (void *)(*eastnode_p), - (*eastnode_p)->type, (*eastnode_p)->start.line, + east_node_type_tab[(*eastnode_p)->type], (*eastnode_p)->start.line, (*eastnode_p)->start.col, (*eastnode_p)->len); } } @@ -800,11 +841,141 @@ static inline void viml_pexpr_debug_print_token( #define PSTACK_P(msg) \ viml_pexpr_debug_print_ast_stack(ast_stack, #msg) #define PNODE_P(eastnode_p, msg) \ - viml_pexpr_debug_print_ast_node((const ExprASTNode *const *)ast_stack, #msg) + viml_pexpr_debug_print_ast_node((const ExprASTNode *const *)eastnode_p, \ + (#msg)) #define PTOKEN(tkn) \ viml_pexpr_debug_print_token(pstate, tkn) #endif +#ifndef NDEBUG +static const uint8_t node_maxchildren[] = { + [kExprNodeMissing] = 0, + [kExprNodeOpMissing] = 2, + [kExprNodeTernary] = 2, + [kExprNodeTernaryValue] = 2, + [kExprNodeRegister] = 0, + [kExprNodeSubscript] = 2, + [kExprNodeListLiteral] = 1, + [kExprNodeUnaryPlus] = 1, + [kExprNodeBinaryPlus] = 2, + [kExprNodeNested] = 1, + [kExprNodeCall] = 2, + [kExprNodePlainIdentifier] = 0, + [kExprNodePlainKey] = 0, + [kExprNodeComplexIdentifier] = 2, + [kExprNodeUnknownFigure] = 1, + [kExprNodeLambda] = 2, + [kExprNodeDictLiteral] = 1, + [kExprNodeCurlyBracesIdentifier] = 1, + [kExprNodeComma] = 2, + [kExprNodeColon] = 2, + [kExprNodeArrow] = 2, + [kExprNodeComparison] = 2, + [kExprNodeConcat] = 2, + [kExprNodeConcatOrSubscript] = 2, + [kExprNodeInteger] = 0, + [kExprNodeFloat] = 0, + [kExprNodeSingleQuotedString] = 0, + [kExprNodeDoubleQuotedString] = 0, + [kExprNodeOr] = 2, + [kExprNodeAnd] = 2, + [kExprNodeUnaryMinus] = 1, + [kExprNodeBinaryMinus] = 2, + [kExprNodeNot] = 1, + [kExprNodeMultiplication] = 2, + [kExprNodeDivision] = 2, + [kExprNodeMod] = 2, + [kExprNodeOption] = 0, + [kExprNodeEnvironment] = 0, +}; +#endif + +/// Free memory occupied by AST +/// +/// @param ast AST stack to free. +void viml_pexpr_free_ast(ExprAST ast) +{ + ExprASTStack ast_stack; + kvi_init(ast_stack); + kvi_push(ast_stack, &ast.root); + while (kv_size(ast_stack)) { + ExprASTNode **const cur_node = kv_last(ast_stack); +#ifndef NDEBUG + // Explicitly check for AST recursiveness. + for (size_t i = 0 ; i < kv_size(ast_stack) - 1 ; i++) { + assert(*kv_A(ast_stack, i) != *cur_node); + } +#endif + if (*cur_node == NULL) { + assert(kv_size(ast_stack) == 1); + kv_drop(ast_stack, 1); + } else if ((*cur_node)->children != NULL) { +#ifndef NDEBUG + const uint8_t maxchildren = node_maxchildren[(*cur_node)->type]; + assert(maxchildren > 0); + assert(maxchildren <= 2); + assert(maxchildren == 1 + ? (*cur_node)->children->next == NULL + : ((*cur_node)->children->next == NULL + || (*cur_node)->children->next->next == NULL)); +#endif + kvi_push(ast_stack, &(*cur_node)->children); + } else if ((*cur_node)->next != NULL) { + kvi_push(ast_stack, &(*cur_node)->next); + } else if (*cur_node != NULL) { + kv_drop(ast_stack, 1); + switch ((*cur_node)->type) { + case kExprNodeDoubleQuotedString: + case kExprNodeSingleQuotedString: { + xfree((*cur_node)->data.str.value); + break; + } + case kExprNodeMissing: + case kExprNodeOpMissing: + case kExprNodeTernary: + case kExprNodeTernaryValue: + case kExprNodeRegister: + case kExprNodeSubscript: + case kExprNodeListLiteral: + case kExprNodeUnaryPlus: + case kExprNodeBinaryPlus: + case kExprNodeNested: + case kExprNodeCall: + case kExprNodePlainIdentifier: + case kExprNodePlainKey: + case kExprNodeComplexIdentifier: + case kExprNodeUnknownFigure: + case kExprNodeLambda: + case kExprNodeDictLiteral: + case kExprNodeCurlyBracesIdentifier: + case kExprNodeComma: + case kExprNodeColon: + case kExprNodeArrow: + case kExprNodeComparison: + case kExprNodeConcat: + case kExprNodeConcatOrSubscript: + case kExprNodeInteger: + case kExprNodeFloat: + case kExprNodeOr: + case kExprNodeAnd: + case kExprNodeUnaryMinus: + case kExprNodeBinaryMinus: + case kExprNodeNot: + case kExprNodeMultiplication: + case kExprNodeDivision: + case kExprNodeMod: + case kExprNodeOption: + case kExprNodeEnvironment: { + break; + } + } + xfree(*cur_node); + *cur_node = NULL; + } + } + kvi_destroy(ast_stack); +} + // start = s ternary_expr s EOC // ternary_expr = binop_expr // ( s Question s ternary_expr s Colon s ternary_expr s )? diff --git a/test/helpers.lua b/test/helpers.lua index 6a42963d7f..83e78ba059 100644 --- a/test/helpers.lua +++ b/test/helpers.lua @@ -358,6 +358,8 @@ format_luav = function(v, indent) else ret = ('%e'):format(v) end + elseif type(v) == 'nil' then + ret = 'nil' else print(type(v)) -- Not implemented yet diff --git a/test/symbolic/klee/viml_expressions_parser.c b/test/symbolic/klee/viml_expressions_parser.c index a4fbdb6bf4..c0cedceb21 100644 --- a/test/symbolic/klee/viml_expressions_parser.c +++ b/test/symbolic/klee/viml_expressions_parser.c @@ -92,6 +92,6 @@ int main(const int argc, const char *const *const argv, assert(ast.root != NULL || plines[0].size == 0); assert(ast.root != NULL || ast.err.msg); - // FIXME: check for AST recursiveness - // FIXME: free memory and assert no memory leaks + viml_pexpr_free_ast(ast); + assert(allocated_memory == 0); } diff --git a/test/unit/viml/expressions/parser_spec.lua b/test/unit/viml/expressions/parser_spec.lua index 407114ff33..9624ced022 100644 --- a/test/unit/viml/expressions/parser_spec.lua +++ b/test/unit/viml/expressions/parser_spec.lua @@ -253,6 +253,7 @@ describe('Expressions parser', function() end eq(exp_highlighting, hls) end + lib.viml_pexpr_free_ast(east) end local function hl(group, str, shift) return function(next_col) -- cgit From 57bb3346d95f325d4894d41c88d3a1434de2d2df Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 15 Oct 2017 20:43:16 +0300 Subject: viml/parser/expressions: Update some comments and add another check --- src/nvim/viml/parser/expressions.c | 104 ++++++++++++++----------------------- 1 file changed, 38 insertions(+), 66 deletions(-) diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index 2f7ec6bcca..370034943e 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -976,59 +976,6 @@ void viml_pexpr_free_ast(ExprAST ast) kvi_destroy(ast_stack); } -// start = s ternary_expr s EOC -// ternary_expr = binop_expr -// ( s Question s ternary_expr s Colon s ternary_expr s )? -// binop_expr = unaryop_expr ( binop unaryop_expr )? -// unaryop_expr = ( unaryop )? subscript_expr -// subscript_expr = subscript_expr subscript -// | value_expr -// subscript = Bracket('[') s ternary_expr s Bracket(']') -// | s Parenthesis('(') call_args Parenthesis(')') -// | Dot ( PlainIdentifier | Number )+ -// # Note: `s` before Parenthesis('(') is only valid if preceding subscript_expr -// # is PlainIdentifier -// value_expr = ( float | Number -// | DoubleQuotedString | SingleQuotedString -// | paren_expr -// | list_literal -// | lambda_literal -// | dict_literal -// | Environment -// | Option -// | Register -// | var ) -// float = Number Dot Number ( PlainIdentifier('e') ( Plus | Minus )? Number )? -// # Note: `1.2.3` is concat and not float. `"abc".2.3` is also concat without -// # floats. -// paren_expr = Parenthesis('(') s ternary_expr s Parenthesis(')') -// list_literal = Bracket('[') s -// ( ternary_expr s Comma s )* -// ternary_expr? s -// Bracket(']') -// dict_literal = FigureBrace('{') s -// ( ternary_expr s Colon s ternary_expr s Comma s )* -// ( ternary_expr s Colon s ternary_expr s )? -// FigureBrace('}') -// lambda_literal = FigureBrace('{') s -// ( PlainIdentifier s Comma s )* -// PlainIdentifier s -// Arrow s -// ternary_expr s -// FigureBrace('}') -// var = varchunk+ -// varchunk = PlainIdentifier -// | Comparison("is" | "is#" | "isnot" | "isnot#") -// | FigureBrace('{') s ternary_expr s FigureBrace('}') -// call_args = ( s ternary_expr s Comma s )* s ternary_expr? s -// binop = s ( Plus | Minus | Dot -// | Comparison -// | Multiplication -// | Or -// | And ) s -// unaryop = s ( Not | Plus | Minus ) s -// s = Spacing? -// // Binary operator precedence and associativity: // // Operator | Precedence | Associativity @@ -1885,6 +1832,14 @@ static void parse_quoted_string(ParserState *const pstate, kvi_destroy(shifts); } +/// Additional flags to pass to lexer depending on want_node +static const int want_node_to_lexer_flags[] = { + [kENodeValue] = kELFlagIsNotCmp, + [kENodeOperator] = kELFlagForbidScope, + [kENodeArgument] = kELFlagIsNotCmp, + [kENodeArgumentSeparator] = kELFlagForbidScope, +}; + /// Parse one VimL expression /// /// @param pstate Parser state. @@ -1902,26 +1857,25 @@ ExprAST viml_pexpr_parse(ParserState *const pstate, const int flags) }, .root = NULL, }; + // Expression stack contains current branch in AST tree: that is + // - Stack item 0 contains root of the tree, i.e. &ast->root. + // - Stack item i points to the previous stack items’ last child. + // + // When parser expects “value” node that is something like identifier or "[" + // (list start) last stack item contains NULL. Otherwise last stack item is + // supposed to contain last “finished” value: e.g. "1" or "+(1, 1)" (node + // representing "1+1"). + // + // Both kENodeValue and kENodeArgument stand for “value” nodes. ExprASTStack ast_stack; kvi_init(ast_stack); kvi_push(ast_stack, &ast.root); - // Expressions stack: - // 1. *last is NULL if want_node is kExprLexValue. Indicates where expression - // is to be put. - // 2. *last is not NULL otherwise, indicates current expression to be used as - // an operator argument. ExprASTWantedNode want_node = kENodeValue; LexExprToken prev_token = { .type = kExprLexMissing }; bool highlighted_prev_spacing = false; // Lambda node, valid when parsing lambda arguments only. ExprASTNode *lambda_node = NULL; do { - const int want_node_to_lexer_flags[] = { - [kENodeValue] = kELFlagIsNotCmp, - [kENodeOperator] = kELFlagForbidScope, - [kENodeArgument] = kELFlagIsNotCmp, - [kENodeArgumentSeparator] = kELFlagForbidScope, - }; const bool is_concat_or_subscript = ( want_node == kENodeValue && kv_size(ast_stack) > 1 @@ -1965,9 +1919,27 @@ viml_pexpr_parse_process_token: } const ParserLine pline = pstate->reader.lines.items[cur_token.start.line]; ExprASTNode **const top_node_p = kv_last(ast_stack); + assert(kv_size(ast_stack) >= 1); ExprASTNode *cur_node = NULL; - assert((want_node == kENodeValue || want_node == kENodeArgument) - == (*top_node_p == NULL)); +#ifndef NDEBUG + const bool want_value = ( + want_node == kENodeValue || want_node == kENodeArgument); + assert(want_value == (*top_node_p == NULL)); + assert(kv_A(ast_stack, 0) == &ast.root); + // Check that stack item i + 1 points to stack items’ i *last* child. + for (size_t i = 0; i + 1 < kv_size(ast_stack); i++) { + const bool item_null = (want_value && i + 2 == kv_size(ast_stack)); + assert((&(*kv_A(ast_stack, i))->children == kv_A(ast_stack, i + 1) + && (item_null + ? (*kv_A(ast_stack, i))->children == NULL + : (*kv_A(ast_stack, i))->children->next == NULL)) + || ((&(*kv_A(ast_stack, i))->children->next + == kv_A(ast_stack, i + 1)) + && (item_null + ? (*kv_A(ast_stack, i))->children->next == NULL + : (*kv_A(ast_stack, i))->children->next->next == NULL))); + } +#endif // Note: in Vim whether expression "cond?d.a:2" is valid depends both on // "cond" and whether "d" is a dictionary: expression is valid if condition // is true and "d" is a dictionary (with "a" key or it will complain about -- cgit From bc386c48829c431fe5b17592c82eb4099621953a Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 15 Oct 2017 21:09:08 +0300 Subject: charset: Fix out-of-bounds array access It is incorrect to *first* access ptr[2] and *then* check whether maxlen allows it. --- src/nvim/charset.c | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/nvim/charset.c b/src/nvim/charset.c index 403ef65c4f..1147b78c9a 100644 --- a/src/nvim/charset.c +++ b/src/nvim/charset.c @@ -1621,6 +1621,7 @@ bool vim_isblankline(char_u *lbuf) void vim_str2nr(const char_u *const start, int *const prep, int *const len, const int what, varnumber_T *const nptr, uvarnumber_T *const unptr, const int maxlen) + FUNC_ATTR_NONNULL_ARG(1) { const char_u *ptr = start; int pre = 0; // default is decimal @@ -1633,20 +1634,21 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, } // Recognize hex, octal and bin. - if ((ptr[0] == '0') && (ptr[1] != '8') && (ptr[1] != '9') - && (maxlen == 0 || maxlen > 1)) { + if ((what & (STR2NR_HEX|STR2NR_OCT|STR2NR_BIN)) + && (maxlen == 0 || maxlen > 1) + && (ptr[0] == '0') && (ptr[1] != '8') && (ptr[1] != '9')) { pre = ptr[1]; if ((what & STR2NR_HEX) + && (maxlen == 0 || maxlen > 2) && ((pre == 'X') || (pre == 'x')) - && ascii_isxdigit(ptr[2]) - && (maxlen == 0 || maxlen > 2)) { + && ascii_isxdigit(ptr[2])) { // hexadecimal ptr += 2; } else if ((what & STR2NR_BIN) + && (maxlen == 0 || maxlen > 2) && ((pre == 'B') || (pre == 'b')) - && ascii_isbdigit(ptr[2]) - && (maxlen == 0 || maxlen > 2)) { + && ascii_isbdigit(ptr[2])) { // binary ptr += 2; } else { @@ -1675,7 +1677,7 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, // Do the string-to-numeric conversion "manually" to avoid sscanf quirks. int n = 1; - if ((pre == 'B') || (pre == 'b') || what == STR2NR_BIN + STR2NR_FORCE) { + if (pre == 'B' || pre == 'b' || what == (STR2NR_BIN|STR2NR_FORCE)) { // bin if (pre != 0) { n += 2; // skip over "0b" @@ -1692,7 +1694,7 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, break; } } - } else if ((pre == '0') || what == STR2NR_OCT + STR2NR_FORCE) { + } else if (pre == '0' || what == (STR2NR_OCT|STR2NR_FORCE)) { // octal while ('0' <= *ptr && *ptr <= '7') { // avoid ubsan error for overflow @@ -1706,8 +1708,7 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, break; } } - } else if ((pre == 'X') || (pre == 'x') - || what == STR2NR_HEX + STR2NR_FORCE) { + } else if (pre == 'X' || pre == 'x' || what == (STR2NR_HEX|STR2NR_FORCE)) { // hex if (pre != 0) { n += 2; // skip over "0x" -- cgit From 3aa2c0d63ae488e302a89fdcdd650404cb2670fd Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 15 Oct 2017 21:11:00 +0300 Subject: viml/parser/expressions,klee: Fix some problems found by KLEE run --- src/nvim/viml/parser/expressions.h | 2 +- test/symbolic/klee/nvim/charset.c | 20 ++++++++++---------- test/symbolic/klee/run.sh | 2 +- test/symbolic/klee/viml_expressions_parser.c | 2 -- test/unit/viml/expressions/parser_spec.lua | 8 ++++++++ 5 files changed, 20 insertions(+), 14 deletions(-) diff --git a/src/nvim/viml/parser/expressions.h b/src/nvim/viml/parser/expressions.h index 0198852bed..025f0f766e 100644 --- a/src/nvim/viml/parser/expressions.h +++ b/src/nvim/viml/parser/expressions.h @@ -69,7 +69,7 @@ typedef enum { } ExprOptScope; #define EXPR_OPT_SCOPE_LIST \ - ((char *)(char[]){ kExprOptScopeGlobal, kExprOptScopeLocal }) + ((char[]){ kExprOptScopeGlobal, kExprOptScopeLocal }) /// All possible variable scopes typedef enum { diff --git a/test/symbolic/klee/nvim/charset.c b/test/symbolic/klee/nvim/charset.c index 409d7d443c..59fe6a1430 100644 --- a/test/symbolic/klee/nvim/charset.c +++ b/test/symbolic/klee/nvim/charset.c @@ -38,20 +38,21 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, } // Recognize hex, octal and bin. - if ((ptr[0] == '0') && (ptr[1] != '8') && (ptr[1] != '9') - && (maxlen == 0 || maxlen > 1)) { + if ((what & (STR2NR_HEX|STR2NR_OCT|STR2NR_BIN)) + && (maxlen == 0 || maxlen > 1) + && (ptr[0] == '0') && (ptr[1] != '8') && (ptr[1] != '9')) { pre = ptr[1]; if ((what & STR2NR_HEX) + && (maxlen == 0 || maxlen > 2) && ((pre == 'X') || (pre == 'x')) - && ascii_isxdigit(ptr[2]) - && (maxlen == 0 || maxlen > 2)) { + && ascii_isxdigit(ptr[2])) { // hexadecimal ptr += 2; } else if ((what & STR2NR_BIN) + && (maxlen == 0 || maxlen > 2) && ((pre == 'B') || (pre == 'b')) - && ascii_isbdigit(ptr[2]) - && (maxlen == 0 || maxlen > 2)) { + && ascii_isbdigit(ptr[2])) { // binary ptr += 2; } else { @@ -80,7 +81,7 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, // Do the string-to-numeric conversion "manually" to avoid sscanf quirks. int n = 1; - if ((pre == 'B') || (pre == 'b') || what == STR2NR_BIN + STR2NR_FORCE) { + if (pre == 'B' || pre == 'b' || what == (STR2NR_BIN|STR2NR_FORCE)) { // bin if (pre != 0) { n += 2; // skip over "0b" @@ -97,7 +98,7 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, break; } } - } else if ((pre == '0') || what == STR2NR_OCT + STR2NR_FORCE) { + } else if (pre == '0' || what == (STR2NR_OCT|STR2NR_FORCE)) { // octal while ('0' <= *ptr && *ptr <= '7') { // avoid ubsan error for overflow @@ -111,8 +112,7 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, break; } } - } else if ((pre == 'X') || (pre == 'x') - || what == STR2NR_HEX + STR2NR_FORCE) { + } else if (pre == 'X' || pre == 'x' || what == (STR2NR_HEX|STR2NR_FORCE)) { // hex if (pre != 0) { n += 2; // skip over "0x" diff --git a/test/symbolic/klee/run.sh b/test/symbolic/klee/run.sh index 388903c234..c143cd0624 100755 --- a/test/symbolic/klee/run.sh +++ b/test/symbolic/klee/run.sh @@ -46,7 +46,7 @@ main() { line1="$line1 '--output-dir=$KLEE_OUT_DIR' '$KLEE_BIN_DIR/a.bc'" local line2="for t in '$KLEE_OUT_DIR'/*.err" line2="$line2 ; do ktest-tool --write-ints" - line2="$line2 \"\$(printf '%s' \"\$t\" | sed -e 's@.[^/]*\$@.out@')\"" + line2="$line2 \"\$(printf '%s' \"\$t\" | sed -e 's@\\.[^/]*\$@.ktest@')\"" line2="$line2 ; done" printf '%s\n%s\n' "$line1" "$line2" | \ docker run \ diff --git a/test/symbolic/klee/viml_expressions_parser.c b/test/symbolic/klee/viml_expressions_parser.c index c0cedceb21..ed280adb22 100644 --- a/test/symbolic/klee/viml_expressions_parser.c +++ b/test/symbolic/klee/viml_expressions_parser.c @@ -89,8 +89,6 @@ int main(const int argc, const char *const *const argv, kvi_init(pstate.reader.lines); const ExprAST ast = viml_pexpr_parse(&pstate, flags); - assert(ast.root != NULL - || plines[0].size == 0); assert(ast.root != NULL || ast.err.msg); viml_pexpr_free_ast(ast); assert(allocated_memory == 0); diff --git a/test/unit/viml/expressions/parser_spec.lua b/test/unit/viml/expressions/parser_spec.lua index 9624ced022..22263af8e2 100644 --- a/test/unit/viml/expressions/parser_spec.lua +++ b/test/unit/viml/expressions/parser_spec.lua @@ -6867,5 +6867,13 @@ describe('Expressions parser', function() }, }, { }) + check_parsing('0', 0, { + -- 0 + ast = { + 'Integer(val=0):0:0:0', + }, + }, { + hl('Number', '0'), + }) end) end) -- cgit From 4ccaf861107f8c737ba5b749a69c75d7f097d1f1 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 15 Oct 2017 21:22:49 +0300 Subject: keymap: Readd figure braces disappeared when resolving conflicts --- src/nvim/keymap.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/nvim/keymap.c b/src/nvim/keymap.c index a44b663cc6..55ac835663 100644 --- a/src/nvim/keymap.c +++ b/src/nvim/keymap.c @@ -719,8 +719,8 @@ int get_special_key_code(const char_u *name) for (int i = 0; key_names_table[i].name != NULL; i++) { const char *const table_name = key_names_table[i].name; int j; - for (j = 0; vim_isIDc(name[j]) && table_name[j] != NUL; j++) - if (TOLOWER_ASC(table_name[j]) != TOLOWER_ASC(name[j])) + for (j = 0; vim_isIDc(name[j]) && table_name[j] != NUL; j++) { + if (TOLOWER_ASC(table_name[j]) != TOLOWER_ASC(name[j])) { break; } } -- cgit From 2cb95bd9378d3c45f2eea527683546825e16f7d3 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 15 Oct 2017 21:39:01 +0300 Subject: viml/parser/expressions: Define east_node_type_tab only when needed --- src/nvim/viml/parser/expressions.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index 370034943e..4801d66988 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -725,6 +725,7 @@ viml_pexpr_repr_token_end: return ret; } +#ifdef UNIT_TESTING static const char *const east_node_type_tab[] = { [kExprNodeMissing] = "Missing", [kExprNodeOpMissing] = "OpMissing", @@ -765,6 +766,7 @@ static const char *const east_node_type_tab[] = { [kExprNodeOption] = "Option", [kExprNodeEnvironment] = "Environment", }; +#endif /// Represent `int` character as a string /// -- cgit From 1a3635304b80b48625bcd9d48f4f38778b42e4af Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 16 Oct 2017 00:07:32 +0300 Subject: charset: Avoid overflow in vim_str2nr --- src/nvim/charset.c | 60 +++++++++++------------------- test/symbolic/klee/nvim/charset.c | 60 +++++++++++------------------- test/unit/viml/expressions/lexer_spec.lua | 3 ++ test/unit/viml/expressions/parser_spec.lua | 10 ++++- 4 files changed, 53 insertions(+), 80 deletions(-) diff --git a/src/nvim/charset.c b/src/nvim/charset.c index f824717fcd..7b307b8160 100644 --- a/src/nvim/charset.c +++ b/src/nvim/charset.c @@ -1620,13 +1620,16 @@ bool vim_isblankline(char_u *lbuf) /// @param maxlen Max length of string to check. void vim_str2nr(const char_u *const start, int *const prep, int *const len, const int what, varnumber_T *const nptr, - uvarnumber_T *const unptr, const int maxlen) + uvarnumber_T *const unptr, int maxlen) FUNC_ATTR_NONNULL_ARG(1) { - const char_u *ptr = start; + const char *ptr = (const char *)start; + if (maxlen == 0) { + maxlen = (int)strlen(ptr); + } + const char *const e = ptr + maxlen; int pre = 0; // default is decimal bool negative = false; - uvarnumber_T un = 0; if (ptr[0] == '-') { negative = true; @@ -1635,19 +1638,19 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, // Recognize hex, octal and bin. if ((what & (STR2NR_HEX|STR2NR_OCT|STR2NR_BIN)) - && (maxlen == 0 || maxlen > 1) - && (ptr[0] == '0') && (ptr[1] != '8') && (ptr[1] != '9')) { + && maxlen > 1 + && ptr[0] == '0' && ptr[1] != '8' && ptr[1] != '9') { pre = ptr[1]; if ((what & STR2NR_HEX) - && (maxlen == 0 || maxlen > 2) - && ((pre == 'X') || (pre == 'x')) + && maxlen > 2 + && (pre == 'X' || pre == 'x') && ascii_isxdigit(ptr[2])) { // hexadecimal ptr += 2; } else if ((what & STR2NR_BIN) - && (maxlen == 0 || maxlen > 2) - && ((pre == 'B') || (pre == 'b')) + && maxlen > 2 + && (pre == 'B' || pre == 'b') && ascii_isbdigit(ptr[2])) { // binary ptr += 2; @@ -1657,32 +1660,26 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, if (what & STR2NR_OCT) { // Don't interpret "0", "08" or "0129" as octal. - for (int n = 1; ascii_isdigit(ptr[n]); ++n) { - if (ptr[n] > '7') { + for (int i = 1; i < maxlen && ascii_isdigit(ptr[i]); i++) { + if (ptr[i] > '7') { // can't be octal pre = 0; break; } - if (ptr[n] >= '0') { + if (ptr[i] >= '0') { // assume octal pre = '0'; } - if (n == maxlen) { - break; - } } } } } // Do the string-to-numeric conversion "manually" to avoid sscanf quirks. - int n = 1; + uvarnumber_T un = 0; if (pre == 'B' || pre == 'b' || what == (STR2NR_BIN|STR2NR_FORCE)) { // bin - if (pre != 0) { - n += 2; // skip over "0b" - } - while ('0' <= *ptr && *ptr <= '1') { + while (ptr < e && '0' <= *ptr && *ptr <= '1') { // avoid ubsan error for overflow if (un < UVARNUMBER_MAX / 2) { un = 2 * un + (uvarnumber_T)(*ptr - '0'); @@ -1690,13 +1687,10 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, un = UVARNUMBER_MAX; } ptr++; - if (n++ == maxlen) { - break; - } } } else if (pre == '0' || what == (STR2NR_OCT|STR2NR_FORCE)) { // octal - while ('0' <= *ptr && *ptr <= '7') { + while (ptr < e && '0' <= *ptr && *ptr <= '7') { // avoid ubsan error for overflow if (un < UVARNUMBER_MAX / 8) { un = 8 * un + (uvarnumber_T)(*ptr - '0'); @@ -1704,16 +1698,10 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, un = UVARNUMBER_MAX; } ptr++; - if (n++ == maxlen) { - break; - } } } else if (pre == 'X' || pre == 'x' || what == (STR2NR_HEX|STR2NR_FORCE)) { // hex - if (pre != 0) { - n += 2; // skip over "0x" - } - while (ascii_isxdigit(*ptr)) { + while (ptr < e && ascii_isxdigit(*ptr)) { // avoid ubsan error for overflow if (un < UVARNUMBER_MAX / 16) { un = 16 * un + (uvarnumber_T)hex2nr(*ptr); @@ -1721,13 +1709,10 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, un = UVARNUMBER_MAX; } ptr++; - if (n++ == maxlen) { - break; - } } } else { // decimal - while (ascii_isdigit(*ptr)) { + while (ptr < e && ascii_isdigit(*ptr)) { // avoid ubsan error for overflow if (un < UVARNUMBER_MAX / 10) { un = 10 * un + (uvarnumber_T)(*ptr - '0'); @@ -1735,9 +1720,6 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, un = UVARNUMBER_MAX; } ptr++; - if (n++ == maxlen) { - break; - } } } @@ -1746,7 +1728,7 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, } if (len != NULL) { - *len = (int)(ptr - start); + *len = (int)(ptr - (const char *)start); } if (nptr != NULL) { diff --git a/test/symbolic/klee/nvim/charset.c b/test/symbolic/klee/nvim/charset.c index 59fe6a1430..7dcf6868bf 100644 --- a/test/symbolic/klee/nvim/charset.c +++ b/test/symbolic/klee/nvim/charset.c @@ -25,12 +25,15 @@ int hex2nr(int c) void vim_str2nr(const char_u *const start, int *const prep, int *const len, const int what, varnumber_T *const nptr, - uvarnumber_T *const unptr, const int maxlen) + uvarnumber_T *const unptr, int maxlen) { - const char_u *ptr = start; + const char *ptr = (const char *)start; + if (maxlen == 0) { + maxlen = (int)strlen(ptr); + } + const char *const e = ptr + maxlen; int pre = 0; // default is decimal bool negative = false; - uvarnumber_T un = 0; if (ptr[0] == '-') { negative = true; @@ -39,19 +42,19 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, // Recognize hex, octal and bin. if ((what & (STR2NR_HEX|STR2NR_OCT|STR2NR_BIN)) - && (maxlen == 0 || maxlen > 1) - && (ptr[0] == '0') && (ptr[1] != '8') && (ptr[1] != '9')) { + && maxlen > 1 + && ptr[0] == '0' && ptr[1] != '8' && ptr[1] != '9') { pre = ptr[1]; if ((what & STR2NR_HEX) - && (maxlen == 0 || maxlen > 2) - && ((pre == 'X') || (pre == 'x')) + && maxlen > 2 + && (pre == 'X' || pre == 'x') && ascii_isxdigit(ptr[2])) { // hexadecimal ptr += 2; } else if ((what & STR2NR_BIN) - && (maxlen == 0 || maxlen > 2) - && ((pre == 'B') || (pre == 'b')) + && maxlen > 2 + && (pre == 'B' || pre == 'b') && ascii_isbdigit(ptr[2])) { // binary ptr += 2; @@ -61,32 +64,26 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, if (what & STR2NR_OCT) { // Don't interpret "0", "08" or "0129" as octal. - for (int n = 1; ascii_isdigit(ptr[n]); ++n) { - if (ptr[n] > '7') { + for (int i = 1; i < maxlen && ascii_isdigit(ptr[i]); i++) { + if (ptr[i] > '7') { // can't be octal pre = 0; break; } - if (ptr[n] >= '0') { + if (ptr[i] >= '0') { // assume octal pre = '0'; } - if (n == maxlen) { - break; - } } } } } // Do the string-to-numeric conversion "manually" to avoid sscanf quirks. - int n = 1; + uvarnumber_T un = 0; if (pre == 'B' || pre == 'b' || what == (STR2NR_BIN|STR2NR_FORCE)) { // bin - if (pre != 0) { - n += 2; // skip over "0b" - } - while ('0' <= *ptr && *ptr <= '1') { + while (ptr < e && '0' <= *ptr && *ptr <= '1') { // avoid ubsan error for overflow if (un < UVARNUMBER_MAX / 2) { un = 2 * un + (uvarnumber_T)(*ptr - '0'); @@ -94,13 +91,10 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, un = UVARNUMBER_MAX; } ptr++; - if (n++ == maxlen) { - break; - } } } else if (pre == '0' || what == (STR2NR_OCT|STR2NR_FORCE)) { // octal - while ('0' <= *ptr && *ptr <= '7') { + while (ptr < e && '0' <= *ptr && *ptr <= '7') { // avoid ubsan error for overflow if (un < UVARNUMBER_MAX / 8) { un = 8 * un + (uvarnumber_T)(*ptr - '0'); @@ -108,16 +102,10 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, un = UVARNUMBER_MAX; } ptr++; - if (n++ == maxlen) { - break; - } } } else if (pre == 'X' || pre == 'x' || what == (STR2NR_HEX|STR2NR_FORCE)) { // hex - if (pre != 0) { - n += 2; // skip over "0x" - } - while (ascii_isxdigit(*ptr)) { + while (ptr < e && ascii_isxdigit(*ptr)) { // avoid ubsan error for overflow if (un < UVARNUMBER_MAX / 16) { un = 16 * un + (uvarnumber_T)hex2nr(*ptr); @@ -125,13 +113,10 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, un = UVARNUMBER_MAX; } ptr++; - if (n++ == maxlen) { - break; - } } } else { // decimal - while (ascii_isdigit(*ptr)) { + while (ptr < e && ascii_isdigit(*ptr)) { // avoid ubsan error for overflow if (un < UVARNUMBER_MAX / 10) { un = 10 * un + (uvarnumber_T)(*ptr - '0'); @@ -139,9 +124,6 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, un = UVARNUMBER_MAX; } ptr++; - if (n++ == maxlen) { - break; - } } } @@ -150,7 +132,7 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, } if (len != NULL) { - *len = (int)(ptr - start); + *len = (int)(ptr - (const char *)start); } if (nptr != NULL) { diff --git a/test/unit/viml/expressions/lexer_spec.lua b/test/unit/viml/expressions/lexer_spec.lua index 674b1b37db..5910468017 100644 --- a/test/unit/viml/expressions/lexer_spec.lua +++ b/test/unit/viml/expressions/lexer_spec.lua @@ -292,6 +292,9 @@ describe('Expressions lexer', function() simple_test({'0b102'}, 'Number', 4, {data={is_float=false, base=2, val=2}, str='0b10'}) simple_test({'10F'}, 'Number', 2, {data={is_float=false, base=10, val=10}, str='10'}) simple_test({'0x0123456789ABCDEFG'}, 'Number', 18, {data={is_float=false, base=16, val=81985529216486895}, str='0x0123456789ABCDEF'}) + simple_test({{data='00', size=2}}, 'Number', 2, {data={is_float=false, base=8, val=0}, str='00'}) + simple_test({{data='009', size=2}}, 'Number', 2, {data={is_float=false, base=8, val=0}, str='00'}) + simple_test({{data='01', size=1}}, 'Number', 1, {data={is_float=false, base=10, val=0}, str='0'}) end local function regular_scope_tests() diff --git a/test/unit/viml/expressions/parser_spec.lua b/test/unit/viml/expressions/parser_spec.lua index 22263af8e2..36af875bd2 100644 --- a/test/unit/viml/expressions/parser_spec.lua +++ b/test/unit/viml/expressions/parser_spec.lua @@ -6867,13 +6867,19 @@ describe('Expressions parser', function() }, }, { }) - check_parsing('0', 0, { - -- 0 + check_parsing({data='01', size=1}, 0, { ast = { 'Integer(val=0):0:0:0', }, }, { hl('Number', '0'), }) + check_parsing({data='001', size=2}, 0, { + ast = { + 'Integer(val=0):0:0:00', + }, + }, { + hl('Number', '00'), + }) end) end) -- cgit From 5e92ee6565233c56e7a33b12ef6a61f05eae91aa Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 16 Oct 2017 00:19:02 +0300 Subject: charset: Do not call strlen() from vim_str2nr --- src/nvim/charset.c | 25 ++++++++++++------------- test/symbolic/klee/nvim/charset.c | 25 ++++++++++++------------- 2 files changed, 24 insertions(+), 26 deletions(-) diff --git a/src/nvim/charset.c b/src/nvim/charset.c index 7b307b8160..a7ffa4bc00 100644 --- a/src/nvim/charset.c +++ b/src/nvim/charset.c @@ -1620,14 +1620,12 @@ bool vim_isblankline(char_u *lbuf) /// @param maxlen Max length of string to check. void vim_str2nr(const char_u *const start, int *const prep, int *const len, const int what, varnumber_T *const nptr, - uvarnumber_T *const unptr, int maxlen) + uvarnumber_T *const unptr, const int maxlen) FUNC_ATTR_NONNULL_ARG(1) { const char *ptr = (const char *)start; - if (maxlen == 0) { - maxlen = (int)strlen(ptr); - } - const char *const e = ptr + maxlen; +#define STRING_ENDED(ptr) \ + (!(maxlen == 0 || (int)((ptr) - (const char *)start) < maxlen)) int pre = 0; // default is decimal bool negative = false; @@ -1638,18 +1636,18 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, // Recognize hex, octal and bin. if ((what & (STR2NR_HEX|STR2NR_OCT|STR2NR_BIN)) - && maxlen > 1 + && !STRING_ENDED(ptr + 1) && ptr[0] == '0' && ptr[1] != '8' && ptr[1] != '9') { pre = ptr[1]; if ((what & STR2NR_HEX) - && maxlen > 2 + && !STRING_ENDED(ptr + 2) && (pre == 'X' || pre == 'x') && ascii_isxdigit(ptr[2])) { // hexadecimal ptr += 2; } else if ((what & STR2NR_BIN) - && maxlen > 2 + && !STRING_ENDED(ptr + 2) && (pre == 'B' || pre == 'b') && ascii_isbdigit(ptr[2])) { // binary @@ -1660,7 +1658,7 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, if (what & STR2NR_OCT) { // Don't interpret "0", "08" or "0129" as octal. - for (int i = 1; i < maxlen && ascii_isdigit(ptr[i]); i++) { + for (int i = 1; !STRING_ENDED(ptr + i) && ascii_isdigit(ptr[i]); i++) { if (ptr[i] > '7') { // can't be octal pre = 0; @@ -1679,7 +1677,7 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, uvarnumber_T un = 0; if (pre == 'B' || pre == 'b' || what == (STR2NR_BIN|STR2NR_FORCE)) { // bin - while (ptr < e && '0' <= *ptr && *ptr <= '1') { + while (!STRING_ENDED(ptr) && '0' <= *ptr && *ptr <= '1') { // avoid ubsan error for overflow if (un < UVARNUMBER_MAX / 2) { un = 2 * un + (uvarnumber_T)(*ptr - '0'); @@ -1690,7 +1688,7 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, } } else if (pre == '0' || what == (STR2NR_OCT|STR2NR_FORCE)) { // octal - while (ptr < e && '0' <= *ptr && *ptr <= '7') { + while (!STRING_ENDED(ptr) && '0' <= *ptr && *ptr <= '7') { // avoid ubsan error for overflow if (un < UVARNUMBER_MAX / 8) { un = 8 * un + (uvarnumber_T)(*ptr - '0'); @@ -1701,7 +1699,7 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, } } else if (pre == 'X' || pre == 'x' || what == (STR2NR_HEX|STR2NR_FORCE)) { // hex - while (ptr < e && ascii_isxdigit(*ptr)) { + while (!STRING_ENDED(ptr) && ascii_isxdigit(*ptr)) { // avoid ubsan error for overflow if (un < UVARNUMBER_MAX / 16) { un = 16 * un + (uvarnumber_T)hex2nr(*ptr); @@ -1712,7 +1710,7 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, } } else { // decimal - while (ptr < e && ascii_isdigit(*ptr)) { + while (!STRING_ENDED(ptr) && ascii_isdigit(*ptr)) { // avoid ubsan error for overflow if (un < UVARNUMBER_MAX / 10) { un = 10 * un + (uvarnumber_T)(*ptr - '0'); @@ -1750,6 +1748,7 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, if (unptr != NULL) { *unptr = un; } +#undef STRING_ENDED } /// Return the value of a single hex character. diff --git a/test/symbolic/klee/nvim/charset.c b/test/symbolic/klee/nvim/charset.c index 7dcf6868bf..c584bd72ef 100644 --- a/test/symbolic/klee/nvim/charset.c +++ b/test/symbolic/klee/nvim/charset.c @@ -25,13 +25,11 @@ int hex2nr(int c) void vim_str2nr(const char_u *const start, int *const prep, int *const len, const int what, varnumber_T *const nptr, - uvarnumber_T *const unptr, int maxlen) + uvarnumber_T *const unptr, const int maxlen) { const char *ptr = (const char *)start; - if (maxlen == 0) { - maxlen = (int)strlen(ptr); - } - const char *const e = ptr + maxlen; +#define STRING_ENDED(ptr) \ + (!(maxlen == 0 || (int)((ptr) - (const char *)start) < maxlen)) int pre = 0; // default is decimal bool negative = false; @@ -42,18 +40,18 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, // Recognize hex, octal and bin. if ((what & (STR2NR_HEX|STR2NR_OCT|STR2NR_BIN)) - && maxlen > 1 + && !STRING_ENDED(ptr + 1) && ptr[0] == '0' && ptr[1] != '8' && ptr[1] != '9') { pre = ptr[1]; if ((what & STR2NR_HEX) - && maxlen > 2 + && !STRING_ENDED(ptr + 2) && (pre == 'X' || pre == 'x') && ascii_isxdigit(ptr[2])) { // hexadecimal ptr += 2; } else if ((what & STR2NR_BIN) - && maxlen > 2 + && !STRING_ENDED(ptr + 2) && (pre == 'B' || pre == 'b') && ascii_isbdigit(ptr[2])) { // binary @@ -64,7 +62,7 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, if (what & STR2NR_OCT) { // Don't interpret "0", "08" or "0129" as octal. - for (int i = 1; i < maxlen && ascii_isdigit(ptr[i]); i++) { + for (int i = 1; !STRING_ENDED(ptr + i) && ascii_isdigit(ptr[i]); i++) { if (ptr[i] > '7') { // can't be octal pre = 0; @@ -83,7 +81,7 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, uvarnumber_T un = 0; if (pre == 'B' || pre == 'b' || what == (STR2NR_BIN|STR2NR_FORCE)) { // bin - while (ptr < e && '0' <= *ptr && *ptr <= '1') { + while (!STRING_ENDED(ptr) && '0' <= *ptr && *ptr <= '1') { // avoid ubsan error for overflow if (un < UVARNUMBER_MAX / 2) { un = 2 * un + (uvarnumber_T)(*ptr - '0'); @@ -94,7 +92,7 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, } } else if (pre == '0' || what == (STR2NR_OCT|STR2NR_FORCE)) { // octal - while (ptr < e && '0' <= *ptr && *ptr <= '7') { + while (!STRING_ENDED(ptr) && '0' <= *ptr && *ptr <= '7') { // avoid ubsan error for overflow if (un < UVARNUMBER_MAX / 8) { un = 8 * un + (uvarnumber_T)(*ptr - '0'); @@ -105,7 +103,7 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, } } else if (pre == 'X' || pre == 'x' || what == (STR2NR_HEX|STR2NR_FORCE)) { // hex - while (ptr < e && ascii_isxdigit(*ptr)) { + while (!STRING_ENDED(ptr) && ascii_isxdigit(*ptr)) { // avoid ubsan error for overflow if (un < UVARNUMBER_MAX / 16) { un = 16 * un + (uvarnumber_T)hex2nr(*ptr); @@ -116,7 +114,7 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, } } else { // decimal - while (ptr < e && ascii_isdigit(*ptr)) { + while (!STRING_ENDED(ptr) && ascii_isdigit(*ptr)) { // avoid ubsan error for overflow if (un < UVARNUMBER_MAX / 10) { un = 10 * un + (uvarnumber_T)(*ptr - '0'); @@ -154,4 +152,5 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, if (unptr != NULL) { *unptr = un; } +#undef STRING_ENDED } -- cgit From fe81380bf5d4d161187998088aa9cff948b7c891 Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 16 Oct 2017 00:30:55 +0300 Subject: viml/parser/expressions: Highlight prefix separately from number Should make accidental octals more visible. --- src/nvim/viml/parser/expressions.c | 17 ++++- test/unit/viml/expressions/parser_spec.lua | 113 ++++++++++++++++++++++++++++- 2 files changed, 128 insertions(+), 2 deletions(-) diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index 4801d66988..35f4385f33 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -1042,6 +1042,7 @@ void viml_pexpr_free_ast(ExprAST ast) // // NVimRegister -> SpecialChar // NVimNumber -> Number +// NVimNumberPrefix -> SpecialChar // NVimFloat -> NVimNumber // // NVimNestingParenthesis -> NVimParenthesis @@ -1842,6 +1843,14 @@ static const int want_node_to_lexer_flags[] = { [kENodeArgumentSeparator] = kELFlagForbidScope, }; +/// Number of characters to highlight as NumberPrefix depending on the base +static const uint8_t base_to_prefix_length[] = { + [2] = 2, + [8] = 1, + [10] = 0, + [16] = 2, +}; + /// Parse one VimL expression /// /// @param pstate Parser state. @@ -2632,7 +2641,13 @@ viml_pexpr_parse_figure_brace_closing_error: } else { NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeInteger); cur_node->data.num.value = cur_token.data.num.val.integer; - HL_CUR_TOKEN(Number); + const uint8_t prefix_length = base_to_prefix_length[ + cur_token.data.num.base]; + viml_parser_highlight(pstate, cur_token.start, prefix_length, + HL(NumberPrefix)); + viml_parser_highlight( + pstate, shifted_pos(cur_token.start, prefix_length), + cur_token.len - prefix_length, HL(Number)); } want_node = kENodeOperator; *top_node_p = cur_node; diff --git a/test/unit/viml/expressions/parser_spec.lua b/test/unit/viml/expressions/parser_spec.lua index 36af875bd2..54fd54aea8 100644 --- a/test/unit/viml/expressions/parser_spec.lua +++ b/test/unit/viml/expressions/parser_spec.lua @@ -6858,6 +6858,116 @@ describe('Expressions parser', function() hl('Number', '2'), }) end) + itp('highlights numbers with prefix', function() + check_parsing('0xABCDEF', 0, { + -- 01234567 + ast = { + 'Integer(val=11259375):0:0:0xABCDEF', + }, + }, { + hl('NumberPrefix', '0x'), + hl('Number', 'ABCDEF'), + }) + + check_parsing('0Xabcdef', 0, { + -- 01234567 + ast = { + 'Integer(val=11259375):0:0:0Xabcdef', + }, + }, { + hl('NumberPrefix', '0X'), + hl('Number', 'abcdef'), + }) + + check_parsing('0XABCDEF', 0, { + -- 01234567 + ast = { + 'Integer(val=11259375):0:0:0XABCDEF', + }, + }, { + hl('NumberPrefix', '0X'), + hl('Number', 'ABCDEF'), + }) + + check_parsing('0xabcdef', 0, { + -- 01234567 + ast = { + 'Integer(val=11259375):0:0:0xabcdef', + }, + }, { + hl('NumberPrefix', '0x'), + hl('Number', 'abcdef'), + }) + + check_parsing('0b001', 0, { + -- 01234 + ast = { + 'Integer(val=1):0:0:0b001', + }, + }, { + hl('NumberPrefix', '0b'), + hl('Number', '001'), + }) + + check_parsing('0B001', 0, { + -- 01234 + ast = { + 'Integer(val=1):0:0:0B001', + }, + }, { + hl('NumberPrefix', '0B'), + hl('Number', '001'), + }) + + check_parsing('0B00', 0, { + -- 0123 + ast = { + 'Integer(val=0):0:0:0B00', + }, + }, { + hl('NumberPrefix', '0B'), + hl('Number', '00'), + }) + + check_parsing('00', 0, { + -- 01 + ast = { + 'Integer(val=0):0:0:00', + }, + }, { + hl('NumberPrefix', '0'), + hl('Number', '0'), + }) + + check_parsing('001', 0, { + -- 012 + ast = { + 'Integer(val=1):0:0:001', + }, + }, { + hl('NumberPrefix', '0'), + hl('Number', '01'), + }) + + check_parsing('01', 0, { + -- 01 + ast = { + 'Integer(val=1):0:0:01', + }, + }, { + hl('NumberPrefix', '0'), + hl('Number', '1'), + }) + + check_parsing('1', 0, { + -- 0 + ast = { + 'Integer(val=1):0:0:1', + }, + }, { + hl('Number', '1'), + }) + end) itp('works (KLEE tests)', function() check_parsing('\0002&A:\000', 0, { ast = nil, @@ -6879,7 +6989,8 @@ describe('Expressions parser', function() 'Integer(val=0):0:0:00', }, }, { - hl('Number', '00'), + hl('NumberPrefix', '0'), + hl('Number', '0'), }) end) end) -- cgit From ed253b5fe6515840fe6dd9df83855a0316de8bad Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 16 Oct 2017 00:39:48 +0300 Subject: klee: Include colors in test --- src/nvim/viml/parser/parser.h | 5 +++-- test/symbolic/klee/viml_expressions_parser.c | 10 +++++++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/nvim/viml/parser/parser.h b/src/nvim/viml/parser/parser.h index 10ced57977..fbc5ba5f07 100644 --- a/src/nvim/viml/parser/parser.h +++ b/src/nvim/viml/parser/parser.h @@ -176,8 +176,9 @@ static inline void viml_parser_highlight(ParserState *const pstate, if (pstate->colors == NULL || len == 0) { return; } - // TODO(ZyX-I): May do some assert() sanitizing here. - // TODO(ZyX-I): May join chunks. + assert(kv_size(*pstate->colors) == 0 + || kv_Z(*pstate->colors, 0).start.line < start.line + || kv_Z(*pstate->colors, 0).end_col <= start.col); kvi_push(*pstate->colors, ((ParserHighlightChunk) { .start = start, .end_col = start.col + len, diff --git a/test/symbolic/klee/viml_expressions_parser.c b/test/symbolic/klee/viml_expressions_parser.c index ed280adb22..8f015ae9a7 100644 --- a/test/symbolic/klee/viml_expressions_parser.c +++ b/test/symbolic/klee/viml_expressions_parser.c @@ -75,6 +75,9 @@ int main(const int argc, const char *const *const argv, #endif ParserLine *cur_pline = &plines[0]; + ParserHighlight colors; + kvi_init(colors); + ParserState pstate = { .reader = { .get_line = simple_get_line, @@ -83,13 +86,18 @@ int main(const int argc, const char *const *const argv, .conv.vc_type = CONV_NONE, }, .pos = { 0, 0 }, - .colors = NULL, + .colors = &colors, .can_continuate = false, }; kvi_init(pstate.reader.lines); const ExprAST ast = viml_pexpr_parse(&pstate, flags); assert(ast.root != NULL || ast.err.msg); + // Can’t possibly have more highlight tokens then there are bytes in string. + assert(kv_size(colors) <= INPUT_SIZE - shift); + kvi_destroy(colors); + // Not destroying pstate.reader.lines because there is no way it could exceed + // its limits in the current circumstances. viml_pexpr_free_ast(ast); assert(allocated_memory == 0); } -- cgit From 15043e93b681ffdf1142d24aa918997e2e63a4b1 Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 16 Oct 2017 00:41:41 +0300 Subject: klee: Update key_name_entry table --- src/nvim/keymap.c | 3 +- test/symbolic/klee/nvim/keymap.c | 292 +++++++++++++++++++-------------------- 2 files changed, 146 insertions(+), 149 deletions(-) diff --git a/src/nvim/keymap.c b/src/nvim/keymap.c index 55ac835663..4f120e66f7 100644 --- a/src/nvim/keymap.c +++ b/src/nvim/keymap.c @@ -141,8 +141,7 @@ static char_u modifier_keys_table[] = static const struct key_name_entry { int key; // Special key code or ascii value const char *name; // Name of key -} key_names_table[] = -{ +} key_names_table[] = { { ' ', "Space" }, { TAB, "Tab" }, { K_TAB, "Tab" }, diff --git a/test/symbolic/klee/nvim/keymap.c b/test/symbolic/klee/nvim/keymap.c index c59d8d4d6e..ff5e46e75b 100644 --- a/test/symbolic/klee/nvim/keymap.c +++ b/test/symbolic/klee/nvim/keymap.c @@ -141,154 +141,152 @@ int handle_x_keys(const int key) } static const struct key_name_entry { - int key; ///< Special key code or ASCII value. - const char *name; ///< Name of the key + int key; // Special key code or ascii value + const char *name; // Name of key } key_names_table[] = { - {' ', "Space"}, - {TAB, "Tab"}, - {K_TAB, "Tab"}, - {NL, "NL"}, - {NL, "NewLine"}, // Alternative name - {NL, "LineFeed"}, // Alternative name - {NL, "LF"}, // Alternative name - {CAR, "CR"}, - {CAR, "Return"}, // Alternative name - {CAR, "Enter"}, // Alternative name - {K_BS, "BS"}, - {K_BS, "BackSpace"}, // Alternative name - {ESC, "Esc"}, - {CSI, "CSI"}, - {K_CSI, "xCSI"}, - {'|', "Bar"}, - {'\\', "Bslash"}, - {K_DEL, "Del"}, - {K_DEL, "Delete"}, // Alternative name - {K_KDEL, "kDel"}, - {K_UP, "Up"}, - {K_DOWN, "Down"}, - {K_LEFT, "Left"}, - {K_RIGHT, "Right"}, - {K_XUP, "xUp"}, - {K_XDOWN, "xDown"}, - {K_XLEFT, "xLeft"}, - {K_XRIGHT, "xRight"}, - - {K_F1, "F1"}, - {K_F2, "F2"}, - {K_F3, "F3"}, - {K_F4, "F4"}, - {K_F5, "F5"}, - {K_F6, "F6"}, - {K_F7, "F7"}, - {K_F8, "F8"}, - {K_F9, "F9"}, - {K_F10, "F10"}, - - {K_F11, "F11"}, - {K_F12, "F12"}, - {K_F13, "F13"}, - {K_F14, "F14"}, - {K_F15, "F15"}, - {K_F16, "F16"}, - {K_F17, "F17"}, - {K_F18, "F18"}, - {K_F19, "F19"}, - {K_F20, "F20"}, - - {K_F21, "F21"}, - {K_F22, "F22"}, - {K_F23, "F23"}, - {K_F24, "F24"}, - {K_F25, "F25"}, - {K_F26, "F26"}, - {K_F27, "F27"}, - {K_F28, "F28"}, - {K_F29, "F29"}, - {K_F30, "F30"}, - - {K_F31, "F31"}, - {K_F32, "F32"}, - {K_F33, "F33"}, - {K_F34, "F34"}, - {K_F35, "F35"}, - {K_F36, "F36"}, - {K_F37, "F37"}, - - {K_XF1, "xF1"}, - {K_XF2, "xF2"}, - {K_XF3, "xF3"}, - {K_XF4, "xF4"}, - - {K_HELP, "Help"}, - {K_UNDO, "Undo"}, - {K_INS, "Insert"}, - {K_INS, "Ins"}, // Alternative name - {K_KINS, "kInsert"}, - {K_HOME, "Home"}, - {K_KHOME, "kHome"}, - {K_XHOME, "xHome"}, - {K_ZHOME, "zHome"}, - {K_END, "End"}, - {K_KEND, "kEnd"}, - {K_XEND, "xEnd"}, - {K_ZEND, "zEnd"}, - {K_PAGEUP, "PageUp"}, - {K_PAGEDOWN, "PageDown"}, - {K_KPAGEUP, "kPageUp"}, - {K_KPAGEDOWN, "kPageDown"}, - - {K_KPLUS, "kPlus"}, - {K_KMINUS, "kMinus"}, - {K_KDIVIDE, "kDivide"}, - {K_KMULTIPLY, "kMultiply"}, - {K_KENTER, "kEnter"}, - {K_KPOINT, "kPoint"}, - - {K_K0, "k0"}, - {K_K1, "k1"}, - {K_K2, "k2"}, - {K_K3, "k3"}, - {K_K4, "k4"}, - {K_K5, "k5"}, - {K_K6, "k6"}, - {K_K7, "k7"}, - {K_K8, "k8"}, - {K_K9, "k9"}, - - {'<', "lt"}, - - {K_MOUSE, "Mouse"}, - {K_LEFTMOUSE, "LeftMouse"}, - {K_LEFTMOUSE_NM, "LeftMouseNM"}, - {K_LEFTDRAG, "LeftDrag"}, - {K_LEFTRELEASE, "LeftRelease"}, - {K_LEFTRELEASE_NM, "LeftReleaseNM"}, - {K_MIDDLEMOUSE, "MiddleMouse"}, - {K_MIDDLEDRAG, "MiddleDrag"}, - {K_MIDDLERELEASE, "MiddleRelease"}, - {K_RIGHTMOUSE, "RightMouse"}, - {K_RIGHTDRAG, "RightDrag"}, - {K_RIGHTRELEASE, "RightRelease"}, - {K_MOUSEDOWN, "ScrollWheelUp"}, - {K_MOUSEUP, "ScrollWheelDown"}, - {K_MOUSELEFT, "ScrollWheelRight"}, - {K_MOUSERIGHT, "ScrollWheelLeft"}, - {K_MOUSEDOWN, "MouseDown"}, // OBSOLETE: Use ScrollWheelXXX instead - {K_MOUSEUP, "MouseUp"}, // Same - {K_X1MOUSE, "X1Mouse"}, - {K_X1DRAG, "X1Drag"}, - {K_X1RELEASE, "X1Release"}, - {K_X2MOUSE, "X2Mouse"}, - {K_X2DRAG, "X2Drag"}, - {K_X2RELEASE, "X2Release"}, - {K_DROP, "Drop"}, - {K_ZERO, "Nul"}, - {K_SNR, "SNR"}, - {K_PLUG, "Plug"}, - {K_PASTE, "Paste"}, - {K_FOCUSGAINED, "FocusGained"}, - {K_FOCUSLOST, "FocusLost"}, - {0, NULL} + { ' ', "Space" }, + { TAB, "Tab" }, + { K_TAB, "Tab" }, + { NL, "NL" }, + { NL, "NewLine" }, // Alternative name + { NL, "LineFeed" }, // Alternative name + { NL, "LF" }, // Alternative name + { CAR, "CR" }, + { CAR, "Return" }, // Alternative name + { CAR, "Enter" }, // Alternative name + { K_BS, "BS" }, + { K_BS, "BackSpace" }, // Alternative name + { ESC, "Esc" }, + { CSI, "CSI" }, + { K_CSI, "xCSI" }, + { '|', "Bar" }, + { '\\', "Bslash" }, + { K_DEL, "Del" }, + { K_DEL, "Delete" }, // Alternative name + { K_KDEL, "kDel" }, + { K_UP, "Up" }, + { K_DOWN, "Down" }, + { K_LEFT, "Left" }, + { K_RIGHT, "Right" }, + { K_XUP, "xUp" }, + { K_XDOWN, "xDown" }, + { K_XLEFT, "xLeft" }, + { K_XRIGHT, "xRight" }, + + { K_F1, "F1" }, + { K_F2, "F2" }, + { K_F3, "F3" }, + { K_F4, "F4" }, + { K_F5, "F5" }, + { K_F6, "F6" }, + { K_F7, "F7" }, + { K_F8, "F8" }, + { K_F9, "F9" }, + { K_F10, "F10" }, + + { K_F11, "F11" }, + { K_F12, "F12" }, + { K_F13, "F13" }, + { K_F14, "F14" }, + { K_F15, "F15" }, + { K_F16, "F16" }, + { K_F17, "F17" }, + { K_F18, "F18" }, + { K_F19, "F19" }, + { K_F20, "F20" }, + + { K_F21, "F21" }, + { K_F22, "F22" }, + { K_F23, "F23" }, + { K_F24, "F24" }, + { K_F25, "F25" }, + { K_F26, "F26" }, + { K_F27, "F27" }, + { K_F28, "F28" }, + { K_F29, "F29" }, + { K_F30, "F30" }, + + { K_F31, "F31" }, + { K_F32, "F32" }, + { K_F33, "F33" }, + { K_F34, "F34" }, + { K_F35, "F35" }, + { K_F36, "F36" }, + { K_F37, "F37" }, + + { K_XF1, "xF1" }, + { K_XF2, "xF2" }, + { K_XF3, "xF3" }, + { K_XF4, "xF4" }, + + { K_HELP, "Help" }, + { K_UNDO, "Undo" }, + { K_INS, "Insert" }, + { K_INS, "Ins" }, // Alternative name + { K_KINS, "kInsert" }, + { K_HOME, "Home" }, + { K_KHOME, "kHome" }, + { K_XHOME, "xHome" }, + { K_ZHOME, "zHome" }, + { K_END, "End" }, + { K_KEND, "kEnd" }, + { K_XEND, "xEnd" }, + { K_ZEND, "zEnd" }, + { K_PAGEUP, "PageUp" }, + { K_PAGEDOWN, "PageDown" }, + { K_KPAGEUP, "kPageUp" }, + { K_KPAGEDOWN, "kPageDown" }, + + { K_KPLUS, "kPlus" }, + { K_KMINUS, "kMinus" }, + { K_KDIVIDE, "kDivide" }, + { K_KMULTIPLY, "kMultiply" }, + { K_KENTER, "kEnter" }, + { K_KPOINT, "kPoint" }, + + { K_K0, "k0" }, + { K_K1, "k1" }, + { K_K2, "k2" }, + { K_K3, "k3" }, + { K_K4, "k4" }, + { K_K5, "k5" }, + { K_K6, "k6" }, + { K_K7, "k7" }, + { K_K8, "k8" }, + { K_K9, "k9" }, + + { '<', "lt" }, + + { K_MOUSE, "Mouse" }, + { K_LEFTMOUSE, "LeftMouse" }, + { K_LEFTMOUSE_NM, "LeftMouseNM" }, + { K_LEFTDRAG, "LeftDrag" }, + { K_LEFTRELEASE, "LeftRelease" }, + { K_LEFTRELEASE_NM, "LeftReleaseNM" }, + { K_MIDDLEMOUSE, "MiddleMouse" }, + { K_MIDDLEDRAG, "MiddleDrag" }, + { K_MIDDLERELEASE, "MiddleRelease" }, + { K_RIGHTMOUSE, "RightMouse" }, + { K_RIGHTDRAG, "RightDrag" }, + { K_RIGHTRELEASE, "RightRelease" }, + { K_MOUSEDOWN, "ScrollWheelUp" }, + { K_MOUSEUP, "ScrollWheelDown" }, + { K_MOUSELEFT, "ScrollWheelRight" }, + { K_MOUSERIGHT, "ScrollWheelLeft" }, + { K_MOUSEDOWN, "MouseDown" }, // OBSOLETE: Use + { K_MOUSEUP, "MouseUp" }, // ScrollWheelXXX instead + { K_X1MOUSE, "X1Mouse" }, + { K_X1DRAG, "X1Drag" }, + { K_X1RELEASE, "X1Release" }, + { K_X2MOUSE, "X2Mouse" }, + { K_X2DRAG, "X2Drag" }, + { K_X2RELEASE, "X2Release" }, + { K_DROP, "Drop" }, + { K_ZERO, "Nul" }, + { K_SNR, "SNR" }, + { K_PLUG, "Plug" }, + { K_PASTE, "Paste" }, + { 0, NULL } }; int get_special_key_code(const char_u *name) -- cgit From a535d68380e472a661478cd6a65aad243ebd6a82 Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 16 Oct 2017 01:00:58 +0300 Subject: keymap: Remove incorrect cast --- src/nvim/keymap.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/nvim/keymap.c b/src/nvim/keymap.c index 4f120e66f7..6ed54464e8 100644 --- a/src/nvim/keymap.c +++ b/src/nvim/keymap.c @@ -696,11 +696,14 @@ int find_special_key_in_table(int c) { int i; - for (i = 0; key_names_table[i].name != NULL; i++) - if (c == (uint8_t)key_names_table[i].key) + for (i = 0; key_names_table[i].name != NULL; i++) { + if (c == key_names_table[i].key) { break; - if (key_names_table[i].name == NULL) + } + } + if (key_names_table[i].name == NULL) { i = -1; + } return i; } -- cgit From 248493f155e42186440c7d081b27ffe760b67b9e Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 16 Oct 2017 03:03:34 +0300 Subject: test/unit/formatc: Fix parsing of most recent viml_parser_highlight --- test/unit/formatc.lua | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/test/unit/formatc.lua b/test/unit/formatc.lua index e288081960..2fd37c599a 100644 --- a/test/unit/formatc.lua +++ b/test/unit/formatc.lua @@ -65,11 +65,12 @@ local tokens = P { "tokens"; identifier = Ct(C(R("az","AZ","__") * R("09","az","AZ","__")^0) * Cc"identifier"), -- Single character in a string - string_char = R("az","AZ","09") + S"$%^&*()_-+={[}]:;@~#<,>.!?/ \t" + (P"\\" * S[[ntvbrfa\?'"0x]]), + sstring_char = R("\001&","([","]\255") + (P"\\" * S[[ntvbrfa\?'"0x]]), + dstring_char = R("\001!","#[","]\255") + (P"\\" * S[[ntvbrfa\?'"0x]]), -- String literal - string = Ct(C(P"'" * (V"string_char" + P'"')^0 * P"'" + - P'"' * (V"string_char" + P"'")^0 * P'"') * Cc"string"), + string = Ct(C(P"'" * (V"sstring_char" + P'"')^0 * P"'" + + P'"' * (V"dstring_char" + P"'")^0 * P'"') * Cc"string"), -- Operator operator = Ct(C(P">>=" + P"<<=" + P"..." + -- cgit From 4c8ed65b608df06b4c72b641f4ecc86985295633 Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 16 Oct 2017 03:04:22 +0300 Subject: viml/parser/expressions: Fix memory leak when processing ternary --- src/nvim/viml/parser/expressions.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index 35f4385f33..876fbc8d37 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -2308,10 +2308,10 @@ viml_pexpr_parse_invalid_colon: _("E15: Colon outside of dictionary or ternary operator: %.*s")); viml_pexpr_parse_valid_colon: ADD_VALUE_IF_MISSING(_(EXP_VAL_COLON)); - NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeColon); if (is_ternary) { HL_CUR_TOKEN(TernaryColon); } else { + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeColon); ADD_OP_NODE(cur_node); HL_CUR_TOKEN(Colon); } -- cgit From c03dc13bb74205d15a83ce3bd6ecb6b76b869878 Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 16 Oct 2017 03:05:27 +0300 Subject: klee: Fix possible assertion error No idea how it did not happen to hit me yet. --- test/symbolic/klee/nvim/memory.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/symbolic/klee/nvim/memory.c b/test/symbolic/klee/nvim/memory.c index 1f9cdce6c0..df422cea3e 100644 --- a/test/symbolic/klee/nvim/memory.c +++ b/test/symbolic/klee/nvim/memory.c @@ -35,6 +35,9 @@ void *xmalloc(const size_t size) void xfree(void *const p) { + if (p == NULL) { + return; + } RINGBUF_FORALL(&arecs, AllocRecord, arec) { if (arec->ptr == p) { allocated_memory -= arec->size; -- cgit From 252a76db80dd846f9ccac4d7001697c12c009826 Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 16 Oct 2017 03:06:34 +0300 Subject: unittests: Free everything and check for memory leaks Also improves error reporting. --- test/unit/helpers.lua | 11 +++++- test/unit/viml/expressions/parser_spec.lua | 56 +++++++++++++++++++----------- 2 files changed, 45 insertions(+), 22 deletions(-) diff --git a/test/unit/helpers.lua b/test/unit/helpers.lua index d3d14a5ca2..68ce9eed62 100644 --- a/test/unit/helpers.lua +++ b/test/unit/helpers.lua @@ -315,7 +315,7 @@ local function alloc_log_new() eq(exp, self.log) self:clear() end - function log:clear_tmp_allocs() + function log:clear_tmp_allocs(clear_null_frees) local toremove = {} local allocs = {} for i, v in ipairs(self.log) do @@ -327,6 +327,8 @@ local function alloc_log_new() if v.func == 'free' then toremove[#toremove + 1] = i end + elseif clear_null_frees and v.args[1] == self.null then + toremove[#toremove + 1] = i end if v.func == 'realloc' then allocs[tostring(v.ret)] = i @@ -779,6 +781,12 @@ local function kvi_init(kvi) return kvi end +local function kvi_destroy(kvi) + if kvi.items ~= kvi.init_array then + lib.xfree(kvi.items) + end +end + local function kvi_new(ct) return kvi_init(ffi.new(ct)) end @@ -830,6 +838,7 @@ local module = { sc = sc, conv_enum = conv_enum, array_size = array_sive, + kvi_destroy = kvi_destroy, kvi_size = kvi_size, kvi_init = kvi_init, kvi_new = kvi_new, diff --git a/test/unit/viml/expressions/parser_spec.lua b/test/unit/viml/expressions/parser_spec.lua index 54fd54aea8..5f5924c630 100644 --- a/test/unit/viml/expressions/parser_spec.lua +++ b/test/unit/viml/expressions/parser_spec.lua @@ -5,6 +5,8 @@ local viml_helpers = require('test.unit.viml.helpers') local make_enum_conv_tab = helpers.make_enum_conv_tab local child_call_once = helpers.child_call_once +local alloc_log_new = helpers.alloc_log_new +local kvi_destroy = helpers.kvi_destroy local conv_enum = helpers.conv_enum local ptr2key = helpers.ptr2key local cimport = helpers.cimport @@ -23,6 +25,8 @@ local format_luav = global_helpers.format_luav local lib = cimport('./src/nvim/viml/parser/expressions.h') +local alloc_log = alloc_log_new() + local function format_check(expr, flags, ast, hls) -- That forces specific order. print( format_string('\ncheck_parsing(%r, %u, {', expr, flags)) @@ -230,30 +234,40 @@ end) describe('Expressions parser', function() local function check_parsing(str, flags, exp_ast, exp_highlighting_fs) - flags = flags or 0 - - if os.getenv('NVIM_TEST_PARSER_SPEC_PRINT_TEST_CASE') == '1' then - print(str, flags) - end + local err, msg = pcall(function() + flags = flags or 0 - local pstate = new_pstate({str}) - local east = lib.viml_pexpr_parse(pstate, flags) - local ast = east2lua(pstate, east) - local hls = phl2lua(pstate) - if exp_ast == nil then - format_check(str, flags, ast, hls) - return - end - eq(exp_ast, ast) - if exp_highlighting_fs then - local exp_highlighting = {} - local next_col = 0 - for i, h in ipairs(exp_highlighting_fs) do - exp_highlighting[i], next_col = h(next_col) + if os.getenv('NVIM_TEST_PARSER_SPEC_PRINT_TEST_CASE') == '1' then + print(str, flags) + end + alloc_log:check({}) + + local pstate = new_pstate({str}) + local east = lib.viml_pexpr_parse(pstate, flags) + local ast = east2lua(pstate, east) + local hls = phl2lua(pstate) + if exp_ast == nil then + format_check(str, flags, ast, hls) + return + end + eq(exp_ast, ast) + if exp_highlighting_fs then + local exp_highlighting = {} + local next_col = 0 + for i, h in ipairs(exp_highlighting_fs) do + exp_highlighting[i], next_col = h(next_col) + end + eq(exp_highlighting, hls) end - eq(exp_highlighting, hls) + lib.viml_pexpr_free_ast(east) + kvi_destroy(pstate.colors) + alloc_log:clear_tmp_allocs(true) + alloc_log:check({}) + end) + if not err then + msg = format_string('Error while processing test (%r, %u):\n%s', str, flags, msg) + error(msg) end - lib.viml_pexpr_free_ast(east) end local function hl(group, str, shift) return function(next_col) -- cgit From 8e856ebcd0357c8782d6825a233a5c800475f396 Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 16 Oct 2017 08:51:26 +0300 Subject: klee: Add run.sh --help and run.sh -s --- test/symbolic/klee/run.sh | 51 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 42 insertions(+), 9 deletions(-) diff --git a/test/symbolic/klee/run.sh b/test/symbolic/klee/run.sh index c143cd0624..0234a935b5 100755 --- a/test/symbolic/klee/run.sh +++ b/test/symbolic/klee/run.sh @@ -10,13 +10,43 @@ KLEE_TEST_DIR="$PROJECT_SOURCE_DIR/test/symbolic/klee" KLEE_BIN_DIR="$PROJECT_BINARY_DIR/klee" KLEE_OUT_DIR="$KLEE_BIN_DIR/out" +help() { + echo "Usage:" + echo + echo " $0 -c fname" + echo " $0 fname" + echo " $0 -s" + echo + echo "First form compiles executable out of test/symbolic/klee/{fname}.c." + echo "Compiled executable is placed into build/klee/{fname}. Must first" + echo "successfully compile Neovim in order to generate declarations." + echo + echo "Second form runs KLEE in a docker container using file " + echo "test/symbolic/klee/{fname.c}. Bitcode is placed into build/klee/a.bc," + echo "results are placed into build/klee/out/. The latter is first deleted if" + echo "it exists." + echo + echo "Third form runs ktest-tool which prints errors found by KLEE via " + echo "the same container used to run KLEE." +} + main() { local compile= - if test "$1" = "-c" ; then + local print_errs= + if test "$1" = "--help" ; then + help + return + fi + if test "$1" = "-s" ; then + print_errs=1 + shift + elif test "$1" = "-c" ; then compile=1 shift fi - local test="$1" ; shift + if test -z "$print_errs" ; then + local test="$1" ; shift + fi local includes= includes="$includes -I$KLEE_TEST_DIR" @@ -36,14 +66,17 @@ main() { test -d "$KLEE_BIN_DIR" || mkdir -p "$KLEE_BIN_DIR" if test -z "$compile" ; then - test -d "$KLEE_OUT_DIR" && rm -r "$KLEE_OUT_DIR" local line1='cd /image' - line1="$line1 && $(echo clang \ - $includes $defines \ - -o "$KLEE_BIN_DIR/a.bc" -emit-llvm -g -c \ - "$KLEE_TEST_DIR/$test.c")" - line1="$line1 && klee --libc=uclibc --posix-runtime " - line1="$line1 '--output-dir=$KLEE_OUT_DIR' '$KLEE_BIN_DIR/a.bc'" + if test -z "$print_errs" ; then + test -d "$KLEE_OUT_DIR" && rm -r "$KLEE_OUT_DIR" + + line1="$line1 && $(echo clang \ + $includes $defines \ + -o "$KLEE_BIN_DIR/a.bc" -emit-llvm -g -c \ + "$KLEE_TEST_DIR/$test.c")" + line1="$line1 && klee --libc=uclibc --posix-runtime " + line1="$line1 '--output-dir=$KLEE_OUT_DIR' '$KLEE_BIN_DIR/a.bc'" + fi local line2="for t in '$KLEE_OUT_DIR'/*.err" line2="$line2 ; do ktest-tool --write-ints" line2="$line2 \"\$(printf '%s' \"\$t\" | sed -e 's@\\.[^/]*\$@.ktest@')\"" -- cgit From c9f511d24a64da135bef4b9874c7bec04d9330e4 Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 16 Oct 2017 09:06:05 +0300 Subject: viml/parser/expressions: Remove unused flag --- src/nvim/viml/parser/expressions.h | 4 ---- test/symbolic/klee/viml_expressions_parser.c | 7 +++---- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/src/nvim/viml/parser/expressions.h b/src/nvim/viml/parser/expressions.h index 025f0f766e..d783518b3a 100644 --- a/src/nvim/viml/parser/expressions.h +++ b/src/nvim/viml/parser/expressions.h @@ -314,10 +314,6 @@ enum { /// When parsing expressions input by user bar is assumed to be a binary /// operator and other two are spacings. kExprFlagsDisallowEOC = (1 << 1), - /// Print errors when encountered - /// - /// Without the flag they are only taken into account when parsing. - kExprFlagsPrintError = (1 << 2), // WARNING: whenever you add a new flag, alter klee_assume() statement in // viml_expressions_parser.c. } ExprParserFlags; diff --git a/test/symbolic/klee/viml_expressions_parser.c b/test/symbolic/klee/viml_expressions_parser.c index 8f015ae9a7..9448937430 100644 --- a/test/symbolic/klee/viml_expressions_parser.c +++ b/test/symbolic/klee/viml_expressions_parser.c @@ -35,7 +35,7 @@ int main(const int argc, const char *const *const argv, { char input[INPUT_SIZE]; uint8_t shift; - int flags; + unsigned flags; const bool peek = false; avoid_optimizing_out = argc; @@ -48,8 +48,7 @@ int main(const int argc, const char *const *const argv, klee_make_symbolic(&shift, sizeof(shift), "shift"); klee_make_symbolic(&flags, sizeof(flags), "flags"); klee_assume(shift < INPUT_SIZE); - klee_assume( - flags <= (kExprFlagsMulti|kExprFlagsDisallowEOC|kExprFlagsPrintError)); + klee_assume(flags <= (kExprFlagsMulti|kExprFlagsDisallowEOC)); #endif ParserLine plines[] = { @@ -91,7 +90,7 @@ int main(const int argc, const char *const *const argv, }; kvi_init(pstate.reader.lines); - const ExprAST ast = viml_pexpr_parse(&pstate, flags); + const ExprAST ast = viml_pexpr_parse(&pstate, (int)flags); assert(ast.root != NULL || ast.err.msg); // Can’t possibly have more highlight tokens then there are bytes in string. assert(kv_size(colors) <= INPUT_SIZE - shift); -- cgit From 895793fc820e04ea2d6bdaa90c6643c4dce2f0e7 Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 16 Oct 2017 09:14:02 +0300 Subject: viml/parser/expressions: Add some casts --- src/nvim/viml/parser/expressions.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index 876fbc8d37..69817bf24f 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -698,8 +698,8 @@ const char *viml_pexpr_repr_token(const ParserState *const pstate, (int)token.data.num.is_float, (int)token.data.num.base, (double)(token.data.num.is_float - ? token.data.num.val.floating - : token.data.num.val.integer)) + ? (double)token.data.num.val.floating + : (double)token.data.num.val.integer)) TKNARGS(kExprLexInvalid, "(msg=%s)", token.data.err.msg) default: { // No additional arguments. -- cgit From 47938e1e22816381f26e8882eacd6e7e8baf37fd Mon Sep 17 00:00:00 2001 From: ZyX Date: Thu, 19 Oct 2017 10:48:05 +0300 Subject: viml/parser/expressions: Fix some errors spotted by KLEE Not all of them are fixed yet though. --- src/nvim/viml/parser/expressions.c | 58 ++++++++---- test/unit/viml/expressions/parser_spec.lua | 139 +++++++++++++++++++++++++++++ 2 files changed, 178 insertions(+), 19 deletions(-) diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index 69817bf24f..da22cf4cdb 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -1091,7 +1091,7 @@ void viml_pexpr_free_ast(ExprAST ast) // NVimInvalidDoubleQuote -> NVimInvalidString // NVimInvalidDoubleQuotedBody -> NVimInvalidString // NVimInvalidDoubleQuotedEscape -> NVimInvalidStringSpecial -// NVimInvalidDoubleQuotedUnknownEscape -> NVimInvalid +// NVimInvalidDoubleQuotedUnknownEscape -> NVimInvalidDoubleQuotedEscape // // NVimFigureBrace -> NVimInternalError // NVimInvalidSingleQuotedUnknownEscape -> NVimInternalError @@ -1313,7 +1313,7 @@ static bool viml_pexpr_handle_bop(const ParserState *const pstate, /// ParserPosition literal based on ParserPosition pos with columns shifted /// -/// Function does not check whether remaining position is valid. +/// Function does not check whether resulting position is valid. /// /// @param[in] pos Position to shift. /// @param[in] shift Number of bytes to shift. @@ -1326,6 +1326,21 @@ static inline ParserPosition shifted_pos(const ParserPosition pos, return (ParserPosition) { .line = pos.line, .col = pos.col + shift }; } +/// ParserPosition literal based on ParserPosition pos with specified column +/// +/// Function does not check whether remaining position is valid. +/// +/// @param[in] pos Position to adjust. +/// @param[in] new_col New column. +/// +/// @return Shifted position. +static inline ParserPosition recol_pos(const ParserPosition pos, + const size_t new_col) + FUNC_ATTR_CONST FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_WARN_UNUSED_RESULT +{ + return (ParserPosition) { .line = pos.line, .col = new_col }; +} + /// Get highlight group name #define HL(g) (is_invalid ? "NVimInvalid" #g : "NVim" #g) @@ -1639,7 +1654,7 @@ static void parse_quoted_string(ParserState *const pstate, size_t n = (*p == 'u' ? 4 : 8); int nr = 0; p++; - while (n-- && ascii_isxdigit(p[1])) { + while (p + 1 < e && n-- && ascii_isxdigit(p[1])) { p++; nr = (nr << 4) + hex2nr(*p); } @@ -1659,7 +1674,7 @@ static void parse_quoted_string(ParserState *const pstate, if (*p >= '0' && *p <= '7') { size--; p++; - if (*p >= '0' && *p <= '7') { + if (p < e && *p >= '0' && *p <= '7') { size--; p++; } @@ -1715,7 +1730,7 @@ static void parse_quoted_string(ParserState *const pstate, // Hexadecimal or unicode. case 'X': case 'x': case 'u': case 'U': { - if (ascii_isxdigit(p[1])) { + if (p + 1 < e && ascii_isxdigit(p[1])) { size_t n; int nr; bool is_hex = (*p == 'x' || *p == 'X'); @@ -1728,7 +1743,7 @@ static void parse_quoted_string(ParserState *const pstate, n = 8; } nr = 0; - while (n-- && ascii_isxdigit(p[1])) { + while (p + 1 < e && n-- && ascii_isxdigit(p[1])) { p++; nr = (nr << 4) + hex2nr(*p); } @@ -1749,9 +1764,9 @@ static void parse_quoted_string(ParserState *const pstate, case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': { uint8_t ch = (uint8_t)(*p++ - '0'); - if (*p >= '0' && *p <= '7') { + if (p < e && *p >= '0' && *p <= '7') { ch = (uint8_t)((ch << 3) + *p++ - '0'); - if (*p >= '0' && *p <= '7') { + if (p < e && *p >= '0' && *p <= '7') { ch = (uint8_t)((ch << 3) + *p++ - '0'); } } @@ -1793,7 +1808,7 @@ static void parse_quoted_string(ParserState *const pstate, // TODO(ZyX-I): use ast_stack to determine and highlight regular expressions // TODO(ZyX-I): use ast_stack to determine and highlight printf format str // TODO(ZyX-I): use ast_stack to determine and highlight expression strings - size_t next_col = 1; + size_t next_col = token.start.col + 1; const char *const body_str = (is_double ? HL(DoubleQuotedBody) : HL(SingleQuotedBody)); @@ -1806,20 +1821,23 @@ static void parse_quoted_string(ParserState *const pstate, for (size_t i = 0; i < kv_size(shifts); i++) { const StringShift cur_shift = kv_A(shifts, i); if (cur_shift.start > next_col) { - viml_parser_highlight(pstate, shifted_pos(token.start, next_col), + viml_parser_highlight(pstate, recol_pos(token.start, next_col), cur_shift.start - next_col, body_str); } - viml_parser_highlight(pstate, shifted_pos(token.start, cur_shift.start), + viml_parser_highlight(pstate, recol_pos(token.start, cur_shift.start), cur_shift.orig_len, (cur_shift.escape_not_known ? ukn_esc_str : esc_str)); next_col = cur_shift.start + cur_shift.orig_len; } - if (next_col < token.len - token.data.str.closed) { - viml_parser_highlight(pstate, shifted_pos(token.start, next_col), - token.len - token.data.str.closed - next_col, + if (next_col - token.start.col < token.len - token.data.str.closed) { + viml_parser_highlight(pstate, recol_pos(token.start, next_col), + (token.start.col + + token.len + - token.data.str.closed + - next_col), body_str); } } @@ -2580,6 +2598,9 @@ viml_pexpr_parse_figure_brace_closing_error: break; } case kExprLexPlainIdentifier: { + const ExprVarScope scope = (cur_token.type == kExprLexInvalid + ? kExprVarScopeMissing + : cur_token.data.var.scope); if (want_node == kENodeValue || want_node == kENodeArgument) { want_node = (want_node == kENodeArgument ? kENodeArgumentSeparator @@ -2588,9 +2609,8 @@ viml_pexpr_parse_figure_brace_closing_error: (node_is_key ? kExprNodePlainKey : kExprNodePlainIdentifier)); - cur_node->data.var.scope = cur_token.data.var.scope; - const size_t scope_shift = ( - cur_token.data.var.scope == kExprVarScopeMissing ? 0 : 2); + cur_node->data.var.scope = scope; + const size_t scope_shift = (scope == kExprVarScopeMissing ? 0 : 2); cur_node->data.var.ident = (pline.data + cur_token.start.col + scope_shift); cur_node->data.var.ident_len = cur_token.len - scope_shift; @@ -2609,11 +2629,11 @@ viml_pexpr_parse_figure_brace_closing_error: ? HL(IdentifierKey) : HL(Identifier))); } else { - if (cur_token.data.var.scope == kExprVarScopeMissing) { + if (scope == kExprVarScopeMissing) { ADD_IDENT( do { NEW_NODE_WITH_CUR_POS(cur_node, kExprNodePlainIdentifier); - cur_node->data.var.scope = cur_token.data.var.scope; + cur_node->data.var.scope = scope; cur_node->data.var.ident = pline.data + cur_token.start.col; cur_node->data.var.ident_len = cur_token.len; want_node = kENodeOperator; diff --git a/test/unit/viml/expressions/parser_spec.lua b/test/unit/viml/expressions/parser_spec.lua index 5f5924c630..b81a3c0f82 100644 --- a/test/unit/viml/expressions/parser_spec.lua +++ b/test/unit/viml/expressions/parser_spec.lua @@ -7006,5 +7006,144 @@ describe('Expressions parser', function() hl('NumberPrefix', '0'), hl('Number', '0'), }) + check_parsing('"\\U\\', 0, { + -- 0123 + ast = { + [[DoubleQuotedString(val="U\\"):0:0:"\U\]], + }, + err = { + arg = '"\\U\\', + msg = 'E114: Missing double quote: %.*s', + }, + }, { + hl('InvalidDoubleQuotedString', '"'), + hl('InvalidDoubleQuotedUnknownEscape', '\\U'), + hl('InvalidDoubleQuotedBody', '\\'), + }) + check_parsing('"\\U', 0, { + -- 012 + ast = { + 'DoubleQuotedString(val="U"):0:0:"\\U', + }, + err = { + arg = '"\\U', + msg = 'E114: Missing double quote: %.*s', + }, + }, { + hl('InvalidDoubleQuotedString', '"'), + hl('InvalidDoubleQuotedUnknownEscape', '\\U'), + }) + check_parsing('|"\\U\\', 2, { + -- 01234 + ast = { + { + 'Or:0:0:|', + children = { + 'Missing:0:0:', + 'DoubleQuotedString(val="U\\\\"):0:1:"\\U\\', + }, + }, + }, + err = { + arg = '|"\\U\\', + msg = 'E15: Unexpected EOC character: %.*s', + }, + }, { + hl('InvalidOr', '|'), + hl('InvalidDoubleQuotedString', '"'), + hl('InvalidDoubleQuotedUnknownEscape', '\\U'), + hl('InvalidDoubleQuotedBody', '\\'), + }) + check_parsing('|"\\e"', 2, { + -- 01234 + ast = { + { + 'Or:0:0:|', + children = { + 'Missing:0:0:', + 'DoubleQuotedString(val="\\27"):0:1:"\\e"', + }, + }, + }, + err = { + arg = '|"\\e"', + msg = 'E15: Unexpected EOC character: %.*s', + }, + }, { + hl('InvalidOr', '|'), + hl('DoubleQuotedString', '"'), + hl('DoubleQuotedEscape', '\\e'), + hl('DoubleQuotedString', '"'), + }) + check_parsing('|\029', 2, { + -- 01 + ast = { + { + 'Or:0:0:|', + children = { + 'Missing:0:0:', + 'PlainIdentifier(scope=0,ident=\029):0:1:\029', + }, + }, + }, + err = { + arg = '|\029', + msg = 'E15: Unexpected EOC character: %.*s', + }, + }, { + hl('InvalidOr', '|'), + hl('InvalidIdentifier', '\029'), + }) + check_parsing('"\\<', 0, { + -- 012 + ast = { + 'DoubleQuotedString(val="<"):0:0:"\\<', + }, + err = { + arg = '"\\<', + msg = 'E114: Missing double quote: %.*s', + }, + }, { + hl('InvalidDoubleQuotedString', '"'), + hl('InvalidDoubleQuotedUnknownEscape', '\\<'), + }) + check_parsing('"\\1', 0, { + -- 012 + ast = { + 'DoubleQuotedString(val="\\1"):0:0:"\\1', + }, + err = { + arg = '"\\1', + msg = 'E114: Missing double quote: %.*s', + }, + }, { + hl('InvalidDoubleQuotedString', '"'), + hl('InvalidDoubleQuotedEscape', '\\1'), + }) + check_parsing('}l') + check_parsing(':?\000\000\000\000\000\000\000', 0, { + ast = { + { + 'Colon:0:0::', + children = { + 'Missing:0:0:', + { + 'Ternary:0:1:?', + children = { + 'Missing:0:1:', + 'TernaryValue:0:1:?', + }, + }, + }, + }, + }, + err = { + arg = ':?', + msg = 'E15: Colon outside of dictionary or ternary operator: %.*s', + }, + }, { + hl('InvalidColon', ':'), + hl('InvalidTernary', '?'), + }) end) end) -- cgit From b574e95850c064d4e746a7373aff1a3d7bd4de27 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 29 Oct 2017 01:17:00 +0300 Subject: charset: Some more refactoring of vim_str2nr --- src/nvim/charset.c | 78 +++++++++++++++++++++--------------------------------- src/nvim/version.c | 2 ++ 2 files changed, 32 insertions(+), 48 deletions(-) diff --git a/src/nvim/charset.c b/src/nvim/charset.c index a7ffa4bc00..c895d65eb7 100644 --- a/src/nvim/charset.c +++ b/src/nvim/charset.c @@ -1656,18 +1656,19 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, // decimal or octal, default is decimal pre = 0; - if (what & STR2NR_OCT) { - // Don't interpret "0", "08" or "0129" as octal. - for (int i = 1; !STRING_ENDED(ptr + i) && ascii_isdigit(ptr[i]); i++) { + if (what & STR2NR_OCT + && !STRING_ENDED(ptr + 1) + && ('0' <= ptr[1] && ptr[1] <= '7')) { + // Assume octal now: what we already know is that string starts with + // zero and some octal digit. + pre = '0'; + // Don’t interpret "0", "008" or "0129" as octal. + for (int i = 2; !STRING_ENDED(ptr + i) && ascii_isdigit(ptr[i]); i++) { if (ptr[i] > '7') { - // can't be octal + // Can’t be octal. pre = 0; break; } - if (ptr[i] >= '0') { - // assume octal - pre = '0'; - } } } } @@ -1675,51 +1676,32 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, // Do the string-to-numeric conversion "manually" to avoid sscanf quirks. uvarnumber_T un = 0; +#define PARSE_NUMBER(base, cond, conv) \ + do { \ + while (!STRING_ENDED(ptr) && (cond)) { \ + /* avoid ubsan error for overflow */ \ + if (un < UVARNUMBER_MAX / base) { \ + un = base * un + (uvarnumber_T)(conv); \ + } else { \ + un = UVARNUMBER_MAX; \ + } \ + ptr++; \ + } \ + } while (0) if (pre == 'B' || pre == 'b' || what == (STR2NR_BIN|STR2NR_FORCE)) { - // bin - while (!STRING_ENDED(ptr) && '0' <= *ptr && *ptr <= '1') { - // avoid ubsan error for overflow - if (un < UVARNUMBER_MAX / 2) { - un = 2 * un + (uvarnumber_T)(*ptr - '0'); - } else { - un = UVARNUMBER_MAX; - } - ptr++; - } + // Binary number. + PARSE_NUMBER(2, (*ptr == '0' || *ptr == '1'), (*ptr - '0')); } else if (pre == '0' || what == (STR2NR_OCT|STR2NR_FORCE)) { - // octal - while (!STRING_ENDED(ptr) && '0' <= *ptr && *ptr <= '7') { - // avoid ubsan error for overflow - if (un < UVARNUMBER_MAX / 8) { - un = 8 * un + (uvarnumber_T)(*ptr - '0'); - } else { - un = UVARNUMBER_MAX; - } - ptr++; - } + // Octal number. + PARSE_NUMBER(8, ('0' <= *ptr && *ptr <= '7'), (*ptr - '0')); } else if (pre == 'X' || pre == 'x' || what == (STR2NR_HEX|STR2NR_FORCE)) { - // hex - while (!STRING_ENDED(ptr) && ascii_isxdigit(*ptr)) { - // avoid ubsan error for overflow - if (un < UVARNUMBER_MAX / 16) { - un = 16 * un + (uvarnumber_T)hex2nr(*ptr); - } else { - un = UVARNUMBER_MAX; - } - ptr++; - } + // Hexadecimal number. + PARSE_NUMBER(16, (ascii_isxdigit(*ptr)), (hex2nr(*ptr))); } else { - // decimal - while (!STRING_ENDED(ptr) && ascii_isdigit(*ptr)) { - // avoid ubsan error for overflow - if (un < UVARNUMBER_MAX / 10) { - un = 10 * un + (uvarnumber_T)(*ptr - '0'); - } else { - un = UVARNUMBER_MAX; - } - ptr++; - } + // Decimal number. + PARSE_NUMBER(10, (ascii_isdigit(*ptr)), (*ptr - '0')); } +#undef PARSE_NUMBER if (prep != NULL) { *prep = pre; diff --git a/src/nvim/version.c b/src/nvim/version.c index 7e59f3327a..3382e394f8 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -77,6 +77,8 @@ static char *features[] = { // clang-format off static const int included_patches[] = { + 1229, + 1230, // 1026, 1025, 1024, -- cgit From 568cf73c90af2966ee091f2180905a8cf9582064 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 29 Oct 2017 01:29:48 +0300 Subject: viml/parser/expressions: Fix last error found by KLEE --- src/nvim/viml/parser/expressions.c | 1 + test/unit/viml/expressions/parser_spec.lua | 20 +++++++++++++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index da22cf4cdb..b413d56592 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -2445,6 +2445,7 @@ viml_pexpr_parse_bracket_closing_error: cur_node->children = *top_node_p; } *top_node_p = cur_node; + new_top_node_p = top_node_p; goto viml_pexpr_parse_figure_brace_closing_error; } if (want_node == kENodeValue) { diff --git a/test/unit/viml/expressions/parser_spec.lua b/test/unit/viml/expressions/parser_spec.lua index b81a3c0f82..f3d0790e68 100644 --- a/test/unit/viml/expressions/parser_spec.lua +++ b/test/unit/viml/expressions/parser_spec.lua @@ -7120,7 +7120,25 @@ describe('Expressions parser', function() hl('InvalidDoubleQuotedString', '"'), hl('InvalidDoubleQuotedEscape', '\\1'), }) - check_parsing('}l') + check_parsing('}l', 0, { + -- 01 + ast = { + { + 'OpMissing:0:1:', + children = { + 'UnknownFigure(---):0:0:', + 'PlainIdentifier(scope=0,ident=l):0:1:l', + }, + }, + }, + err = { + arg = '}l', + msg = 'E15: Unexpected closing figure brace: %.*s', + }, + }, { + hl('InvalidFigureBrace', '}'), + hl('InvalidIdentifier', 'l'), + }) check_parsing(':?\000\000\000\000\000\000\000', 0, { ast = { { -- cgit From c202f17c8d8cb2140aa1b21b2e2d2ab3925a7812 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 29 Oct 2017 01:31:31 +0300 Subject: unittests: Avoid alloc log checking errors when printing tests --- test/unit/viml/expressions/parser_spec.lua | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/test/unit/viml/expressions/parser_spec.lua b/test/unit/viml/expressions/parser_spec.lua index f3d0790e68..125a658f7b 100644 --- a/test/unit/viml/expressions/parser_spec.lua +++ b/test/unit/viml/expressions/parser_spec.lua @@ -248,16 +248,16 @@ describe('Expressions parser', function() local hls = phl2lua(pstate) if exp_ast == nil then format_check(str, flags, ast, hls) - return - end - eq(exp_ast, ast) - if exp_highlighting_fs then - local exp_highlighting = {} - local next_col = 0 - for i, h in ipairs(exp_highlighting_fs) do - exp_highlighting[i], next_col = h(next_col) + else + eq(exp_ast, ast) + if exp_highlighting_fs then + local exp_highlighting = {} + local next_col = 0 + for i, h in ipairs(exp_highlighting_fs) do + exp_highlighting[i], next_col = h(next_col) + end + eq(exp_highlighting, hls) end - eq(exp_highlighting, hls) end lib.viml_pexpr_free_ast(east) kvi_destroy(pstate.colors) -- cgit From 06bdc9ed839eedbead34d58214927d3c0cebff58 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 29 Oct 2017 01:40:55 +0300 Subject: klee: Update vim_str2nr in mock as well --- test/symbolic/klee/nvim/charset.c | 78 +++++++++++++++------------------------ 1 file changed, 30 insertions(+), 48 deletions(-) diff --git a/test/symbolic/klee/nvim/charset.c b/test/symbolic/klee/nvim/charset.c index c584bd72ef..f3a218949f 100644 --- a/test/symbolic/klee/nvim/charset.c +++ b/test/symbolic/klee/nvim/charset.c @@ -60,18 +60,19 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, // decimal or octal, default is decimal pre = 0; - if (what & STR2NR_OCT) { - // Don't interpret "0", "08" or "0129" as octal. - for (int i = 1; !STRING_ENDED(ptr + i) && ascii_isdigit(ptr[i]); i++) { + if (what & STR2NR_OCT + && !STRING_ENDED(ptr + 1) + && ('0' <= ptr[1] && ptr[1] <= '7')) { + // Assume octal now: what we already know is that string starts with + // zero and some octal digit. + pre = '0'; + // Don’t interpret "0", "008" or "0129" as octal. + for (int i = 2; !STRING_ENDED(ptr + i) && ascii_isdigit(ptr[i]); i++) { if (ptr[i] > '7') { - // can't be octal + // Can’t be octal. pre = 0; break; } - if (ptr[i] >= '0') { - // assume octal - pre = '0'; - } } } } @@ -79,51 +80,32 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, // Do the string-to-numeric conversion "manually" to avoid sscanf quirks. uvarnumber_T un = 0; +#define PARSE_NUMBER(base, cond, conv) \ + do { \ + while (!STRING_ENDED(ptr) && (cond)) { \ + /* avoid ubsan error for overflow */ \ + if (un < UVARNUMBER_MAX / base) { \ + un = base * un + (uvarnumber_T)(conv); \ + } else { \ + un = UVARNUMBER_MAX; \ + } \ + ptr++; \ + } \ + } while (0) if (pre == 'B' || pre == 'b' || what == (STR2NR_BIN|STR2NR_FORCE)) { - // bin - while (!STRING_ENDED(ptr) && '0' <= *ptr && *ptr <= '1') { - // avoid ubsan error for overflow - if (un < UVARNUMBER_MAX / 2) { - un = 2 * un + (uvarnumber_T)(*ptr - '0'); - } else { - un = UVARNUMBER_MAX; - } - ptr++; - } + // Binary number. + PARSE_NUMBER(2, (*ptr == '0' || *ptr == '1'), (*ptr - '0')); } else if (pre == '0' || what == (STR2NR_OCT|STR2NR_FORCE)) { - // octal - while (!STRING_ENDED(ptr) && '0' <= *ptr && *ptr <= '7') { - // avoid ubsan error for overflow - if (un < UVARNUMBER_MAX / 8) { - un = 8 * un + (uvarnumber_T)(*ptr - '0'); - } else { - un = UVARNUMBER_MAX; - } - ptr++; - } + // Octal number. + PARSE_NUMBER(8, ('0' <= *ptr && *ptr <= '7'), (*ptr - '0')); } else if (pre == 'X' || pre == 'x' || what == (STR2NR_HEX|STR2NR_FORCE)) { - // hex - while (!STRING_ENDED(ptr) && ascii_isxdigit(*ptr)) { - // avoid ubsan error for overflow - if (un < UVARNUMBER_MAX / 16) { - un = 16 * un + (uvarnumber_T)hex2nr(*ptr); - } else { - un = UVARNUMBER_MAX; - } - ptr++; - } + // Hexadecimal number. + PARSE_NUMBER(16, (ascii_isxdigit(*ptr)), (hex2nr(*ptr))); } else { - // decimal - while (!STRING_ENDED(ptr) && ascii_isdigit(*ptr)) { - // avoid ubsan error for overflow - if (un < UVARNUMBER_MAX / 10) { - un = 10 * un + (uvarnumber_T)(*ptr - '0'); - } else { - un = UVARNUMBER_MAX; - } - ptr++; - } + // Decimal number. + PARSE_NUMBER(10, (ascii_isdigit(*ptr)), (*ptr - '0')); } +#undef PARSE_NUMBER if (prep != NULL) { *prep = pre; -- cgit From b935a12dab17c3887db9c5fd7c90b34b2c51170f Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 29 Oct 2017 16:32:13 +0300 Subject: ex_getln: Make use of new parser to color expressions Retires g:Nvim_color_expr callback. --- src/nvim/ex_getln.c | 67 ++++++++++++++++++++++++--- src/nvim/mbyte.c | 4 +- src/nvim/mbyte.h | 6 +++ src/nvim/viml/parser/parser.c | 13 ++++++ src/nvim/viml/parser/parser.h | 55 ++++++++++++++++++++++ test/functional/ui/cmdline_highlight_spec.lua | 26 +++++++---- 6 files changed, 152 insertions(+), 19 deletions(-) create mode 100644 src/nvim/viml/parser/parser.c diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 54e5bcb9ff..386e9e81aa 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -66,6 +66,8 @@ #include "nvim/lib/kvec.h" #include "nvim/api/private/helpers.h" #include "nvim/highlight_defs.h" +#include "nvim/viml/parser/parser.h" +#include "nvim/viml/parser/expressions.h" /* * Variables shared between getcmdline(), redrawcmdline() and others. @@ -2341,6 +2343,62 @@ void free_cmdline_buf(void) enum { MAX_CB_ERRORS = 1 }; +/// Color expression cmdline using built-in expressions parser +/// +/// @param[in] colored_ccline Command-line to color. +/// @param[out] ret_ccline_colors What should be colored. +/// +/// Always colors the whole cmdline. +static void color_expr_cmdline(const CmdlineInfo *const colored_ccline, + ColoredCmdline *const ret_ccline_colors) + FUNC_ATTR_NONNULL_ALL +{ + ParserLine plines[] = { + { + .data = (const char *)colored_ccline->cmdbuff, + .size = STRLEN(colored_ccline->cmdbuff), + .allocated = false, + }, + { NULL, 0, false }, + }; + ParserLine *plines_p = plines; + ParserHighlight colors; + kvi_init(colors); + ParserState pstate; + viml_parser_init( + &pstate, parser_simple_get_line, &plines_p, &colors); + ExprAST east = viml_pexpr_parse(&pstate, kExprFlagsDisallowEOC); + viml_pexpr_free_ast(east); + viml_parser_destroy(&pstate); + kv_resize(ret_ccline_colors->colors, kv_size(colors)); + size_t prev_end = 0; + for (size_t i = 0 ; i < kv_size(colors) ; i++) { + const ParserHighlightChunk chunk = kv_A(colors, i); + if (chunk.start.col != prev_end) { + kv_push(ret_ccline_colors->colors, ((CmdlineColorChunk) { + .start = prev_end, + .end = chunk.start.col, + .attr = 0, + })); + } + const int id = syn_name2id((const char_u *)chunk.group); + const int attr = (id == 0 ? 0 : syn_id2attr(id)); + kv_push(ret_ccline_colors->colors, ((CmdlineColorChunk) { + .start = chunk.start.col, + .end = chunk.end_col, + .attr = attr, + })); + prev_end = chunk.end_col; + } + if (prev_end < (size_t)colored_ccline->cmdlen) { + kv_push(ret_ccline_colors->colors, ((CmdlineColorChunk) { + .start = prev_end, + .end = (size_t)colored_ccline->cmdlen, + .attr = 0, + })); + } +} + /// Color command-line /// /// Should use built-in command parser or user-specified one. Currently only the @@ -2422,13 +2480,8 @@ static bool color_cmdline(const CmdlineInfo *const colored_ccline, tl_ret = try_leave(&tstate, &err); can_free_cb = true; } else if (colored_ccline->cmdfirstc == '=') { - try_enter(&tstate); - err_errmsg = N_( - "E5409: Unable to get g:Nvim_color_expr callback: %s"); - dgc_ret = tv_dict_get_callback(&globvardict, S_LEN("Nvim_color_expr"), - &color_cb); - tl_ret = try_leave(&tstate, &err); - can_free_cb = true; + color_expr_cmdline(colored_ccline, ret_ccline_colors); + can_free_cb = false; } if (!tl_ret || !dgc_ret) { goto color_cmdline_error; diff --git a/src/nvim/mbyte.c b/src/nvim/mbyte.c index f65d7a6b13..843007b97b 100644 --- a/src/nvim/mbyte.c +++ b/src/nvim/mbyte.c @@ -2288,9 +2288,7 @@ int convert_setup_ext(vimconv_T *vcp, char_u *from, bool from_unicode_is_utf8, if (vcp->vc_type == CONV_ICONV && vcp->vc_fd != (iconv_t)-1) iconv_close(vcp->vc_fd); # endif - vcp->vc_type = CONV_NONE; - vcp->vc_factor = 1; - vcp->vc_fail = false; + *vcp = (vimconv_T)MBYTE_NONE_CONV; /* No conversion when one of the names is empty or they are equal. */ if (from == NULL || *from == NUL || to == NULL || *to == NUL diff --git a/src/nvim/mbyte.h b/src/nvim/mbyte.h index fce600d0a9..a5ce1b0a15 100644 --- a/src/nvim/mbyte.h +++ b/src/nvim/mbyte.h @@ -60,6 +60,12 @@ typedef enum { CONV_ICONV = 5, } ConvFlags; +#define MBYTE_NONE_CONV { \ + .vc_type = CONV_NONE, \ + .vc_factor = 1, \ + .vc_fail = false, \ +} + /// Structure used for string conversions typedef struct { int vc_type; ///< Zero or more ConvFlags. diff --git a/src/nvim/viml/parser/parser.c b/src/nvim/viml/parser/parser.c new file mode 100644 index 0000000000..08d8846018 --- /dev/null +++ b/src/nvim/viml/parser/parser.c @@ -0,0 +1,13 @@ +#include "nvim/viml/parser/parser.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "viml/parser/parser.c.generated.h" +#endif + + +void parser_simple_get_line(void *cookie, ParserLine *ret_pline) +{ + ParserLine **plines_p = (ParserLine **)cookie; + *ret_pline = **plines_p; + (*plines_p)++; +} diff --git a/src/nvim/viml/parser/parser.h b/src/nvim/viml/parser/parser.h index fbc5ba5f07..7ac49709d8 100644 --- a/src/nvim/viml/parser/parser.h +++ b/src/nvim/viml/parser/parser.h @@ -8,6 +8,7 @@ #include "nvim/lib/kvec.h" #include "nvim/func_attr.h" #include "nvim/mbyte.h" +#include "nvim/memory.h" /// One parsed line typedef struct { @@ -80,6 +81,56 @@ typedef struct { bool can_continuate; } ParserState; +static inline void viml_parser_init( + ParserState *const ret_pstate, + const ParserLineGetter get_line, void *const cookie, + ParserHighlight *const colors) + REAL_FATTR_ALWAYS_INLINE REAL_FATTR_NONNULL_ARG(1, 2); + +/// Initialize a new parser state instance +/// +/// @param[out] ret_pstate Parser state to initialize. +/// @param[in] get_line Line getter function. +/// @param[in] cookie Argument for the get_line function. +/// @param[in] colors Where to save highlighting. May be NULL if it is not +/// needed. +static inline void viml_parser_init( + ParserState *const ret_pstate, + const ParserLineGetter get_line, void *const cookie, + ParserHighlight *const colors) +{ + *ret_pstate = (ParserState) { + .reader = { + .get_line = get_line, + .cookie = cookie, + .conv = MBYTE_NONE_CONV, + }, + .pos = { 0, 0 }, + .colors = colors, + .can_continuate = false, + }; + kvi_init(ret_pstate->reader.lines); + kvi_init(ret_pstate->stack); +} + +static inline void viml_parser_destroy(ParserState *const pstate) + REAL_FATTR_NONNULL_ALL REAL_FATTR_ALWAYS_INLINE; + +/// Free all memory allocated by the parser on heap +/// +/// @param pstate Parser state to free. +static inline void viml_parser_destroy(ParserState *const pstate) +{ + for (size_t i = 0; i < kv_size(pstate->reader.lines); i++) { + ParserLine pline = kv_A(pstate->reader.lines, i); + if (pline.allocated) { + xfree((void *)pline.data); + } + } + kvi_destroy(pstate->reader.lines); + kvi_destroy(pstate->stack); +} + static inline void viml_preader_get_line(ParserInputReader *const preader, ParserLine *const ret_pline) REAL_FATTR_NONNULL_ALL; @@ -186,4 +237,8 @@ static inline void viml_parser_highlight(ParserState *const pstate, })); } +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "viml/parser/parser.h.generated.h" +#endif + #endif // NVIM_VIML_PARSER_PARSER_H diff --git a/test/functional/ui/cmdline_highlight_spec.lua b/test/functional/ui/cmdline_highlight_spec.lua index d87ce72599..60a4a815e7 100644 --- a/test/functional/ui/cmdline_highlight_spec.lua +++ b/test/functional/ui/cmdline_highlight_spec.lua @@ -144,7 +144,9 @@ before_each(function() EOB={bold = true, foreground = Screen.colors.Blue1}, ERR={foreground = Screen.colors.Grey100, background = Screen.colors.Red}, SK={foreground = Screen.colors.Blue}, - PE={bold = true, foreground = Screen.colors.SeaGreen4} + PE={bold = true, foreground = Screen.colors.SeaGreen4}, + NUM={foreground = Screen.colors.Blue2}, + NPAR={foreground = Screen.colors.Yellow}, }) end) @@ -863,7 +865,10 @@ describe('Ex commands coloring support', function() end) describe('Expressions coloring support', function() it('works', function() - meths.set_var('Nvim_color_expr', 'RainBowParens') + meths.command('hi clear NVimNumber') + meths.command('hi clear NVimNestingParenthesis') + meths.command('hi NVimNumber guifg=Blue2') + meths.command('hi NVimNestingParenthesis guifg=Yellow') feed(':echo =(((1)))') screen:expect([[ | @@ -873,21 +878,24 @@ describe('Expressions coloring support', function() {EOB:~ }| {EOB:~ }| {EOB:~ }| - ={RBP1:(}{RBP2:(}{RBP3:(}1{RBP3:)}{RBP2:)}{RBP1:)}^ | + ={NPAR:(((}{NUM:1}{NPAR:)))}^ | ]]) end) - it('errors out when failing to get callback', function() + it('does not use Nvim_color_expr', function() meths.set_var('Nvim_color_expr', 42) + -- Used to error out due to failing to get callback. + meths.command('hi clear NVimNumber') + meths.command('hi NVimNumber guifg=Blue2') feed(':=1') screen:expect([[ + | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| {EOB:~ }| {EOB:~ }| {EOB:~ }| - = | - {ERR:E5409: Unable to get g:Nvim_color_expr c}| - {ERR:allback: Vim:E6000: Argument is not a fu}| - {ERR:nction or function name} | - =1^ | + ={NUM:1}^ | ]]) end) end) -- cgit From 1be29dc5acc071591bd776890fc3b61aba1488f9 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 29 Oct 2017 16:54:50 +0300 Subject: gen_declarations: Do not generate line numbers by default --- src/nvim/generators/gen_declarations.lua | 43 ++++++++++++++++++++++++++++---- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/src/nvim/generators/gen_declarations.lua b/src/nvim/generators/gen_declarations.lua index e999e53e4a..0c73376ba0 100755 --- a/src/nvim/generators/gen_declarations.lua +++ b/src/nvim/generators/gen_declarations.lua @@ -164,9 +164,40 @@ local pattern = concat( ) if fname == '--help' then - print'Usage:' - print() - print' gendeclarations.lua definitions.c static.h non-static.h preprocessor.i' + print([[ +Usage: + + gendeclarations.lua definitions.c static.h non-static.h definitions.i + +Generates declarations for a C file defitions.c, putting declarations for +static functions into static.h and declarations for non-static functions into +non-static.h. File `definitions.i' should contain an already preprocessed +version of defintions.c and it is the only one which is actually parsed, +definitions.c is needed only to determine functions from which file out of all +functions found in definitions.i are needed. + +Additionally uses the following environment variables: + + NVIM_GEN_DECLARATIONS_LINE_NUMBERS: + If set to 1 then all generated declarations receive a comment with file + name and line number after the declaration. This may be useful for + debugging gen_declarations script, but not much beyound that with + configured development environment (i.e. with ctags/cscope/finding + definitions with clang/etc). + + WARNING: setting this to 1 will cause extensive rebuilds: declarations + generator script will not regenerate non-static.h file if its + contents did not change, but including line numbers will make + contents actually change. + + With contents changed timestamp of the file is regenerated even + when no real changes were made (e.g. a few lines were added to + a function which is not at the bottom of the file). + + With changed timestamp build system will assume that header + changed, triggering rebuilds of all C files which depend on the + "changed" header. +]]) os.exit() end @@ -249,8 +280,10 @@ while init ~= nil do declaration = declaration:gsub(' $', '') declaration = declaration:gsub('^ ', '') declaration = declaration .. ';' - declaration = declaration .. (' // %s/%s:%u'):format( - curdir, curfile, declline) + if os.getenv('NVIM_GEN_DECLARATIONS_LINE_NUMBERS') == '1' then + declaration = declaration .. (' // %s/%s:%u'):format( + curdir, curfile, declline) + end declaration = declaration .. '\n' if declaration:sub(1, 6) == 'static' then static = static .. declaration -- cgit From 22d161a5dd1c519f998916f45d61be92662fbb44 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 29 Oct 2017 20:11:44 +0300 Subject: api/vim: Add nvim_parse_expression function --- src/nvim/api/vim.c | 66 ++++++++++++++++++++++++++++++++++++++ src/nvim/viml/parser/expressions.c | 4 +-- src/nvim/viml/parser/expressions.h | 3 ++ 3 files changed, 70 insertions(+), 3 deletions(-) diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 98f4410347..9daa2fb398 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -886,6 +886,72 @@ theend: return rv; } +/// Parse a VimL expression +/// +/// @param[in] expr Expression to parse. Is always treated as a single line. +/// @param[in] flags Flags: "m" if multiple expressions in a row are allowed +/// (only the first one will be parsed), "E" if EOC tokens +/// are not allowed (determines whether they will stop +/// parsing process or be recognized as an operator/space, +/// though also yielding an error). +/// +/// Use only "m" to parse like for "=", only "E" to +/// parse like for ":echo", empty string for ":let". +/// +/// @return AST: top-level dectionary holds keys +/// +/// "error": Dictionary with error, present only if parser saw some +/// error. Contains the following keys: +/// +/// "message": String, error message in printf format, translated. +/// Must contain exactly one "%.*s". +/// "arg": String, error message argument. +/// +/// "ast": actual AST, a dictionary with the following keys: +/// +/// "type": node type, one of the value names from ExprASTNodeType +/// stringified without "kExprNode" prefix. +/// "start": a pair [line, column] describing where node is “started” +/// where "line" is always 0 (will not be 0 if you will be +/// using nvim_parse_viml() on e.g. ":let", but that is not +/// present yet). Both elements are Integers. +/// "len": “length” of the node. This and "start" are there for +/// debugging purposes primary (debugging parser and providing +/// debug information). +/// "children": a list of nodes described in top/"ast". There always +/// is zero, one or two children, key will contain an +/// empty array if node can have children, but has no and +/// will not be present at all if node can’t have any +/// children. Maximum number of children may be found in +/// node_maxchildren array. +/// +/// Local values (present only for certain nodes): +/// +/// "scope": a single Integer, specifies scope for "Option" and +/// "PlainIdentifier" nodes. For "Option" it is one of +/// ExprOptScope values, for "PlainIdentifier" it is one of +/// ExprVarScope values. +/// "ident": identifier (without scope, if any), present for "Option", +/// "PlainIdentifier", "PlainKey" and "Environment" nodes. +/// "name": Integer, register name (one character) or -1. Only present +/// for "Register" nodes. +/// "cmp_type": String, comparison type, one of the value names from +/// ExprComparisonType, stringified without "kExprCmp" +/// prefix. Only present for "Comparison" nodes. +/// "ccs_strategy": String, case comparison strategy, one of the +/// value names from ExprCaseCompareStrategy, +/// stringified without "kCCStrategy" prefix. Only +/// present for "Comparison" nodes. +/// "ivalue": Integer, integer value for "Integer" nodes. +/// "fvalue": Float, floating-point value for "Float" nodes. +/// "svalue": String, value for "SingleQuotedString" and +/// "DoubleQuotedString" nodes. +Dictionary nvim_parse_expression(String expr, String flags, Error *err) + FUNC_API_SINCE(4) +{ + return (Dictionary)ARRAY_DICT_INIT; +} + /// Writes a message to vim output or error buffer. The string is split /// and flushed after each newline. Incomplete lines are kept for writing diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index b413d56592..4e8a9b8523 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -849,8 +849,7 @@ static inline void viml_pexpr_debug_print_token( viml_pexpr_debug_print_token(pstate, tkn) #endif -#ifndef NDEBUG -static const uint8_t node_maxchildren[] = { +const uint8_t node_maxchildren[] = { [kExprNodeMissing] = 0, [kExprNodeOpMissing] = 2, [kExprNodeTernary] = 2, @@ -890,7 +889,6 @@ static const uint8_t node_maxchildren[] = { [kExprNodeOption] = 0, [kExprNodeEnvironment] = 0, }; -#endif /// Free memory occupied by AST /// diff --git a/src/nvim/viml/parser/expressions.h b/src/nvim/viml/parser/expressions.h index d783518b3a..d00d4855f3 100644 --- a/src/nvim/viml/parser/expressions.h +++ b/src/nvim/viml/parser/expressions.h @@ -338,6 +338,9 @@ typedef struct { ExprASTNode *root; } ExprAST; +/// Array mapping ExprASTNodeType to maximum amount of children node may have +extern const uint8_t node_maxchildren[]; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "viml/parser/expressions.h.generated.h" #endif -- cgit From 748f3ad5bbf9706dddddeea5df693221d6ae5e94 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 29 Oct 2017 21:30:06 +0300 Subject: syntax,viml/expressions/parser: Create defaults for expr highlighting --- src/nvim/ex_docmd.c | 5 +- src/nvim/syntax.c | 599 ++++++++++++++++++----------- src/nvim/viml/parser/expressions.c | 115 +----- test/unit/viml/expressions/parser_spec.lua | 530 +++++++++++++------------ 4 files changed, 667 insertions(+), 582 deletions(-) diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 3130747e08..664ab69792 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -5911,9 +5911,10 @@ static void ex_colorscheme(exarg_T *eap) static void ex_highlight(exarg_T *eap) { - if (*eap->arg == NUL && eap->cmd[2] == '!') + if (*eap->arg == NUL && eap->cmd[2] == '!') { MSG(_("Greetings, Vim user!")); - do_highlight(eap->arg, eap->forceit, FALSE); + } + do_highlight((const char *)eap->arg, eap->forceit, false); } diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index 70bda42d83..b418df77a4 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -5929,8 +5929,7 @@ static void syntime_report(void) // // When making changes here, also change runtime/colors/default.vim! -static char *highlight_init_both[] = -{ +static const char *highlight_init_both[] = { "Conceal ctermbg=DarkGrey ctermfg=LightGrey guibg=DarkGrey guifg=LightGrey", "Cursor guibg=fg guifg=bg", "lCursor guibg=fg guifg=bg", @@ -5954,8 +5953,7 @@ static char *highlight_init_both[] = NULL }; -static char *highlight_init_light[] = -{ +static const char *highlight_init_light[] = { "ColorColumn ctermbg=LightRed guibg=LightRed", "CursorColumn ctermbg=LightGrey guibg=Grey90", "CursorLine cterm=underline guibg=Grey90", @@ -5988,8 +5986,7 @@ static char *highlight_init_light[] = NULL }; -static char *highlight_init_dark[] = -{ +static const char *highlight_init_dark[] = { "ColorColumn ctermbg=DarkRed guibg=DarkRed", "CursorColumn ctermbg=DarkGrey guibg=Grey40", "CursorLine cterm=underline guibg=Grey40", @@ -6022,6 +6019,157 @@ static char *highlight_init_dark[] = NULL }; +static const char *highlight_init_cmdline[] = { + // NVimInternalError should appear only when highlighter has a bug. + "NVimInternalError ctermfg=Red ctermbg=Red guifg=Red guibg=Red", + + // Highlight groups (links) used by parser: + + "default link NVimOperator Operator", + + "default link NVimUnaryOperator NVimOperator", + "default link NVimUnaryPlus NVimUnaryOperator", + + "default link NVimBinaryOperator NVimOperator", + "default link NVimComparison NVimBinaryOperator", + "default link NVimComparisonModifier NVimComparison", + "default link NVimBinaryPlus NVimBinaryOperator", + "default link NVimConcat NVimBinaryOperator", + "default link NVimConcatOrSubscript NVimConcat", + + "default link NVimTernary NVimOperator", + "default link NVimTernaryColon NVimTernary", + + "default link NVimParenthesis Delimiter", + "default link NVimLambda NVimParenthesis", + "default link NVimNestingParenthesis NVimParenthesis", + "default link NVimCallingParenthesis NVimParenthesis", + + "default link NVimSubscript NVimParenthesis", + "default link NVimSubscriptBracket NVimSubscript", + "default link NVimSubscriptColon NVimSubscript", + "default link NVimSubscriptColon NVimSubscript", + "default link NVimCurly NVimSubscript", + + "default link NVimContainer NVimParenthesis", + "default link NVimDict NVimContainer", + "default link NVimList NVimContainer", + + "default link NVimIdentifier Identifier", + "default link NVimIdentifierScope NVimIdentifier", + "default link NVimIdentifierScopeDelimiter NVimIdentifier", + "default link NVimIdentifierName NVimIdentifier", + "default link NVimIdentifierKey NVimIdentifier", + + "default link NVimColon Delimiter", + "default link NVimComma Delimiter", + "default link NVimArrow Delimiter", + + "default link NVimRegister SpecialChar", + "default link NVimNumber Number", + "default link NVimFloat NVimNumber", + "default link NVimNumberPrefix SpecialChar", + + "default link NVimString String", + "default link NVimStringBody NVimString", + "default link NVimStringQuote NVimString", + "default link NVimStringSpecial SpecialChar", + + "default link NVimSingleQuote NVimStringQuote", + "default link NVimSingleQuotedBody NVimStringBody", + "default link NVimSingleQuotedQuote NVimStringSpecial", + + "default link NVimDoubleQuote NVimStringQuote", + "default link NVimDoubleQuotedBody NVimStringBody", + "default link NVimDoubleQuotedEscape NVimStringSpecial", + // Not actually invalid, but we highlight user that he is doing something + // wrong. + "default link NVimDoubleQuotedUnknownEscape NVimInvalidValue", + + "default link NVimFigureBrace NVimInternalError", + "default link NVimSingleQuotedUnknownEscape NVimInternalError", + + // NVimInvalid groups: + + "default link NVimInvalidSingleQuotedUnknownEscape NVimInternalError", + + "default link NVimInvalid Error", + + "default link NVimInvalidOperator NVimInvalid", + + "default link NVimInvalidUnaryOperator NVimInvalidOperator", + "default link NVimInvalidUnaryPlus NVimInvalidUnaryOperator", + + "default link NVimInvalidBinaryOperator NVimInvalidOperator", + "default link NVimInvalidComparison NVimInvalidBinaryOperator", + "default link NVimInvalidComparisonModifier NVimInvalidComparison", + "default link NVimInvalidBinaryPlus NVimInvalidBinaryOperator", + "default link NVimInvalidConcat NVimInvalidBinaryOperator", + "default link NVimInvalidConcatOrSubscript NVimInvalidConcat", + + "default link NVimInvalidTernary NVimInvalidOperator", + "default link NVimInvalidTernaryColon NVimInvalidTernary", + + "default link NVimInvalidDelimiter NVimInvalid", + + "default link NVimInvalidParenthesis NVimInvalidDelimiter", + "default link NVimInvalidLambda NVimInvalidParenthesis", + "default link NVimInvalidNestingParenthesis NVimInvalidParenthesis", + "default link NVimInvalidCallingParenthesis NVimInvalidParenthesis", + + "default link NVimInvalidSubscript NVimInvalidParenthesis", + "default link NVimInvalidSubscriptBracket NVimInvalidSubscript", + "default link NVimInvalidSubscriptColon NVimInvalidSubscript", + "default link NVimInvalidSubscriptColon NVimInvalidSubscript", + "default link NVimInvalidCurly NVimInvalidSubscript", + + "default link NVimInvalidContainer NVimInvalidParenthesis", + "default link NVimInvalidDict NVimInvalidContainer", + "default link NVimInvalidList NVimInvalidContainer", + + "default link NVimInvalidIdentifier Identifier", + "default link NVimInvalidIdentifierScope NVimIdentifier", + "default link NVimInvalidIdentifierScopeDelimiter NVimIdentifier", + "default link NVimInvalidIdentifierName NVimIdentifier", + "default link NVimInvalidIdentifierKey NVimIdentifier", + + "default link NVimInvalidColon NVimInvalidDelimiter", + "default link NVimInvalidComma NVimInvalidDelimiter", + "default link NVimInvalidArrow NVimInvalidDelimiter", + + "default link NVimInvalidValue NVimInvalid", + + "default link NVimInvalidRegister NVimInvalidValue", + "default link NVimInvalidNumber NVimInvalidValue", + "default link NVimInvalidFloat NVimInvalidNumber", + "default link NVimInvalidNumberPrefix NVimInvalidNumber", + + // Invalid string bodies and specials are still highlighted as valid ones to + // minimize the red area. + "default link NVimInvalidString NVimInvalidValue", + "default link NVimInvalidStringBody NVimString", + "default link NVimInvalidStringQuote NVimInvalidString", + "default link NVimInvalidStringSpecial NVimStringSpecial", + + "default link NVimInvalidSingleQuote NVimInvalidStringQuote", + "default link NVimInvalidSingleQuotedBody NVimInvalidStringBody", + "default link NVimInvalidSingleQuotedQuote NVimInvalidStringSpecial", + + "default link NVimInvalidDoubleQuote NVimInvalidStringQuote", + "default link NVimInvalidDoubleQuotedBody NVimInvalidStringBody", + "default link NVimInvalidDoubleQuotedEscape NVimInvalidStringSpecial", + "default link NVimInvalidDoubleQuotedUnknownEscape NVimInvalidValue", + + "default link NVimInvalidFigureBrace NVimInternalError", +}; + +/// Create default links for NVim* highlight groups used for cmdline coloring +void syn_init_cmdline_highlight(bool reset, bool init) +{ + for (size_t i = 0 ; i < ARRAY_SIZE(highlight_init_cmdline) ; i++) { + do_highlight(highlight_init_cmdline[i], reset, init); + } +} /// Load colors from a file if "g:colors_name" is set, otherwise load builtin /// colors @@ -6032,7 +6180,6 @@ void init_highlight(int both, int reset) { int i; - char **pp; static int had_both = FALSE; // Try finding the color scheme file. Used when a color file was loaded @@ -6054,9 +6201,9 @@ init_highlight(int both, int reset) */ if (both) { had_both = TRUE; - pp = highlight_init_both; + const char *const *const pp = highlight_init_both; for (i = 0; pp[i] != NULL; i++) { - do_highlight((char_u *)pp[i], reset, true); + do_highlight(pp[i], reset, true); } } else if (!had_both) { // Don't do anything before the call with both == TRUE from main(). @@ -6065,10 +6212,11 @@ init_highlight(int both, int reset) return; } - pp = (*p_bg == 'l') ? highlight_init_light : highlight_init_dark; - + const char *const *const pp = ((*p_bg == 'l') + ? highlight_init_light + : highlight_init_dark); for (i = 0; pp[i] != NULL; i++) { - do_highlight((char_u *)pp[i], reset, true); + do_highlight(pp[i], reset, true); } /* Reverse looks ugly, but grey may not work for 8 colors. Thus let it @@ -6078,15 +6226,13 @@ init_highlight(int both, int reset) * Clear the attributes, needed when changing the t_Co value. */ if (t_colors > 8) { do_highlight( - (char_u *)(*p_bg == 'l' - ? "Visual cterm=NONE ctermbg=LightGrey" - : "Visual cterm=NONE ctermbg=DarkGrey"), false, - true); + (*p_bg == 'l' + ? "Visual cterm=NONE ctermbg=LightGrey" + : "Visual cterm=NONE ctermbg=DarkGrey"), false, true); } else { - do_highlight((char_u *)"Visual cterm=reverse ctermbg=NONE", - FALSE, TRUE); + do_highlight("Visual cterm=reverse ctermbg=NONE", false, true); if (*p_bg == 'l') - do_highlight((char_u *)"Search ctermfg=black", FALSE, TRUE); + do_highlight("Search ctermfg=black", false, true); } /* @@ -6102,6 +6248,10 @@ init_highlight(int both, int reset) (void)source_runtime((char_u *)"syntax/syncolor.vim", DIP_ALL); recursive--; } + // Without syncolor.vim it is going to screw everything over by defining + // cleared highlight groups by creating links to non-existent groups. This + // effectively prevents ":highlight default" from working properly. + syn_init_cmdline_highlight(reset, true); } } @@ -6137,17 +6287,22 @@ int load_colors(char_u *name) } -/// Handle the ":highlight .." command. -/// When using ":hi clear" this is called recursively for each group with -/// "forceit" and "init" both TRUE. -/// @param init TRUE when called for initializing -void -do_highlight(char_u *line, int forceit, int init) { - char_u *name_end; - char_u *linep; - char_u *key_start; - char_u *arg_start; - char_u *key = NULL, *arg = NULL; +/// Handle ":highlight" command +/// +/// When using ":highlight clear" this is called recursively for each group with +/// forceit and init being both true. +/// +/// @param[in] line Command arguments. +/// @param[in] forceit True when bang is given, allows to link group even if +/// it has its own settings. +/// @param[in] init True when initializing. +void do_highlight(const char *line, const bool forceit, const bool init) + FUNC_ATTR_NONNULL_ALL +{ + const char *name_end; + const char *linep; + const char *key_start; + const char *arg_start; long i; int off; int len; @@ -6161,94 +6316,87 @@ do_highlight(char_u *line, int forceit, int init) { int color; bool is_normal_group = false; // "Normal" group - /* - * If no argument, list current highlighting. - */ - if (ends_excmd(*line)) { + // If no argument, list current highlighting. + if (ends_excmd((uint8_t)(*line))) { for (int i = 1; i <= highlight_ga.ga_len && !got_int; i++) { - // todo(vim): only call when the group has attributes set + // TODO(brammool): only call when the group has attributes set highlight_list_one(i); } return; } - /* - * Isolate the name. - */ - name_end = skiptowhite(line); - linep = skipwhite(name_end); + // Isolate the name. + name_end = (const char *)skiptowhite((const char_u *)line); + linep = (const char *)skipwhite((const char_u *)name_end); - /* - * Check for "default" argument. - */ - if (STRNCMP(line, "default", name_end - line) == 0) { - dodefault = TRUE; + // Check for "default" argument. + if (strncmp(line, "default", name_end - line) == 0) { + dodefault = true; line = linep; - name_end = skiptowhite(line); - linep = skipwhite(name_end); + name_end = (const char *)skiptowhite((const char_u *)line); + linep = (const char *)skipwhite((const char_u *)name_end); } - /* - * Check for "clear" or "link" argument. - */ - if (STRNCMP(line, "clear", name_end - line) == 0) - doclear = TRUE; - if (STRNCMP(line, "link", name_end - line) == 0) - dolink = TRUE; + // Check for "clear" or "link" argument. + if (strncmp(line, "clear", name_end - line) == 0) { + doclear = true; + } else if (strncmp(line, "link", name_end - line) == 0) { + dolink = true; + } - /* - * ":highlight {group-name}": list highlighting for one group. - */ - if (!doclear && !dolink && ends_excmd(*linep)) { - id = syn_namen2id(line, (int)(name_end - line)); - if (id == 0) - EMSG2(_("E411: highlight group not found: %s"), line); - else + // ":highlight {group-name}": list highlighting for one group. + if (!doclear && !dolink && ends_excmd((uint8_t)(*linep))) { + id = syn_namen2id((const char_u *)line, (int)(name_end - line)); + if (id == 0) { + emsgf(_("E411: highlight group not found: %s"), line); + } else { highlight_list_one(id); + } return; } - /* - * Handle ":highlight link {from} {to}" command. - */ + // Handle ":highlight link {from} {to}" command. if (dolink) { - char_u *from_start = linep; - char_u *from_end; - char_u *to_start; - char_u *to_end; + const char *from_start = linep; + const char *from_end; + const char *to_start; + const char *to_end; int from_id; int to_id; - from_end = skiptowhite(from_start); - to_start = skipwhite(from_end); - to_end = skiptowhite(to_start); + from_end = (const char *)skiptowhite((const char_u *)from_start); + to_start = (const char *)skipwhite((const char_u *)from_end); + to_end = (const char *)skiptowhite((const char_u *)to_start); - if (ends_excmd(*from_start) || ends_excmd(*to_start)) { - EMSG2(_("E412: Not enough arguments: \":highlight link %s\""), - from_start); + if (ends_excmd((uint8_t)(*from_start)) + || ends_excmd((uint8_t)(*to_start))) { + emsgf(_("E412: Not enough arguments: \":highlight link %s\""), + from_start); return; } - if (!ends_excmd(*skipwhite(to_end))) { - EMSG2(_("E413: Too many arguments: \":highlight link %s\""), from_start); + if (!ends_excmd(*skipwhite((const char_u *)to_end))) { + emsgf(_("E413: Too many arguments: \":highlight link %s\""), from_start); return; } - from_id = syn_check_group(from_start, (int)(from_end - from_start)); - if (STRNCMP(to_start, "NONE", 4) == 0) + from_id = syn_check_group((const char_u *)from_start, + (int)(from_end - from_start)); + if (strncmp(to_start, "NONE", 4) == 0) { to_id = 0; - else - to_id = syn_check_group(to_start, (int)(to_end - to_start)); + } else { + to_id = syn_check_group((const char_u *)to_start, + (int)(to_end - to_start)); + } if (from_id > 0 && (!init || HL_TABLE()[from_id - 1].sg_set == 0)) { - /* - * Don't allow a link when there already is some highlighting - * for the group, unless '!' is used - */ + // Don't allow a link when there already is some highlighting + // for the group, unless '!' is used if (to_id > 0 && !forceit && !init && hl_has_settings(from_id - 1, dodefault)) { - if (sourcing_name == NULL && !dodefault) + if (sourcing_name == NULL && !dodefault) { EMSG(_("E414: group has settings, highlight link ignored")); + } } else { if (!init) HL_TABLE()[from_id - 1].sg_set |= SG_LINK; @@ -6258,43 +6406,38 @@ do_highlight(char_u *line, int forceit, int init) { } } - /* Only call highlight_changed() once, after sourcing a syntax file */ - need_highlight_changed = TRUE; + // Only call highlight_changed() once, after sourcing a syntax file. + need_highlight_changed = true; return; } if (doclear) { - /* - * ":highlight clear [group]" command. - */ + // ":highlight clear [group]" command. line = linep; - if (ends_excmd(*line)) { + if (ends_excmd((uint8_t)(*line))) { do_unlet(S_LEN("colors_name"), true); restore_cterm_colors(); - /* - * Clear all default highlight groups and load the defaults. - */ + // Clear all default highlight groups and load the defaults. for (int idx = 0; idx < highlight_ga.ga_len; ++idx) { highlight_clear(idx); } - init_highlight(TRUE, TRUE); + init_highlight(true, true); highlight_changed(); redraw_later_clear(); return; } - name_end = skiptowhite(line); - linep = skipwhite(name_end); + name_end = (const char *)skiptowhite((const char_u *)line); + linep = (const char *)skipwhite((const char_u *)name_end); } - /* - * Find the group name in the table. If it does not exist yet, add it. - */ - id = syn_check_group(line, (int)(name_end - line)); - if (id == 0) /* failed (out of memory) */ + // Find the group name in the table. If it does not exist yet, add it. + id = syn_check_group((const char_u *)line, (int)(name_end - line)); + if (id == 0) { // Failed (out of memory). return; - idx = id - 1; /* index is ID minus one */ + } + idx = id - 1; // Index is ID minus one. // Return if "default" was used and the group already has settings if (dodefault && hl_has_settings(idx, true)) { @@ -6303,19 +6446,21 @@ do_highlight(char_u *line, int forceit, int init) { is_normal_group = (STRCMP(HL_TABLE()[idx].sg_name_u, "NORMAL") == 0); - /* Clear the highlighting for ":hi clear {group}" and ":hi clear". */ + // Clear the highlighting for ":hi clear {group}" and ":hi clear". if (doclear || (forceit && init)) { highlight_clear(idx); if (!doclear) HL_TABLE()[idx].sg_set = 0; } + char *key = NULL; + char *arg = NULL; if (!doclear) { - while (!ends_excmd(*linep)) { + while (!ends_excmd((uint8_t)(*linep))) { key_start = linep; if (*linep == '=') { - EMSG2(_("E415: unexpected equal sign: %s"), key_start); - error = TRUE; + emsgf(_("E415: unexpected equal sign: %s"), key_start); + error = true; break; } @@ -6325,61 +6470,58 @@ do_highlight(char_u *line, int forceit, int init) { linep++; } xfree(key); - key = vim_strnsave_up(key_start, (int)(linep - key_start)); - linep = skipwhite(linep); + key = (char *)vim_strnsave_up((const char_u *)key_start, + (int)(linep - key_start)); + linep = (const char *)skipwhite((const char_u *)linep); - if (STRCMP(key, "NONE") == 0) { + if (strcmp(key, "NONE") == 0) { if (!init || HL_TABLE()[idx].sg_set == 0) { - if (!init) + if (!init) { HL_TABLE()[idx].sg_set |= SG_CTERM+SG_GUI; + } highlight_clear(idx); } continue; } - /* - * Check for the equal sign. - */ + // Check for the equal sign. if (*linep != '=') { - EMSG2(_("E416: missing equal sign: %s"), key_start); - error = TRUE; + emsgf(_("E416: missing equal sign: %s"), key_start); + error = true; break; } - ++linep; + linep++; - /* - * Isolate the argument. - */ - linep = skipwhite(linep); - if (*linep == '\'') { /* guifg='color name' */ + // Isolate the argument. + linep = (const char *)skipwhite((const char_u *)linep); + if (*linep == '\'') { // guifg='color name' arg_start = ++linep; - linep = vim_strchr(linep, '\''); + linep = strchr(linep, '\''); if (linep == NULL) { - EMSG2(_(e_invarg2), key_start); - error = TRUE; + emsgf(_(e_invarg2), key_start); + error = true; break; } } else { arg_start = linep; - linep = skiptowhite(linep); + linep = (const char *)skiptowhite((const char_u *)linep); } if (linep == arg_start) { - EMSG2(_("E417: missing argument: %s"), key_start); - error = TRUE; + emsgf(_("E417: missing argument: %s"), key_start); + error = true; break; } xfree(arg); - arg = vim_strnsave(arg_start, (int)(linep - arg_start)); + arg = xstrndup(arg_start, (size_t)(linep - arg_start)); - if (*linep == '\'') - ++linep; + if (*linep == '\'') { + linep++; + } - /* - * Store the argument. - */ - if ( STRCMP(key, "TERM") == 0 - || STRCMP(key, "CTERM") == 0 - || STRCMP(key, "GUI") == 0) { + // Store the argument. + if (strcmp(key, "TERM") == 0 + || strcmp(key, "CTERM") == 0 + || strcmp(key, "GUI") == 0) { attr = 0; off = 0; while (arg[off] != NUL) { @@ -6392,26 +6534,30 @@ do_highlight(char_u *line, int forceit, int init) { } } if (i < 0) { - EMSG2(_("E418: Illegal value: %s"), arg); - error = TRUE; + emsgf(_("E418: Illegal value: %s"), arg); + error = true; break; } - if (arg[off] == ',') /* another one follows */ - ++off; + if (arg[off] == ',') { // Another one follows. + off++; + } } - if (error) + if (error) { break; + } if (*key == 'C') { if (!init || !(HL_TABLE()[idx].sg_set & SG_CTERM)) { - if (!init) + if (!init) { HL_TABLE()[idx].sg_set |= SG_CTERM; + } HL_TABLE()[idx].sg_cterm = attr; HL_TABLE()[idx].sg_cterm_bold = FALSE; } } else if (*key == 'G') { if (!init || !(HL_TABLE()[idx].sg_set & SG_GUI)) { - if (!init) + if (!init) { HL_TABLE()[idx].sg_set |= SG_GUI; + } HL_TABLE()[idx].sg_gui = attr; } } @@ -6430,14 +6576,14 @@ do_highlight(char_u *line, int forceit, int init) { HL_TABLE()[idx].sg_cterm_bold = FALSE; } - if (ascii_isdigit(*arg)) + if (ascii_isdigit(*arg)) { color = atoi((char *)arg); - else if (STRICMP(arg, "fg") == 0) { - if (cterm_normal_fg_color) + } else if (STRICMP(arg, "fg") == 0) { + if (cterm_normal_fg_color) { color = cterm_normal_fg_color - 1; - else { + } else { EMSG(_("E419: FG color unknown")); - error = TRUE; + error = true; break; } } else if (STRICMP(arg, "bg") == 0) { @@ -6445,70 +6591,85 @@ do_highlight(char_u *line, int forceit, int init) { color = cterm_normal_bg_color - 1; else { EMSG(_("E420: BG color unknown")); - error = TRUE; + error = true; break; } } else { - static char *(color_names[28]) = { + static const char *color_names[] = { "Black", "DarkBlue", "DarkGreen", "DarkCyan", "DarkRed", "DarkMagenta", "Brown", "DarkYellow", "Gray", "Grey", "LightGray", "LightGrey", "DarkGray", "DarkGrey", "Blue", "LightBlue", "Green", "LightGreen", "Cyan", "LightCyan", "Red", "LightRed", "Magenta", - "LightMagenta", "Yellow", "LightYellow", "White", "NONE" + "LightMagenta", "Yellow", "LightYellow", "White", + "NONE" + }; + static const int color_numbers_16[] = { + 0, 1, 2, 3, + 4, 5, 6, 6, + 7, 7, + 7, 7, 8, 8, + 9, 9, 10, 10, + 11, 11, 12, 12, 13, + 13, 14, 14, 15, + -1 + }; + // For xterm with 88 colors: + static int color_numbers_88[] = { + 0, 4, 2, 6, + 1, 5, 32, 72, + 84, 84, + 7, 7, 82, 82, + 12, 43, 10, 61, + 14, 63, 9, 74, 13, + 75, 11, 78, 15, + -1 + }; + // For xterm with 256 colors: + static int color_numbers_256[] = { + 0, 4, 2, 6, + 1, 5, 130, 130, + 248, 248, + 7, 7, 242, 242, + 12, 81, 10, 121, + 14, 159, 9, 224, 13, + 225, 11, 229, 15, + -1 }; - static int color_numbers_16[28] = {0, 1, 2, 3, - 4, 5, 6, 6, - 7, 7, - 7, 7, 8, 8, - 9, 9, 10, 10, - 11, 11, 12, 12, 13, - 13, 14, 14, 15, -1}; - /* for xterm with 88 colors... */ - static int color_numbers_88[28] = {0, 4, 2, 6, - 1, 5, 32, 72, - 84, 84, - 7, 7, 82, 82, - 12, 43, 10, 61, - 14, 63, 9, 74, 13, - 75, 11, 78, 15, -1}; - /* for xterm with 256 colors... */ - static int color_numbers_256[28] = {0, 4, 2, 6, - 1, 5, 130, 130, - 248, 248, - 7, 7, 242, 242, - 12, 81, 10, 121, - 14, 159, 9, 224, 13, - 225, 11, 229, 15, -1}; - /* for terminals with less than 16 colors... */ - static int color_numbers_8[28] = {0, 4, 2, 6, - 1, 5, 3, 3, - 7, 7, - 7, 7, 0+8, 0+8, - 4+8, 4+8, 2+8, 2+8, - 6+8, 6+8, 1+8, 1+8, 5+8, - 5+8, 3+8, 3+8, 7+8, -1}; - - /* reduce calls to STRICMP a bit, it can be slow */ + // For terminals with less than 16 colors: + static int color_numbers_8[28] = { + 0, 4, 2, 6, + 1, 5, 3, 3, + 7, 7, + 7, 7, 0+8, 0+8, + 4+8, 4+8, 2+8, 2+8, + 6+8, 6+8, 1+8, 1+8, 5+8, + 5+8, 3+8, 3+8, 7+8, + -1 + }; + + // Reduce calls to STRICMP a bit, it can be slow. off = TOUPPER_ASC(*arg); - for (i = ARRAY_SIZE(color_names); --i >= 0; ) + for (i = ARRAY_SIZE(color_names); --i >= 0; ) { if (off == color_names[i][0] - && STRICMP(arg + 1, color_names[i] + 1) == 0) + && STRICMP(arg + 1, color_names[i] + 1) == 0) { break; + } + } if (i < 0) { - EMSG2(_( + emsgf(_( "E421: Color name or number not recognized: %s"), key_start); - error = TRUE; + error = true; break; } - /* Use the _16 table to check if its a valid color name. */ + // Use the _16 table to check if its a valid color name. color = color_numbers_16[i]; if (color >= 0) { if (t_colors == 8) { - /* t_Co is 8: use the 8 colors table */ + // t_Co is 8: use the 8 colors table. color = color_numbers_8[i]; if (key[5] == 'F') { /* set/reset bold attribute to get light foreground @@ -6532,16 +6693,14 @@ do_highlight(char_u *line, int forceit, int init) { } } } - /* Add one to the argument, to avoid zero. Zero is used for - * "NONE", then "color" is -1. */ + // Add one to the argument, to avoid zero. Zero is used for + // "NONE", then "color" is -1. if (key[5] == 'F') { HL_TABLE()[idx].sg_cterm_fg = color + 1; if (is_normal_group) { cterm_normal_fg_color = color + 1; cterm_normal_fg_bold = (HL_TABLE()[idx].sg_cterm & HL_BOLD); - { - must_redraw = CLEAR; - } + must_redraw = CLEAR; } } else { HL_TABLE()[idx].sg_cterm_bg = color + 1; @@ -6565,15 +6724,15 @@ do_highlight(char_u *line, int forceit, int init) { } } } - } else if (STRCMP(key, "GUIFG") == 0) { + } else if (strcmp(key, "GUIFG") == 0) { if (!init || !(HL_TABLE()[idx].sg_set & SG_GUI)) { if (!init) HL_TABLE()[idx].sg_set |= SG_GUI; xfree(HL_TABLE()[idx].sg_rgb_fg_name); - if (STRCMP(arg, "NONE")) { - HL_TABLE()[idx].sg_rgb_fg_name = (uint8_t *)xstrdup((char *)arg); - HL_TABLE()[idx].sg_rgb_fg = name_to_color(arg); + if (strcmp(arg, "NONE")) { + HL_TABLE()[idx].sg_rgb_fg_name = (char_u *)xstrdup((char *)arg); + HL_TABLE()[idx].sg_rgb_fg = name_to_color((const char_u *)arg); } else { HL_TABLE()[idx].sg_rgb_fg_name = NULL; HL_TABLE()[idx].sg_rgb_fg = -1; @@ -6590,8 +6749,8 @@ do_highlight(char_u *line, int forceit, int init) { xfree(HL_TABLE()[idx].sg_rgb_bg_name); if (STRCMP(arg, "NONE") != 0) { - HL_TABLE()[idx].sg_rgb_bg_name = (uint8_t *)xstrdup((char *)arg); - HL_TABLE()[idx].sg_rgb_bg = name_to_color(arg); + HL_TABLE()[idx].sg_rgb_bg_name = (char_u *)xstrdup((char *)arg); + HL_TABLE()[idx].sg_rgb_bg = name_to_color((const char_u *)arg); } else { HL_TABLE()[idx].sg_rgb_bg_name = NULL; HL_TABLE()[idx].sg_rgb_bg = -1; @@ -6601,15 +6760,15 @@ do_highlight(char_u *line, int forceit, int init) { if (is_normal_group) { normal_bg = HL_TABLE()[idx].sg_rgb_bg; } - } else if (STRCMP(key, "GUISP") == 0) { + } else if (strcmp(key, "GUISP") == 0) { if (!init || !(HL_TABLE()[idx].sg_set & SG_GUI)) { if (!init) HL_TABLE()[idx].sg_set |= SG_GUI; xfree(HL_TABLE()[idx].sg_rgb_sp_name); - if (STRCMP(arg, "NONE") != 0) { - HL_TABLE()[idx].sg_rgb_sp_name = (uint8_t *)xstrdup((char *)arg); - HL_TABLE()[idx].sg_rgb_sp = name_to_color(arg); + if (strcmp(arg, "NONE") != 0) { + HL_TABLE()[idx].sg_rgb_sp_name = (char_u *)xstrdup((char *)arg); + HL_TABLE()[idx].sg_rgb_sp = name_to_color((const char_u *)arg); } else { HL_TABLE()[idx].sg_rgb_sp_name = NULL; HL_TABLE()[idx].sg_rgb_sp = -1; @@ -6619,31 +6778,25 @@ do_highlight(char_u *line, int forceit, int init) { if (is_normal_group) { normal_sp = HL_TABLE()[idx].sg_rgb_sp; } - } else if (STRCMP(key, "START") == 0 || STRCMP(key, "STOP") == 0) { + } else if (strcmp(key, "START") == 0 || strcmp(key, "STOP") == 0) { // Ignored for now } else { - EMSG2(_("E423: Illegal argument: %s"), key_start); - error = TRUE; + emsgf(_("E423: Illegal argument: %s"), key_start); + error = true; break; } - /* - * When highlighting has been given for a group, don't link it. - */ + // When highlighting has been given for a group, don't link it. if (!init || !(HL_TABLE()[idx].sg_set & SG_LINK)) { HL_TABLE()[idx].sg_link = 0; } - /* - * Continue with next argument. - */ - linep = skipwhite(linep); + // Continue with next argument. + linep = (const char *)skipwhite((const char_u *)linep); } } - /* - * If there is an error, and it's a new entry, remove it from the table. - */ + // If there is an error, and it's a new entry, remove it from the table. if (error && idx == highlight_ga.ga_len) { syn_unadd_group(); } else { @@ -7201,7 +7354,7 @@ char_u *syn_id2name(int id) /* * Like syn_name2id(), but take a pointer + length argument. */ -int syn_namen2id(char_u *linep, int len) +int syn_namen2id(const char_u *linep, int len) { char_u *name = vim_strnsave(linep, len); int id = syn_name2id(name); @@ -7217,7 +7370,7 @@ int syn_namen2id(char_u *linep, int len) /// @param len length of \p pp /// /// @return 0 for failure else the id of the group -int syn_check_group(char_u *pp, int len) +int syn_check_group(const char_u *pp, int len) { char_u *name = vim_strnsave(pp, len); int id = syn_name2id(name); @@ -8220,7 +8373,7 @@ color_name_table_T color_name_table[] = { /// /// @param[in] name string value to convert to RGB /// return the hex value or -1 if could not find a correct value -RgbValue name_to_color(const uint8_t *name) +RgbValue name_to_color(const char_u *name) { if (name[0] == '#' && isxdigit(name[1]) && isxdigit(name[2]) diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index 4e8a9b8523..615a59573f 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -993,107 +993,6 @@ void viml_pexpr_free_ast(ExprAST ast) // > ># >? <= <=# <=? // < <# = >=# >=? // is is# is? isnot isnot# isnot? -// -// Used highlighting groups and assumed linkage: -// -// NVimInternalError -> highlight as fg:red/bg:red -// -// NVimInvalid -> Error -// NVimInvalidValue -> NVimInvalid -// NVimInvalidOperator -> NVimInvalid -// NVimInvalidDelimiter -> NVimInvalid -// -// NVimOperator -> Operator -// NVimUnaryOperator -> NVimOperator -// NVimBinaryOperator -> NVimOperator -// -// NVimComparisonOperator -> NVimBinaryOperator -// NVimComparisonOperatorModifier -> NVimComparisonOperator -// -// NVimTernary -> NVimOperator -// NVimTernaryColon -> NVimTernary -// -// NVimParenthesis -> Delimiter -// -// NVimColon -> Delimiter -// NVimComma -> Delimiter -// NVimArrow -> Delimiter -// -// NVimLambda -> Delimiter -// NVimDict -> Delimiter -// NVimCurly -> Delimiter -// -// NVimList -> Delimiter -// NVimSubscript -> Delimiter -// NVimSubscriptColon -> NVimSubscript -// -// NVimIdentifier -> Identifier -// NVimIdentifierScope -> NVimIdentifier -// NVimIdentifierScopeDelimiter -> NVimIdentifier -// -// NVimIdentifierKey -> Identifier -// -// NVimUnaryPlus -> NVimUnaryOperator -// NVimBinaryPlus -> NVimBinaryOperator -// NVimConcat -> NVimBinaryOperator -// NVimConcatOrSubscript -> NVimConcat -// -// NVimRegister -> SpecialChar -// NVimNumber -> Number -// NVimNumberPrefix -> SpecialChar -// NVimFloat -> NVimNumber -// -// NVimNestingParenthesis -> NVimParenthesis -// NVimCallingParenthesis -> NVimParenthesis -// -// NVimString -> String -// NVimStringSpecial -> SpecialChar -// NVimSingleQuote -> NVimString -// NVimSingleQuotedBody -> NVimString -// NVimSingleQuotedQuote -> NVimStringSpecial -// NVimDoubleQuote -> NVimString -// NVimDoubleQuotedBody -> NVimString -// NVimDoubleQuotedEscape -> NVimStringSpecial -// NVimDoubleQuotedUnknownEscape -> NVimInvalid -// -// " Note: NVimDoubleQuotedUnknownEscape is not actually invalid -// -// NVimInvalidComma -> NVimInvalidDelimiter -// NVimInvalidSpacing -> NVimInvalid -// NVimInvalidTernary -> NVimInvalidOperator -// NVimInvalidTernaryColon -> NVimInvalidTernary -// NVimInvalidRegister -> NVimInvalidValue -// NVimInvalidClosingBracket -> NVimInvalidDelimiter -// NVimInvalidSpacing -> NVimInvalid -// NVimInvalidArrow -> NVimInvalidDelimiter -// NVimInvalidLambda -> NVimInvalidDelimiter -// NVimInvalidDict -> NVimInvalidDelimiter -// NVimInvalidCurly -> NVimInvalidDelimiter -// NVimInvalidFigureBrace -> NVimInvalidDelimiter -// NVimInvalidIdentifier -> NVimInvalidValue -// NVimInvalidIdentifierScope -> NVimInvalidValue -// NVimInvalidIdentifierScopeDelimiter -> NVimInvalidValue -// NVimInvalidComparisonOperator -> NVimInvalidOperator -// NVimInvalidComparisonOperatorModifier -> NVimInvalidComparisonOperator -// NVimInvalidNumber -> NVimInvalidValue -// NVimInvalidFloat -> NVimInvalidValue -// NVimInvalidIdentifierKey -> NVimInvalidIdentifier -// NVimInvalidList -> NVimInvalidDelimiter -// NVimInvalidSubscript -> NVimInvalidDelimiter -// NVimInvalidSubscriptColon -> NVimInvalidSubscript -// NVimInvalidString -> NVimInvalidValue -// NVimInvalidStringSpecial -> NVimInvalidString -// NVimInvalidSingleQuote -> NVimInvalidString -// NVimInvalidSingleQuotedBody -> NVimInvalidString -// NVimInvalidSingleQuotedQuote -> NVimInvalidStringSpecial -// NVimInvalidDoubleQuote -> NVimInvalidString -// NVimInvalidDoubleQuotedBody -> NVimInvalidString -// NVimInvalidDoubleQuotedEscape -> NVimInvalidStringSpecial -// NVimInvalidDoubleQuotedUnknownEscape -> NVimInvalidDoubleQuotedEscape -// -// NVimFigureBrace -> NVimInternalError -// NVimInvalidSingleQuotedUnknownEscape -> NVimInternalError -// NVimSingleQuotedUnknownEscape -> NVimInternalError /// Allocate a new node and set some of the values /// @@ -2183,12 +2082,12 @@ viml_pexpr_parse_process_token: ADD_OP_NODE(cur_node); if (cur_token.data.cmp.ccs != kCCStrategyUseOption) { viml_parser_highlight(pstate, cur_token.start, cur_token.len - 1, - HL(ComparisonOperator)); + HL(Comparison)); viml_parser_highlight( pstate, shifted_pos(cur_token.start, cur_token.len - 1), 1, - HL(ComparisonOperatorModifier)); + HL(ComparisonModifier)); } else { - HL_CUR_TOKEN(ComparisonOperator); + HL_CUR_TOKEN(Comparison); } want_node = kENodeValue; break; @@ -2390,7 +2289,7 @@ viml_pexpr_parse_valid_colon: break; } case kExprNodeSubscript: { - HL_CUR_TOKEN(Subscript); + HL_CUR_TOKEN(SubscriptBracket); break; } default: { @@ -2418,7 +2317,7 @@ viml_pexpr_parse_bracket_closing_error: } NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeSubscript); ADD_OP_NODE(cur_node); - HL_CUR_TOKEN(Subscript); + HL_CUR_TOKEN(SubscriptBracket); } } break; @@ -2626,7 +2525,7 @@ viml_pexpr_parse_figure_brace_closing_error: cur_token.len - scope_shift, (node_is_key ? HL(IdentifierKey) - : HL(Identifier))); + : HL(IdentifierName))); } else { if (scope == kExprVarScopeMissing) { ADD_IDENT( @@ -2637,7 +2536,7 @@ viml_pexpr_parse_figure_brace_closing_error: cur_node->data.var.ident_len = cur_token.len; want_node = kENodeOperator; } while (0), - Identifier); + IdentifierName); } else { OP_MISSING; } diff --git a/test/unit/viml/expressions/parser_spec.lua b/test/unit/viml/expressions/parser_spec.lua index 125a658f7b..454fbad236 100644 --- a/test/unit/viml/expressions/parser_spec.lua +++ b/test/unit/viml/expressions/parser_spec.lua @@ -1183,7 +1183,7 @@ describe('Expressions parser', function() 'PlainIdentifier(scope=0,ident=var):0:0:var', }, }, { - hl('Identifier', 'var'), + hl('IdentifierName', 'var'), }) check_parsing('g:var', 0, { ast = { @@ -1192,7 +1192,7 @@ describe('Expressions parser', function() }, { hl('IdentifierScope', 'g'), hl('IdentifierScopeDelimiter', ':'), - hl('Identifier', 'var'), + hl('IdentifierName', 'var'), }) check_parsing('g:', 0, { ast = { @@ -1214,7 +1214,7 @@ describe('Expressions parser', function() }, }, { hl('Curly', '{'), - hl('Identifier', 'a'), + hl('IdentifierName', 'a'), hl('Curly', '}'), }) check_parsing('{a:b}', 0, { @@ -1231,7 +1231,7 @@ describe('Expressions parser', function() hl('Curly', '{'), hl('IdentifierScope', 'a'), hl('IdentifierScopeDelimiter', ':'), - hl('Identifier', 'b'), + hl('IdentifierName', 'b'), hl('Curly', '}'), }) check_parsing('{a:@b}', 0, { @@ -1347,7 +1347,7 @@ describe('Expressions parser', function() hl('Curly', '{'), hl('Register', '@a'), hl('Curly', '}'), - hl('Identifier', '_test'), + hl('IdentifierName', '_test'), }) check_parsing('g:{@a}_test', 0, { -- 01234567890 @@ -1377,7 +1377,7 @@ describe('Expressions parser', function() hl('Curly', '{'), hl('Register', '@a'), hl('Curly', '}'), - hl('Identifier', '_test'), + hl('IdentifierName', '_test'), }) check_parsing('g:{@a}_test()', 0, { -- 0123456789012 @@ -1412,7 +1412,7 @@ describe('Expressions parser', function() hl('Curly', '{'), hl('Register', '@a'), hl('Curly', '}'), - hl('Identifier', '_test'), + hl('IdentifierName', '_test'), hl('CallingParenthesis', '('), hl('CallingParenthesis', ')'), }) @@ -1563,7 +1563,7 @@ describe('Expressions parser', function() }, }, { hl('Lambda', '{'), - hl('Identifier', 'a'), + hl('IdentifierName', 'a'), hl('Arrow', '->'), hl('Register', '@a'), hl('Lambda', '}'), @@ -1592,9 +1592,9 @@ describe('Expressions parser', function() }, }, { hl('Lambda', '{'), - hl('Identifier', 'a'), + hl('IdentifierName', 'a'), hl('Comma', ','), - hl('Identifier', 'b'), + hl('IdentifierName', 'b'), hl('Arrow', '->'), hl('Register', '@a'), hl('Lambda', '}'), @@ -1629,11 +1629,11 @@ describe('Expressions parser', function() }, }, { hl('Lambda', '{'), - hl('Identifier', 'a'), + hl('IdentifierName', 'a'), hl('Comma', ','), - hl('Identifier', 'b'), + hl('IdentifierName', 'b'), hl('Comma', ','), - hl('Identifier', 'c'), + hl('IdentifierName', 'c'), hl('Arrow', '->'), hl('Register', '@a'), hl('Lambda', '}'), @@ -1674,13 +1674,13 @@ describe('Expressions parser', function() }, }, { hl('Lambda', '{'), - hl('Identifier', 'a'), + hl('IdentifierName', 'a'), hl('Comma', ','), - hl('Identifier', 'b'), + hl('IdentifierName', 'b'), hl('Comma', ','), - hl('Identifier', 'c'), + hl('IdentifierName', 'c'), hl('Comma', ','), - hl('Identifier', 'd'), + hl('IdentifierName', 'd'), hl('Arrow', '->'), hl('Register', '@a'), hl('Lambda', '}'), @@ -1726,13 +1726,13 @@ describe('Expressions parser', function() }, }, { hl('Lambda', '{'), - hl('Identifier', 'a'), + hl('IdentifierName', 'a'), hl('Comma', ','), - hl('Identifier', 'b'), + hl('IdentifierName', 'b'), hl('Comma', ','), - hl('Identifier', 'c'), + hl('IdentifierName', 'c'), hl('Comma', ','), - hl('Identifier', 'd'), + hl('IdentifierName', 'd'), hl('Comma', ','), hl('Arrow', '->'), hl('Register', '@a'), @@ -1797,19 +1797,19 @@ describe('Expressions parser', function() }, }, { hl('Lambda', '{'), - hl('Identifier', 'a'), + hl('IdentifierName', 'a'), hl('Comma', ','), - hl('Identifier', 'b'), + hl('IdentifierName', 'b'), hl('Arrow', '->'), hl('Lambda', '{'), - hl('Identifier', 'c'), + hl('IdentifierName', 'c'), hl('Comma', ','), - hl('Identifier', 'd'), + hl('IdentifierName', 'd'), hl('Arrow', '->'), hl('Lambda', '{'), - hl('Identifier', 'e'), + hl('IdentifierName', 'e'), hl('Comma', ','), - hl('Identifier', 'f'), + hl('IdentifierName', 'f'), hl('Arrow', '->'), hl('Register', '@a'), hl('Lambda', '}'), @@ -1850,13 +1850,13 @@ describe('Expressions parser', function() }, }, { hl('Lambda', '{'), - hl('Identifier', 'a'), + hl('IdentifierName', 'a'), hl('Comma', ','), - hl('Identifier', 'b'), + hl('IdentifierName', 'b'), hl('Arrow', '->'), - hl('Identifier', 'c'), + hl('IdentifierName', 'c'), hl('InvalidComma', ','), - hl('Identifier', 'd'), + hl('IdentifierName', 'd'), hl('Lambda', '}'), }) check_parsing('a,b,c,d', 0, { @@ -1887,13 +1887,13 @@ describe('Expressions parser', function() msg = 'E15: Comma outside of call, lambda or literal: %.*s', }, }, { - hl('Identifier', 'a'), + hl('IdentifierName', 'a'), hl('InvalidComma', ','), - hl('Identifier', 'b'), + hl('IdentifierName', 'b'), hl('InvalidComma', ','), - hl('Identifier', 'c'), + hl('IdentifierName', 'c'), hl('InvalidComma', ','), - hl('Identifier', 'd'), + hl('IdentifierName', 'd'), }) check_parsing('a,b,c,d,', 0, { -- 0123456789 @@ -1928,13 +1928,13 @@ describe('Expressions parser', function() msg = 'E15: Comma outside of call, lambda or literal: %.*s', }, }, { - hl('Identifier', 'a'), + hl('IdentifierName', 'a'), hl('InvalidComma', ','), - hl('Identifier', 'b'), + hl('IdentifierName', 'b'), hl('InvalidComma', ','), - hl('Identifier', 'c'), + hl('IdentifierName', 'c'), hl('InvalidComma', ','), - hl('Identifier', 'd'), + hl('IdentifierName', 'd'), hl('InvalidComma', ','), }) check_parsing(',', 0, { @@ -1983,7 +1983,7 @@ describe('Expressions parser', function() }, { hl('Curly', '{'), hl('InvalidComma', ','), - hl('Identifier', 'a'), + hl('IdentifierName', 'a'), hl('InvalidArrow', '->'), hl('Register', '@a'), hl('Curly', '}'), @@ -2041,9 +2041,9 @@ describe('Expressions parser', function() }, }, { hl('Lambda', '{'), - hl('Identifier', 'a'), + hl('IdentifierName', 'a'), hl('Comma', ','), - hl('Identifier', 'b'), + hl('IdentifierName', 'b'), hl('InvalidLambda', '}'), }) check_parsing('{a,}', 0, { @@ -2067,7 +2067,7 @@ describe('Expressions parser', function() }, }, { hl('Lambda', '{'), - hl('Identifier', 'a'), + hl('IdentifierName', 'a'), hl('Comma', ','), hl('InvalidLambda', '}'), }) @@ -2343,9 +2343,9 @@ describe('Expressions parser', function() hl('Curly', '{'), hl('NestingParenthesis', '('), hl('Lambda', '{'), - hl('Identifier', 'f'), + hl('IdentifierName', 'f'), hl('Arrow', '->', 1), - hl('Identifier', 'g', 1), + hl('IdentifierName', 'g', 1), hl('Lambda', '}'), hl('NestingParenthesis', ')'), hl('CallingParenthesis', '('), @@ -2387,11 +2387,11 @@ describe('Expressions parser', function() hl('IdentifierScope', 'a'), hl('IdentifierScopeDelimiter', ':'), hl('Curly', '{'), - hl('Identifier', 'b'), + hl('IdentifierName', 'b'), hl('CallingParenthesis', '('), hl('CallingParenthesis', ')'), hl('Curly', '}'), - hl('Identifier', 'c'), + hl('IdentifierName', 'c'), }) check_parsing('a:{{b, c -> @d + @e + ({f -> g})(@h)}(@i)}j', 0, { -- 01234567890123456789012345678901234567890123456 @@ -2478,9 +2478,9 @@ describe('Expressions parser', function() hl('IdentifierScopeDelimiter', ':'), hl('Curly', '{'), hl('Lambda', '{'), - hl('Identifier', 'b'), + hl('IdentifierName', 'b'), hl('Comma', ','), - hl('Identifier', 'c', 1), + hl('IdentifierName', 'c', 1), hl('Arrow', '->', 1), hl('Register', '@d', 1), hl('BinaryPlus', '+', 1), @@ -2488,9 +2488,9 @@ describe('Expressions parser', function() hl('BinaryPlus', '+', 1), hl('NestingParenthesis', '(', 1), hl('Lambda', '{'), - hl('Identifier', 'f'), + hl('IdentifierName', 'f'), hl('Arrow', '->', 1), - hl('Identifier', 'g', 1), + hl('IdentifierName', 'g', 1), hl('Lambda', '}'), hl('NestingParenthesis', ')'), hl('CallingParenthesis', '('), @@ -2501,7 +2501,7 @@ describe('Expressions parser', function() hl('Register', '@i'), hl('CallingParenthesis', ')'), hl('Curly', '}'), - hl('Identifier', 'j'), + hl('IdentifierName', 'j'), }) check_parsing('{@a + @b : @c + @d, @e + @f : @g + @i}', 0, { -- 01234567890123456789012345678901234567 @@ -2635,13 +2635,13 @@ describe('Expressions parser', function() msg = 'E15: Arrow outside of lambda: %.*s', }, }, { - hl('Identifier', 'a'), + hl('IdentifierName', 'a'), hl('InvalidArrow', '->', 1), - hl('Identifier', 'b', 1), + hl('IdentifierName', 'b', 1), hl('InvalidArrow', '->', 1), - hl('Identifier', 'c', 1), + hl('IdentifierName', 'c', 1), hl('InvalidArrow', '->', 1), - hl('Identifier', 'd', 1), + hl('IdentifierName', 'd', 1), }) check_parsing('{a -> b -> c}', 0, { -- 0123456789012 @@ -2672,11 +2672,11 @@ describe('Expressions parser', function() }, }, { hl('Lambda', '{'), - hl('Identifier', 'a'), + hl('IdentifierName', 'a'), hl('Arrow', '->', 1), - hl('Identifier', 'b', 1), + hl('IdentifierName', 'b', 1), hl('InvalidArrow', '->', 1), - hl('Identifier', 'c', 1), + hl('IdentifierName', 'c', 1), hl('Lambda', '}'), }) check_parsing('{a: -> b}', 0, { @@ -2704,7 +2704,7 @@ describe('Expressions parser', function() hl('IdentifierScope', 'a'), hl('IdentifierScopeDelimiter', ':'), hl('InvalidArrow', '->', 1), - hl('Identifier', 'b', 1), + hl('IdentifierName', 'b', 1), hl('Curly', '}'), }) @@ -2732,9 +2732,9 @@ describe('Expressions parser', function() hl('Curly', '{'), hl('IdentifierScope', 'a'), hl('IdentifierScopeDelimiter', ':'), - hl('Identifier', 'b'), + hl('IdentifierName', 'b'), hl('InvalidArrow', '->', 1), - hl('Identifier', 'b', 1), + hl('IdentifierName', 'b', 1), hl('Curly', '}'), }) @@ -2760,9 +2760,9 @@ describe('Expressions parser', function() }, }, { hl('Curly', '{'), - hl('Identifier', 'a#b'), + hl('IdentifierName', 'a#b'), hl('InvalidArrow', '->', 1), - hl('Identifier', 'b', 1), + hl('IdentifierName', 'b', 1), hl('Curly', '}'), }) check_parsing('{a : b : c}', 0, { @@ -2794,11 +2794,11 @@ describe('Expressions parser', function() }, }, { hl('Dict', '{'), - hl('Identifier', 'a'), + hl('IdentifierName', 'a'), hl('Colon', ':', 1), - hl('Identifier', 'b', 1), + hl('IdentifierName', 'b', 1), hl('InvalidColon', ':', 1), - hl('Identifier', 'c', 1), + hl('IdentifierName', 'c', 1), hl('Dict', '}'), }) check_parsing('{', 0, { @@ -2829,7 +2829,7 @@ describe('Expressions parser', function() }, }, { hl('FigureBrace', '{'), - hl('Identifier', 'a'), + hl('IdentifierName', 'a'), }) check_parsing('{a,b', 0, { -- 0123 @@ -2853,9 +2853,9 @@ describe('Expressions parser', function() }, }, { hl('Lambda', '{'), - hl('Identifier', 'a'), + hl('IdentifierName', 'a'), hl('Comma', ','), - hl('Identifier', 'b'), + hl('IdentifierName', 'b'), }) check_parsing('{a,b->', 0, { -- 012345 @@ -2880,9 +2880,9 @@ describe('Expressions parser', function() }, }, { hl('Lambda', '{'), - hl('Identifier', 'a'), + hl('IdentifierName', 'a'), hl('Comma', ','), - hl('Identifier', 'b'), + hl('IdentifierName', 'b'), hl('Arrow', '->'), }) check_parsing('{a,b->c', 0, { @@ -2913,11 +2913,11 @@ describe('Expressions parser', function() }, }, { hl('Lambda', '{'), - hl('Identifier', 'a'), + hl('IdentifierName', 'a'), hl('Comma', ','), - hl('Identifier', 'b'), + hl('IdentifierName', 'b'), hl('Arrow', '->'), - hl('Identifier', 'c'), + hl('IdentifierName', 'c'), }) check_parsing('{a : b', 0, { -- 012345 @@ -2941,9 +2941,9 @@ describe('Expressions parser', function() }, }, { hl('Dict', '{'), - hl('Identifier', 'a'), + hl('IdentifierName', 'a'), hl('Colon', ':', 1), - hl('Identifier', 'b', 1), + hl('IdentifierName', 'b', 1), }) check_parsing('{a : b,', 0, { -- 0123456 @@ -2972,9 +2972,9 @@ describe('Expressions parser', function() }, }, { hl('Dict', '{'), - hl('Identifier', 'a'), + hl('IdentifierName', 'a'), hl('Colon', ':', 1), - hl('Identifier', 'b', 1), + hl('IdentifierName', 'b', 1), hl('Comma', ','), }) end) @@ -2997,11 +2997,11 @@ describe('Expressions parser', function() }, }, }, { - hl('Identifier', 'a'), + hl('IdentifierName', 'a'), hl('Ternary', '?', 1), - hl('Identifier', 'b', 1), + hl('IdentifierName', 'b', 1), hl('TernaryColon', ':', 1), - hl('Identifier', 'c', 1), + hl('IdentifierName', 'c', 1), }) check_parsing('@a?@b?@c:@d:@e', 0, { -- 01234567890123 @@ -3271,9 +3271,9 @@ describe('Expressions parser', function() msg = 'E109: Missing \':\' after \'?\': %.*s', }, }, { - hl('Identifier', 'a'), + hl('IdentifierName', 'a'), hl('Ternary', '?'), - hl('Identifier', 'b'), + hl('IdentifierName', 'b'), }) check_parsing('a?b:', 0, { -- 0123 @@ -3296,7 +3296,7 @@ describe('Expressions parser', function() msg = 'E109: Missing \':\' after \'?\': %.*s', }, }, { - hl('Identifier', 'a'), + hl('IdentifierName', 'a'), hl('Ternary', '?'), hl('IdentifierScope', 'b'), hl('IdentifierScopeDelimiter', ':'), @@ -3320,12 +3320,12 @@ describe('Expressions parser', function() }, }, }, { - hl('Identifier', 'a'), + hl('IdentifierName', 'a'), hl('Ternary', '?'), hl('IdentifierScope', 'b'), hl('IdentifierScopeDelimiter', ':'), hl('TernaryColon', ':'), - hl('Identifier', 'c'), + hl('IdentifierName', 'c'), }) check_parsing('a?b :', 0, { @@ -3349,9 +3349,9 @@ describe('Expressions parser', function() msg = 'E15: Expected value, got EOC: %.*s', }, }, { - hl('Identifier', 'a'), + hl('IdentifierName', 'a'), hl('Ternary', '?'), - hl('Identifier', 'b'), + hl('IdentifierName', 'b'), hl('TernaryColon', ':', 1), }) @@ -3615,15 +3615,15 @@ describe('Expressions parser', function() }, }, }, { - hl('Identifier', 'a'), + hl('IdentifierName', 'a'), hl('Ternary', '?'), - hl('Identifier', 'b'), + hl('IdentifierName', 'b'), hl('Curly', '{'), - hl('Identifier', 'cdef'), + hl('IdentifierName', 'cdef'), hl('Curly', '}'), - hl('Identifier', 'g'), + hl('IdentifierName', 'g'), hl('TernaryColon', ':'), - hl('Identifier', 'h'), + hl('IdentifierName', 'h'), }) check_parsing('a ? b : c : d', 0, { -- 0123456789012 @@ -3654,13 +3654,13 @@ describe('Expressions parser', function() msg = 'E15: Colon outside of dictionary or ternary operator: %.*s', }, }, { - hl('Identifier', 'a'), + hl('IdentifierName', 'a'), hl('Ternary', '?', 1), - hl('Identifier', 'b', 1), + hl('IdentifierName', 'b', 1), hl('TernaryColon', ':', 1), - hl('Identifier', 'c', 1), + hl('IdentifierName', 'c', 1), hl('InvalidColon', ':', 1), - hl('Identifier', 'd', 1), + hl('IdentifierName', 'd', 1), }) end) itp('works with comparison operators', function() @@ -3676,9 +3676,9 @@ describe('Expressions parser', function() }, }, }, { - hl('Identifier', 'a'), - hl('ComparisonOperator', '==', 1), - hl('Identifier', 'b', 1), + hl('IdentifierName', 'a'), + hl('Comparison', '==', 1), + hl('IdentifierName', 'b', 1), }) check_parsing('a ==? b', 0, { @@ -3693,10 +3693,10 @@ describe('Expressions parser', function() }, }, }, { - hl('Identifier', 'a'), - hl('ComparisonOperator', '==', 1), - hl('ComparisonOperatorModifier', '?'), - hl('Identifier', 'b', 1), + hl('IdentifierName', 'a'), + hl('Comparison', '==', 1), + hl('ComparisonModifier', '?'), + hl('IdentifierName', 'b', 1), }) check_parsing('a ==# b', 0, { @@ -3711,10 +3711,10 @@ describe('Expressions parser', function() }, }, }, { - hl('Identifier', 'a'), - hl('ComparisonOperator', '==', 1), - hl('ComparisonOperatorModifier', '#'), - hl('Identifier', 'b', 1), + hl('IdentifierName', 'a'), + hl('Comparison', '==', 1), + hl('ComparisonModifier', '#'), + hl('IdentifierName', 'b', 1), }) check_parsing('a !=# b', 0, { @@ -3729,10 +3729,10 @@ describe('Expressions parser', function() }, }, }, { - hl('Identifier', 'a'), - hl('ComparisonOperator', '!=', 1), - hl('ComparisonOperatorModifier', '#'), - hl('Identifier', 'b', 1), + hl('IdentifierName', 'a'), + hl('Comparison', '!=', 1), + hl('ComparisonModifier', '#'), + hl('IdentifierName', 'b', 1), }) check_parsing('a <=# b', 0, { @@ -3747,10 +3747,10 @@ describe('Expressions parser', function() }, }, }, { - hl('Identifier', 'a'), - hl('ComparisonOperator', '<=', 1), - hl('ComparisonOperatorModifier', '#'), - hl('Identifier', 'b', 1), + hl('IdentifierName', 'a'), + hl('Comparison', '<=', 1), + hl('ComparisonModifier', '#'), + hl('IdentifierName', 'b', 1), }) check_parsing('a >=# b', 0, { @@ -3765,10 +3765,10 @@ describe('Expressions parser', function() }, }, }, { - hl('Identifier', 'a'), - hl('ComparisonOperator', '>=', 1), - hl('ComparisonOperatorModifier', '#'), - hl('Identifier', 'b', 1), + hl('IdentifierName', 'a'), + hl('Comparison', '>=', 1), + hl('ComparisonModifier', '#'), + hl('IdentifierName', 'b', 1), }) check_parsing('a ># b', 0, { @@ -3783,10 +3783,10 @@ describe('Expressions parser', function() }, }, }, { - hl('Identifier', 'a'), - hl('ComparisonOperator', '>', 1), - hl('ComparisonOperatorModifier', '#'), - hl('Identifier', 'b', 1), + hl('IdentifierName', 'a'), + hl('Comparison', '>', 1), + hl('ComparisonModifier', '#'), + hl('IdentifierName', 'b', 1), }) check_parsing('a <# b', 0, { @@ -3801,10 +3801,10 @@ describe('Expressions parser', function() }, }, }, { - hl('Identifier', 'a'), - hl('ComparisonOperator', '<', 1), - hl('ComparisonOperatorModifier', '#'), - hl('Identifier', 'b', 1), + hl('IdentifierName', 'a'), + hl('Comparison', '<', 1), + hl('ComparisonModifier', '#'), + hl('IdentifierName', 'b', 1), }) check_parsing('a is#b', 0, { @@ -3819,10 +3819,10 @@ describe('Expressions parser', function() }, }, }, { - hl('Identifier', 'a'), - hl('ComparisonOperator', 'is', 1), - hl('ComparisonOperatorModifier', '#'), - hl('Identifier', 'b'), + hl('IdentifierName', 'a'), + hl('Comparison', 'is', 1), + hl('ComparisonModifier', '#'), + hl('IdentifierName', 'b'), }) check_parsing('a is?b', 0, { @@ -3837,10 +3837,10 @@ describe('Expressions parser', function() }, }, }, { - hl('Identifier', 'a'), - hl('ComparisonOperator', 'is', 1), - hl('ComparisonOperatorModifier', '?'), - hl('Identifier', 'b'), + hl('IdentifierName', 'a'), + hl('Comparison', 'is', 1), + hl('ComparisonModifier', '?'), + hl('IdentifierName', 'b'), }) check_parsing('a isnot b', 0, { @@ -3855,9 +3855,9 @@ describe('Expressions parser', function() }, }, }, { - hl('Identifier', 'a'), - hl('ComparisonOperator', 'isnot', 1), - hl('Identifier', 'b', 1), + hl('IdentifierName', 'a'), + hl('Comparison', 'isnot', 1), + hl('IdentifierName', 'b', 1), }) check_parsing('a < b < c', 0, { @@ -3882,12 +3882,43 @@ describe('Expressions parser', function() msg = 'E15: Operator is not associative: %.*s', }, }, { - hl('Identifier', 'a'), - hl('ComparisonOperator', '<', 1), - hl('Identifier', 'b', 1), - hl('InvalidComparisonOperator', '<', 1), - hl('Identifier', 'c', 1), + hl('IdentifierName', 'a'), + hl('Comparison', '<', 1), + hl('IdentifierName', 'b', 1), + hl('InvalidComparison', '<', 1), + hl('IdentifierName', 'c', 1), }) + + check_parsing('a < b <# c', 0, { + -- 012345678 + ast = { + { + 'Comparison(type=GreaterOrEqual,inv=1,ccs=UseOption):0:1: <', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'Comparison(type=GreaterOrEqual,inv=1,ccs=MatchCase):0:5: <#', + children = { + 'PlainIdentifier(scope=0,ident=b):0:3: b', + 'PlainIdentifier(scope=0,ident=c):0:8: c', + }, + }, + }, + }, + }, + err = { + arg = ' <# c', + msg = 'E15: Operator is not associative: %.*s', + }, + }, { + hl('IdentifierName', 'a'), + hl('Comparison', '<', 1), + hl('IdentifierName', 'b', 1), + hl('InvalidComparison', '<', 1), + hl('InvalidComparisonModifier', '#'), + hl('IdentifierName', 'c', 1), + }) + check_parsing('a += b', 0, { -- 012345 ast = { @@ -3910,10 +3941,10 @@ describe('Expressions parser', function() msg = 'E15: Expected == or =~: %.*s', }, }, { - hl('Identifier', 'a'), + hl('IdentifierName', 'a'), hl('BinaryPlus', '+', 1), - hl('InvalidComparisonOperator', '='), - hl('Identifier', 'b', 1), + hl('InvalidComparison', '='), + hl('IdentifierName', 'b', 1), }) check_parsing('a + b == c + d', 0, { -- 01234567890123 @@ -3940,13 +3971,13 @@ describe('Expressions parser', function() }, }, }, { - hl('Identifier', 'a'), + hl('IdentifierName', 'a'), hl('BinaryPlus', '+', 1), - hl('Identifier', 'b', 1), - hl('ComparisonOperator', '==', 1), - hl('Identifier', 'c', 1), + hl('IdentifierName', 'b', 1), + hl('Comparison', '==', 1), + hl('IdentifierName', 'c', 1), hl('BinaryPlus', '+', 1), - hl('Identifier', 'd', 1), + hl('IdentifierName', 'd', 1), }) check_parsing('+ a == + b', 0, { -- 0123456789 @@ -3971,10 +4002,10 @@ describe('Expressions parser', function() }, }, { hl('UnaryPlus', '+'), - hl('Identifier', 'a', 1), - hl('ComparisonOperator', '==', 1), + hl('IdentifierName', 'a', 1), + hl('Comparison', '==', 1), hl('UnaryPlus', '+', 1), - hl('Identifier', 'b', 1), + hl('IdentifierName', 'b', 1), }) end) itp('works with concat/subscript', function() @@ -4011,7 +4042,7 @@ describe('Expressions parser', function() msg = 'E15: Expected value, got EOC: %.*s', }, }, { - hl('Identifier', 'a'), + hl('IdentifierName', 'a'), hl('ConcatOrSubscript', '.'), }) @@ -4027,7 +4058,7 @@ describe('Expressions parser', function() }, }, }, { - hl('Identifier', 'a'), + hl('IdentifierName', 'a'), hl('ConcatOrSubscript', '.'), hl('IdentifierKey', 'b'), }) @@ -4084,7 +4115,7 @@ describe('Expressions parser', function() }, }, }, { - hl('Identifier', 'a'), + hl('IdentifierName', 'a'), hl('Concat', '.', 1), hl('Number', '1', 1), hl('ConcatOrSubscript', '.'), @@ -4116,7 +4147,7 @@ describe('Expressions parser', function() hl('BinaryPlus', '+', 1), hl('Float', '1.2', 1), hl('Concat', '.', 1), - hl('Identifier', 'a', 1), + hl('IdentifierName', 'a', 1), }) check_parsing('1.3e-5 + a . 1.2', 0, { @@ -4146,7 +4177,7 @@ describe('Expressions parser', function() }, { hl('Float', '1.3e-5'), hl('BinaryPlus', '+', 1), - hl('Identifier', 'a', 1), + hl('IdentifierName', 'a', 1), hl('Concat', '.', 1), hl('Number', '1', 1), hl('ConcatOrSubscript', '.'), @@ -4196,7 +4227,7 @@ describe('Expressions parser', function() }, }, }, { - hl('Identifier', 'a'), + hl('IdentifierName', 'a'), hl('ConcatOrSubscript', '.'), hl('IdentifierKey', '1'), hl('ConcatOrSubscript', '.'), @@ -4221,7 +4252,7 @@ describe('Expressions parser', function() }, }, }, { - hl('Identifier', 'a'), + hl('IdentifierName', 'a'), hl('Concat', '.', 1), hl('Number', '1', 1), hl('ConcatOrSubscript', '.'), @@ -4251,10 +4282,10 @@ describe('Expressions parser', function() }, }, { hl('UnaryPlus', '+'), - hl('Identifier', 'a'), + hl('IdentifierName', 'a'), hl('Concat', '.', 1), hl('UnaryPlus', '+', 1), - hl('Identifier', 'b'), + hl('IdentifierName', 'b'), }) check_parsing('a. b', 0, { @@ -4269,9 +4300,9 @@ describe('Expressions parser', function() }, }, }, { - hl('Identifier', 'a'), + hl('IdentifierName', 'a'), hl('ConcatOrSubscript', '.'), - hl('Identifier', 'b', 1), + hl('IdentifierName', 'b', 1), }) check_parsing('a. 1', 0, { @@ -4286,7 +4317,7 @@ describe('Expressions parser', function() }, }, }, { - hl('Identifier', 'a'), + hl('IdentifierName', 'a'), hl('ConcatOrSubscript', '.'), hl('Number', '1', 1), }) @@ -4324,9 +4355,9 @@ describe('Expressions parser', function() msg = 'E15: Expected value, got closing bracket: %.*s', }, }, { - hl('Identifier', 'a'), - hl('Subscript', '['), - hl('InvalidSubscript', ']'), + hl('IdentifierName', 'a'), + hl('SubscriptBracket', '['), + hl('InvalidSubscriptBracket', ']'), }) check_parsing('a[b:]', 0, { -- 01234 @@ -4340,11 +4371,11 @@ describe('Expressions parser', function() }, }, }, { - hl('Identifier', 'a'), - hl('Subscript', '['), + hl('IdentifierName', 'a'), + hl('SubscriptBracket', '['), hl('IdentifierScope', 'b'), hl('IdentifierScopeDelimiter', ':'), - hl('Subscript', ']'), + hl('SubscriptBracket', ']'), }) check_parsing('a[b:c]', 0, { @@ -4359,12 +4390,12 @@ describe('Expressions parser', function() }, }, }, { - hl('Identifier', 'a'), - hl('Subscript', '['), + hl('IdentifierName', 'a'), + hl('SubscriptBracket', '['), hl('IdentifierScope', 'b'), hl('IdentifierScopeDelimiter', ':'), - hl('Identifier', 'c'), - hl('Subscript', ']'), + hl('IdentifierName', 'c'), + hl('SubscriptBracket', ']'), }) check_parsing('a[b : c]', 0, { -- 01234567 @@ -4384,12 +4415,12 @@ describe('Expressions parser', function() }, }, }, { - hl('Identifier', 'a'), - hl('Subscript', '['), - hl('Identifier', 'b'), + hl('IdentifierName', 'a'), + hl('SubscriptBracket', '['), + hl('IdentifierName', 'b'), hl('SubscriptColon', ':', 1), - hl('Identifier', 'c', 1), - hl('Subscript', ']'), + hl('IdentifierName', 'c', 1), + hl('SubscriptBracket', ']'), }) check_parsing('a[: b]', 0, { @@ -4410,11 +4441,11 @@ describe('Expressions parser', function() }, }, }, { - hl('Identifier', 'a'), - hl('Subscript', '['), + hl('IdentifierName', 'a'), + hl('SubscriptBracket', '['), hl('SubscriptColon', ':'), - hl('Identifier', 'b', 1), - hl('Subscript', ']'), + hl('IdentifierName', 'b', 1), + hl('SubscriptBracket', ']'), }) check_parsing('a[b :]', 0, { @@ -4434,11 +4465,11 @@ describe('Expressions parser', function() }, }, }, { - hl('Identifier', 'a'), - hl('Subscript', '['), - hl('Identifier', 'b'), + hl('IdentifierName', 'a'), + hl('SubscriptBracket', '['), + hl('IdentifierName', 'b'), hl('SubscriptColon', ':', 1), - hl('Subscript', ']'), + hl('SubscriptBracket', ']'), }) check_parsing('a[b][c][d](e)(f)(g)', 0, { -- 0123456789012345678 @@ -4483,24 +4514,24 @@ describe('Expressions parser', function() }, }, }, { - hl('Identifier', 'a'), - hl('Subscript', '['), - hl('Identifier', 'b'), - hl('Subscript', ']'), - hl('Subscript', '['), - hl('Identifier', 'c'), - hl('Subscript', ']'), - hl('Subscript', '['), - hl('Identifier', 'd'), - hl('Subscript', ']'), + hl('IdentifierName', 'a'), + hl('SubscriptBracket', '['), + hl('IdentifierName', 'b'), + hl('SubscriptBracket', ']'), + hl('SubscriptBracket', '['), + hl('IdentifierName', 'c'), + hl('SubscriptBracket', ']'), + hl('SubscriptBracket', '['), + hl('IdentifierName', 'd'), + hl('SubscriptBracket', ']'), hl('CallingParenthesis', '('), - hl('Identifier', 'e'), + hl('IdentifierName', 'e'), hl('CallingParenthesis', ')'), hl('CallingParenthesis', '('), - hl('Identifier', 'f'), + hl('IdentifierName', 'f'), hl('CallingParenthesis', ')'), hl('CallingParenthesis', '('), - hl('Identifier', 'g'), + hl('IdentifierName', 'g'), hl('CallingParenthesis', ')'), }) check_parsing('{a}{b}{c}[d][e][f]', 0, { @@ -4556,23 +4587,23 @@ describe('Expressions parser', function() }, }, { hl('Curly', '{'), - hl('Identifier', 'a'), + hl('IdentifierName', 'a'), hl('Curly', '}'), hl('Curly', '{'), - hl('Identifier', 'b'), + hl('IdentifierName', 'b'), hl('Curly', '}'), hl('Curly', '{'), - hl('Identifier', 'c'), + hl('IdentifierName', 'c'), hl('Curly', '}'), - hl('Subscript', '['), - hl('Identifier', 'd'), - hl('Subscript', ']'), - hl('Subscript', '['), - hl('Identifier', 'e'), - hl('Subscript', ']'), - hl('Subscript', '['), - hl('Identifier', 'f'), - hl('Subscript', ']'), + hl('SubscriptBracket', '['), + hl('IdentifierName', 'd'), + hl('SubscriptBracket', ']'), + hl('SubscriptBracket', '['), + hl('IdentifierName', 'e'), + hl('SubscriptBracket', ']'), + hl('SubscriptBracket', '['), + hl('IdentifierName', 'f'), + hl('SubscriptBracket', ']'), }) end) itp('supports list literals', function() @@ -4598,7 +4629,7 @@ describe('Expressions parser', function() }, }, { hl('List', '['), - hl('Identifier', 'a'), + hl('IdentifierName', 'a'), hl('List', ']'), }) @@ -4620,9 +4651,9 @@ describe('Expressions parser', function() }, }, { hl('List', '['), - hl('Identifier', 'a'), + hl('IdentifierName', 'a'), hl('Comma', ','), - hl('Identifier', 'b', 1), + hl('IdentifierName', 'b', 1), hl('List', ']'), }) @@ -4650,11 +4681,11 @@ describe('Expressions parser', function() }, }, { hl('List', '['), - hl('Identifier', 'a'), + hl('IdentifierName', 'a'), hl('Comma', ','), - hl('Identifier', 'b', 1), + hl('IdentifierName', 'b', 1), hl('Comma', ','), - hl('Identifier', 'c', 1), + hl('IdentifierName', 'c', 1), hl('List', ']'), }) @@ -4688,11 +4719,11 @@ describe('Expressions parser', function() }, }, { hl('List', '['), - hl('Identifier', 'a'), + hl('IdentifierName', 'a'), hl('Comma', ','), - hl('Identifier', 'b', 1), + hl('IdentifierName', 'b', 1), hl('Comma', ','), - hl('Identifier', 'c', 1), + hl('IdentifierName', 'c', 1), hl('Comma', ','), hl('List', ']', 1), }) @@ -4732,13 +4763,13 @@ describe('Expressions parser', function() }, }, { hl('List', '['), - hl('Identifier', 'a'), + hl('IdentifierName', 'a'), hl('InvalidColon', ':', 1), - hl('Identifier', 'b', 1), + hl('IdentifierName', 'b', 1), hl('Comma', ','), - hl('Identifier', 'c', 1), + hl('IdentifierName', 'c', 1), hl('InvalidColon', ':', 1), - hl('Identifier', 'd', 1), + hl('IdentifierName', 'd', 1), hl('List', ']'), }) @@ -4770,7 +4801,7 @@ describe('Expressions parser', function() msg = 'E15: Unexpected closing figure brace: %.*s', }, }, { - hl('Identifier', 'a'), + hl('IdentifierName', 'a'), hl('InvalidList', ']'), }) @@ -4814,8 +4845,8 @@ describe('Expressions parser', function() }, { hl('List', '['), hl('List', ']'), - hl('Subscript', '['), - hl('InvalidSubscript', ']'), + hl('SubscriptBracket', '['), + hl('InvalidSubscriptBracket', ']'), }) check_parsing('[', 0, { @@ -6119,13 +6150,13 @@ describe('Expressions parser', function() }, }, }, { - hl('Identifier', 'a'), + hl('IdentifierName', 'a'), hl('And', '&&', 1), - hl('Identifier', 'b', 1), + hl('IdentifierName', 'b', 1), hl('Or', '||', 1), - hl('Identifier', 'c', 1), + hl('IdentifierName', 'c', 1), hl('And', '&&', 1), - hl('Identifier', 'd', 1), + hl('IdentifierName', 'd', 1), }) check_parsing('&& a', 0, { @@ -6145,7 +6176,7 @@ describe('Expressions parser', function() }, }, { hl('InvalidAnd', '&&'), - hl('Identifier', 'a', 1), + hl('IdentifierName', 'a', 1), }) check_parsing('|| a', 0, { @@ -6165,7 +6196,7 @@ describe('Expressions parser', function() }, }, { hl('InvalidOr', '||'), - hl('Identifier', 'a', 1), + hl('IdentifierName', 'a', 1), }) check_parsing('a||', 0, { @@ -6183,7 +6214,7 @@ describe('Expressions parser', function() msg = 'E15: Expected value, got EOC: %.*s', }, }, { - hl('Identifier', 'a'), + hl('IdentifierName', 'a'), hl('Or', '||'), }) @@ -6202,7 +6233,7 @@ describe('Expressions parser', function() msg = 'E15: Expected value, got EOC: %.*s', }, }, { - hl('Identifier', 'a'), + hl('IdentifierName', 'a'), hl('And', '&&'), }) @@ -6280,7 +6311,7 @@ describe('Expressions parser', function() }, }, { hl('NestingParenthesis', '('), - hl('Identifier', 'a'), + hl('IdentifierName', 'a'), hl('Or', '||'), hl('InvalidNestingParenthesis', ')'), }) @@ -6307,7 +6338,7 @@ describe('Expressions parser', function() }, }, { hl('NestingParenthesis', '('), - hl('Identifier', 'a'), + hl('IdentifierName', 'a'), hl('And', '&&'), hl('InvalidNestingParenthesis', ')'), }) @@ -6335,7 +6366,7 @@ describe('Expressions parser', function() }, { hl('NestingParenthesis', '('), hl('InvalidAnd', '&&'), - hl('Identifier', 'a'), + hl('IdentifierName', 'a'), hl('NestingParenthesis', ')'), }) @@ -6362,7 +6393,7 @@ describe('Expressions parser', function() }, { hl('NestingParenthesis', '('), hl('InvalidOr', '||'), - hl('Identifier', 'a'), + hl('IdentifierName', 'a'), hl('NestingParenthesis', ')'), }) end) @@ -6433,7 +6464,7 @@ describe('Expressions parser', function() hl('OptionSigil', '&'), hl('Option', 's'), hl('InvalidColon', ':'), - hl('Identifier', 'opt'), + hl('IdentifierName', 'opt'), }) check_parsing('& ', 0, { @@ -6496,7 +6527,7 @@ describe('Expressions parser', function() }, { hl('OptionSigil', '&'), hl('Option', 'xxx'), - hl('InvalidIdentifier', '_yyy'), + hl('InvalidIdentifierName', '_yyy'), }) check_parsing('(1+&)', 0, { @@ -6588,7 +6619,7 @@ describe('Expressions parser', function() hl('EnvironmentSigil', '$'), hl('Environment', 'g'), hl('InvalidColon', ':'), - hl('Identifier', 'A'), + hl('IdentifierName', 'A'), }) check_parsing('$A', 0, { @@ -7092,7 +7123,7 @@ describe('Expressions parser', function() }, }, { hl('InvalidOr', '|'), - hl('InvalidIdentifier', '\029'), + hl('InvalidIdentifierName', '\029'), }) check_parsing('"\\<', 0, { -- 012 @@ -7137,7 +7168,7 @@ describe('Expressions parser', function() }, }, { hl('InvalidFigureBrace', '}'), - hl('InvalidIdentifier', 'l'), + hl('InvalidIdentifierName', 'l'), }) check_parsing(':?\000\000\000\000\000\000\000', 0, { ast = { @@ -7164,4 +7195,5 @@ describe('Expressions parser', function() hl('InvalidTernary', '?'), }) end) + -- FIXME: check flag effects end) -- cgit From b91cb18c3688a4a936c14484af57de05ca113641 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 29 Oct 2017 21:42:37 +0300 Subject: syntax: Adjust position and arguments of syn_init_cmdline_highlight This way it works both after `nvim -u NORC` and after that and `colorscheme wombat256mod`. Removed the comment because I do not actually know why it works here with these arguments and not in previous position with previous arguments. --- src/nvim/syntax.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index b418df77a4..f8a62423ce 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -6248,11 +6248,8 @@ init_highlight(int both, int reset) (void)source_runtime((char_u *)"syntax/syncolor.vim", DIP_ALL); recursive--; } - // Without syncolor.vim it is going to screw everything over by defining - // cleared highlight groups by creating links to non-existent groups. This - // effectively prevents ":highlight default" from working properly. - syn_init_cmdline_highlight(reset, true); } + syn_init_cmdline_highlight(false, false); } /* -- cgit From 538af1c90a4ac9928f60e97338869e516def4956 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 29 Oct 2017 22:02:19 +0300 Subject: syntax,viml/parser/expressions: Add missing highlight groups Also adjusts some names. --- src/nvim/syntax.c | 39 +++- src/nvim/viml/parser/expressions.c | 12 +- test/unit/viml/expressions/parser_spec.lua | 290 ++++++++++++++--------------- 3 files changed, 188 insertions(+), 153 deletions(-) diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index f8a62423ce..d787790bc3 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -6029,6 +6029,8 @@ static const char *highlight_init_cmdline[] = { "default link NVimUnaryOperator NVimOperator", "default link NVimUnaryPlus NVimUnaryOperator", + "default link NVimUnaryMinus NVimUnaryOperator", + "default link NVimNot NVimUnaryOperator", "default link NVimBinaryOperator NVimOperator", "default link NVimComparison NVimBinaryOperator", @@ -6036,6 +6038,11 @@ static const char *highlight_init_cmdline[] = { "default link NVimBinaryPlus NVimBinaryOperator", "default link NVimConcat NVimBinaryOperator", "default link NVimConcatOrSubscript NVimConcat", + "default link NVimOr NVimBinaryOperator", + "default link NVimAnd NVimBinaryOperator", + "default link NVimMultiplication NVimBinaryOperator", + "default link NVimDivision NVimBinaryOperator", + "default link NVimMod NVimBinaryOperator", "default link NVimTernary NVimOperator", "default link NVimTernaryColon NVimTernary", @@ -6068,7 +6075,15 @@ static const char *highlight_init_cmdline[] = { "default link NVimRegister SpecialChar", "default link NVimNumber Number", "default link NVimFloat NVimNumber", - "default link NVimNumberPrefix SpecialChar", + "default link NVimNumberPrefix Type", + + "default link NVimOptionSigil Type", + "default link NVimOptionName NVimIdentifier", + "default link NVimOptionScope NVimIdentifierScope", + "default link NVimOptionScopeDelimiter NVimIdentifierScopeDelimiter", + + "default link NVimEnvironmentSigil NVimOptionSigil", + "default link NVimEnvironmentName NVimIdentifier", "default link NVimString String", "default link NVimStringBody NVimString", @@ -6089,6 +6104,8 @@ static const char *highlight_init_cmdline[] = { "default link NVimFigureBrace NVimInternalError", "default link NVimSingleQuotedUnknownEscape NVimInternalError", + "default link NVimSpacing Normal", + // NVimInvalid groups: "default link NVimInvalidSingleQuotedUnknownEscape NVimInternalError", @@ -6099,6 +6116,8 @@ static const char *highlight_init_cmdline[] = { "default link NVimInvalidUnaryOperator NVimInvalidOperator", "default link NVimInvalidUnaryPlus NVimInvalidUnaryOperator", + "default link NVimInvalidUnaryMinus NVimInvalidUnaryOperator", + "default link NVimInvalidNot NVimInvalidUnaryOperator", "default link NVimInvalidBinaryOperator NVimInvalidOperator", "default link NVimInvalidComparison NVimInvalidBinaryOperator", @@ -6106,6 +6125,11 @@ static const char *highlight_init_cmdline[] = { "default link NVimInvalidBinaryPlus NVimInvalidBinaryOperator", "default link NVimInvalidConcat NVimInvalidBinaryOperator", "default link NVimInvalidConcatOrSubscript NVimInvalidConcat", + "default link NVimInvalidOr NVimInvalidBinaryOperator", + "default link NVimInvalidAnd NVimInvalidBinaryOperator", + "default link NVimInvalidMultiplication NVimInvalidBinaryOperator", + "default link NVimInvalidDivision NVimInvalidBinaryOperator", + "default link NVimInvalidMod NVimInvalidBinaryOperator", "default link NVimInvalidTernary NVimInvalidOperator", "default link NVimInvalidTernaryColon NVimInvalidTernary", @@ -6144,6 +6168,15 @@ static const char *highlight_init_cmdline[] = { "default link NVimInvalidFloat NVimInvalidNumber", "default link NVimInvalidNumberPrefix NVimInvalidNumber", + "default link NVimInvalidOptionSigil NVimInvalidIdentifier", + "default link NVimInvalidOptionName NVimInvalidIdentifier", + "default link NVimInvalidOptionScope NVimInvalidIdentifierScope", + "default link NVimInvalidOptionScopeDelimiter " + "NVimInvalidIdentifierScopeDelimiter", + + "default link NVimInvalidEnvironmentSigil NVimInvalidOptionSigil", + "default link NVimInvalidEnvironmentName NVimInvalidIdentifier", + // Invalid string bodies and specials are still highlighted as valid ones to // minimize the red area. "default link NVimInvalidString NVimInvalidValue", @@ -6160,7 +6193,9 @@ static const char *highlight_init_cmdline[] = { "default link NVimInvalidDoubleQuotedEscape NVimInvalidStringSpecial", "default link NVimInvalidDoubleQuotedUnknownEscape NVimInvalidValue", - "default link NVimInvalidFigureBrace NVimInternalError", + "default link NVimInvalidFigureBrace NVimInvalidDelimiter", + + "default link NVimInvalidSpacing ErrorMsg", }; /// Create default links for NVim* highlight groups used for cmdline coloring diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index 615a59573f..f5bc547d54 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -1472,7 +1472,7 @@ static void parse_quoted_string(ParserState *const pstate, kvec_withinit_t(StringShift, 16) shifts; kvi_init(shifts); if (!is_double) { - viml_parser_highlight(pstate, token.start, 1, HL(SingleQuotedString)); + viml_parser_highlight(pstate, token.start, 1, HL(SingleQuote)); while (p < e) { const char *const chunk_e = memchr(p, '\'', (size_t)(e - p)); if (chunk_e == NULL) { @@ -1509,7 +1509,7 @@ static void parse_quoted_string(ParserState *const pstate, } } } else { - viml_parser_highlight(pstate, token.start, 1, HL(DoubleQuotedString)); + viml_parser_highlight(pstate, token.start, 1, HL(DoubleQuote)); for (p = s + 1; p < e; p++) { if (*p == '\\' && p + 1 < e) { p++; @@ -1741,10 +1741,10 @@ static void parse_quoted_string(ParserState *const pstate, if (token.data.str.closed) { if (is_double) { viml_parser_highlight(pstate, shifted_pos(token.start, token.len - 1), - 1, HL(DoubleQuotedString)); + 1, HL(DoubleQuote)); } else { viml_parser_highlight(pstate, shifted_pos(token.start, token.len - 1), - 1, HL(SingleQuotedString)); + 1, HL(SingleQuote)); } } kvi_destroy(shifts); @@ -2035,7 +2035,7 @@ viml_pexpr_parse_process_token: } viml_parser_highlight( pstate, shifted_pos(cur_token.start, scope_shift + 1), - cur_token.len - (scope_shift + 1), HL(Option)); + cur_token.len - (scope_shift + 1), HL(OptionName)); break; } case kExprLexEnv: { @@ -2053,7 +2053,7 @@ viml_pexpr_parse_process_token: want_node = kENodeOperator; viml_parser_highlight(pstate, cur_token.start, 1, HL(EnvironmentSigil)); viml_parser_highlight(pstate, shifted_pos(cur_token.start, 1), - cur_token.len - 1, HL(Environment)); + cur_token.len - 1, HL(EnvironmentName)); break; } case kExprLexNot: { diff --git a/test/unit/viml/expressions/parser_spec.lua b/test/unit/viml/expressions/parser_spec.lua index 454fbad236..019fe69046 100644 --- a/test/unit/viml/expressions/parser_spec.lua +++ b/test/unit/viml/expressions/parser_spec.lua @@ -4888,9 +4888,9 @@ describe('Expressions parser', function() 'SingleQuotedString(val="abc"):0:0:\'abc\'', }, }, { - hl('SingleQuotedString', '\''), + hl('SingleQuote', '\''), hl('SingleQuotedBody', 'abc'), - hl('SingleQuotedString', '\''), + hl('SingleQuote', '\''), }) check_parsing('"abc"', 0, { -- 01234 @@ -4898,9 +4898,9 @@ describe('Expressions parser', function() 'DoubleQuotedString(val="abc"):0:0:"abc"', }, }, { - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), hl('DoubleQuotedBody', 'abc'), - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), }) check_parsing('\'\'', 0, { -- 01 @@ -4908,8 +4908,8 @@ describe('Expressions parser', function() 'SingleQuotedString(val=NULL):0:0:\'\'', }, }, { - hl('SingleQuotedString', '\''), - hl('SingleQuotedString', '\''), + hl('SingleQuote', '\''), + hl('SingleQuote', '\''), }) check_parsing('""', 0, { -- 01 @@ -4917,8 +4917,8 @@ describe('Expressions parser', function() 'DoubleQuotedString(val=NULL):0:0:""', }, }, { - hl('DoubleQuotedString', '"'), - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), + hl('DoubleQuote', '"'), }) check_parsing('"', 0, { -- 0 @@ -4930,7 +4930,7 @@ describe('Expressions parser', function() msg = 'E114: Missing double quote: %.*s', }, }, { - hl('InvalidDoubleQuotedString', '"'), + hl('InvalidDoubleQuote', '"'), }) check_parsing('\'', 0, { -- 0 @@ -4942,7 +4942,7 @@ describe('Expressions parser', function() msg = 'E115: Missing single quote: %.*s', }, }, { - hl('InvalidSingleQuotedString', '\''), + hl('InvalidSingleQuote', '\''), }) check_parsing('"a', 0, { -- 01 @@ -4954,7 +4954,7 @@ describe('Expressions parser', function() msg = 'E114: Missing double quote: %.*s', }, }, { - hl('InvalidDoubleQuotedString', '"'), + hl('InvalidDoubleQuote', '"'), hl('InvalidDoubleQuotedBody', 'a'), }) check_parsing('\'a', 0, { @@ -4967,7 +4967,7 @@ describe('Expressions parser', function() msg = 'E115: Missing single quote: %.*s', }, }, { - hl('InvalidSingleQuotedString', '\''), + hl('InvalidSingleQuote', '\''), hl('InvalidSingleQuotedBody', 'a'), }) check_parsing('\'abc\'\'def\'', 0, { @@ -4976,11 +4976,11 @@ describe('Expressions parser', function() 'SingleQuotedString(val="abc\'def"):0:0:\'abc\'\'def\'', }, }, { - hl('SingleQuotedString', '\''), + hl('SingleQuote', '\''), hl('SingleQuotedBody', 'abc'), hl('SingleQuotedQuote', '\'\''), hl('SingleQuotedBody', 'def'), - hl('SingleQuotedString', '\''), + hl('SingleQuote', '\''), }) check_parsing('\'abc\'\'', 0, { -- 012345 @@ -4992,7 +4992,7 @@ describe('Expressions parser', function() msg = 'E115: Missing single quote: %.*s', }, }, { - hl('InvalidSingleQuotedString', '\''), + hl('InvalidSingleQuote', '\''), hl('InvalidSingleQuotedBody', 'abc'), hl('InvalidSingleQuotedQuote', '\'\''), }) @@ -5002,11 +5002,11 @@ describe('Expressions parser', function() 'SingleQuotedString(val="\'\'\'"):0:0:\'\'\'\'\'\'\'\'', }, }, { - hl('SingleQuotedString', '\''), + hl('SingleQuote', '\''), hl('SingleQuotedQuote', '\'\''), hl('SingleQuotedQuote', '\'\''), hl('SingleQuotedQuote', '\'\''), - hl('SingleQuotedString', '\''), + hl('SingleQuote', '\''), }) check_parsing('\'\'\'a\'\'\'\'bc\'', 0, { -- 01234567890 @@ -5015,13 +5015,13 @@ describe('Expressions parser', function() 'SingleQuotedString(val="\'a\'\'bc"):0:0:\'\'\'a\'\'\'\'bc\'', }, }, { - hl('SingleQuotedString', '\''), + hl('SingleQuote', '\''), hl('SingleQuotedQuote', '\'\''), hl('SingleQuotedBody', 'a'), hl('SingleQuotedQuote', '\'\''), hl('SingleQuotedQuote', '\'\''), hl('SingleQuotedBody', 'bc'), - hl('SingleQuotedString', '\''), + hl('SingleQuote', '\''), }) check_parsing('"\\"\\"\\"\\""', 0, { -- 0123456789 @@ -5029,12 +5029,12 @@ describe('Expressions parser', function() 'DoubleQuotedString(val="\\"\\"\\"\\""):0:0:"\\"\\"\\"\\""', }, }, { - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), hl('DoubleQuotedEscape', '\\"'), hl('DoubleQuotedEscape', '\\"'), hl('DoubleQuotedEscape', '\\"'), hl('DoubleQuotedEscape', '\\"'), - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), }) check_parsing('"abc\\"def\\"ghi\\"jkl\\"mno"', 0, { -- 0123456789012345678901234 @@ -5043,7 +5043,7 @@ describe('Expressions parser', function() 'DoubleQuotedString(val="abc\\"def\\"ghi\\"jkl\\"mno"):0:0:"abc\\"def\\"ghi\\"jkl\\"mno"', }, }, { - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), hl('DoubleQuotedBody', 'abc'), hl('DoubleQuotedEscape', '\\"'), hl('DoubleQuotedBody', 'def'), @@ -5053,7 +5053,7 @@ describe('Expressions parser', function() hl('DoubleQuotedBody', 'jkl'), hl('DoubleQuotedEscape', '\\"'), hl('DoubleQuotedBody', 'mno'), - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), }) check_parsing('"\\b\\e\\f\\r\\t\\\\"', 0, { -- 0123456789012345 @@ -5062,14 +5062,14 @@ describe('Expressions parser', function() [[DoubleQuotedString(val="\8\27\12\13\9\\"):0:0:"\b\e\f\r\t\\"]], }, }, { - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), hl('DoubleQuotedEscape', '\\b'), hl('DoubleQuotedEscape', '\\e'), hl('DoubleQuotedEscape', '\\f'), hl('DoubleQuotedEscape', '\\r'), hl('DoubleQuotedEscape', '\\t'), hl('DoubleQuotedEscape', '\\\\'), - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), }) check_parsing('"\\n\n"', 0, { -- 01234 @@ -5077,10 +5077,10 @@ describe('Expressions parser', function() 'DoubleQuotedString(val="\\\n\\\n"):0:0:"\\n\n"', }, }, { - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), hl('DoubleQuotedEscape', '\\n'), hl('DoubleQuotedBody', '\n'), - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), }) check_parsing('"\\x00"', 0, { -- 012345 @@ -5088,9 +5088,9 @@ describe('Expressions parser', function() 'DoubleQuotedString(val="\\0"):0:0:"\\x00"', }, }, { - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), hl('DoubleQuotedEscape', '\\x00'), - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), }) check_parsing('"\\xFF"', 0, { -- 012345 @@ -5098,9 +5098,9 @@ describe('Expressions parser', function() 'DoubleQuotedString(val="\255"):0:0:"\\xFF"', }, }, { - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), hl('DoubleQuotedEscape', '\\xFF'), - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), }) check_parsing('"\\xF"', 0, { -- 012345 @@ -5108,9 +5108,9 @@ describe('Expressions parser', function() 'DoubleQuotedString(val="\\15"):0:0:"\\xF"', }, }, { - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), hl('DoubleQuotedEscape', '\\xF'), - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), }) check_parsing('"\\u00AB"', 0, { -- 01234567 @@ -5118,9 +5118,9 @@ describe('Expressions parser', function() 'DoubleQuotedString(val="«"):0:0:"\\u00AB"', }, }, { - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), hl('DoubleQuotedEscape', '\\u00AB'), - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), }) check_parsing('"\\U000000AB"', 0, { -- 01234567 @@ -5128,9 +5128,9 @@ describe('Expressions parser', function() 'DoubleQuotedString(val="«"):0:0:"\\U000000AB"', }, }, { - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), hl('DoubleQuotedEscape', '\\U000000AB'), - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), }) check_parsing('"\\x"', 0, { -- 0123 @@ -5138,9 +5138,9 @@ describe('Expressions parser', function() 'DoubleQuotedString(val="x"):0:0:"\\x"', }, }, { - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), hl('DoubleQuotedUnknownEscape', '\\x'), - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), }) check_parsing('"\\x', 0, { @@ -5153,7 +5153,7 @@ describe('Expressions parser', function() msg = 'E114: Missing double quote: %.*s', }, }, { - hl('InvalidDoubleQuotedString', '"'), + hl('InvalidDoubleQuote', '"'), hl('InvalidDoubleQuotedUnknownEscape', '\\x'), }) @@ -5167,7 +5167,7 @@ describe('Expressions parser', function() msg = 'E114: Missing double quote: %.*s', }, }, { - hl('InvalidDoubleQuotedString', '"'), + hl('InvalidDoubleQuote', '"'), hl('InvalidDoubleQuotedEscape', '\\xF'), }) @@ -5177,9 +5177,9 @@ describe('Expressions parser', function() 'DoubleQuotedString(val="u"):0:0:"\\u"', }, }, { - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), hl('DoubleQuotedUnknownEscape', '\\u'), - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), }) check_parsing('"\\u', 0, { @@ -5192,7 +5192,7 @@ describe('Expressions parser', function() msg = 'E114: Missing double quote: %.*s', }, }, { - hl('InvalidDoubleQuotedString', '"'), + hl('InvalidDoubleQuote', '"'), hl('InvalidDoubleQuotedUnknownEscape', '\\u'), }) @@ -5206,7 +5206,7 @@ describe('Expressions parser', function() msg = 'E114: Missing double quote: %.*s', }, }, { - hl('InvalidDoubleQuotedString', '"'), + hl('InvalidDoubleQuote', '"'), hl('InvalidDoubleQuotedUnknownEscape', '\\U'), }) @@ -5216,9 +5216,9 @@ describe('Expressions parser', function() 'DoubleQuotedString(val="U"):0:0:"\\U"', }, }, { - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), hl('DoubleQuotedUnknownEscape', '\\U'), - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), }) check_parsing('"\\xFX"', 0, { @@ -5227,10 +5227,10 @@ describe('Expressions parser', function() 'DoubleQuotedString(val="\\15X"):0:0:"\\xFX"', }, }, { - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), hl('DoubleQuotedEscape', '\\xF'), hl('DoubleQuotedBody', 'X'), - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), }) check_parsing('"\\XFX"', 0, { @@ -5239,10 +5239,10 @@ describe('Expressions parser', function() 'DoubleQuotedString(val="\\15X"):0:0:"\\XFX"', }, }, { - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), hl('DoubleQuotedEscape', '\\XF'), hl('DoubleQuotedBody', 'X'), - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), }) check_parsing('"\\xX"', 0, { @@ -5251,10 +5251,10 @@ describe('Expressions parser', function() 'DoubleQuotedString(val="xX"):0:0:"\\xX"', }, }, { - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), hl('DoubleQuotedUnknownEscape', '\\x'), hl('DoubleQuotedBody', 'X'), - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), }) check_parsing('"\\XX"', 0, { @@ -5263,10 +5263,10 @@ describe('Expressions parser', function() 'DoubleQuotedString(val="XX"):0:0:"\\XX"', }, }, { - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), hl('DoubleQuotedUnknownEscape', '\\X'), hl('DoubleQuotedBody', 'X'), - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), }) check_parsing('"\\uX"', 0, { @@ -5275,10 +5275,10 @@ describe('Expressions parser', function() 'DoubleQuotedString(val="uX"):0:0:"\\uX"', }, }, { - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), hl('DoubleQuotedUnknownEscape', '\\u'), hl('DoubleQuotedBody', 'X'), - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), }) check_parsing('"\\UX"', 0, { @@ -5287,10 +5287,10 @@ describe('Expressions parser', function() 'DoubleQuotedString(val="UX"):0:0:"\\UX"', }, }, { - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), hl('DoubleQuotedUnknownEscape', '\\U'), hl('DoubleQuotedBody', 'X'), - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), }) check_parsing('"\\x0X"', 0, { @@ -5299,10 +5299,10 @@ describe('Expressions parser', function() 'DoubleQuotedString(val="\\0X"):0:0:"\\x0X"', }, }, { - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), hl('DoubleQuotedEscape', '\\x0'), hl('DoubleQuotedBody', 'X'), - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), }) check_parsing('"\\X0X"', 0, { @@ -5311,10 +5311,10 @@ describe('Expressions parser', function() 'DoubleQuotedString(val="\\0X"):0:0:"\\X0X"', }, }, { - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), hl('DoubleQuotedEscape', '\\X0'), hl('DoubleQuotedBody', 'X'), - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), }) check_parsing('"\\u0X"', 0, { @@ -5323,10 +5323,10 @@ describe('Expressions parser', function() 'DoubleQuotedString(val="\\0X"):0:0:"\\u0X"', }, }, { - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), hl('DoubleQuotedEscape', '\\u0'), hl('DoubleQuotedBody', 'X'), - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), }) check_parsing('"\\U0X"', 0, { @@ -5335,10 +5335,10 @@ describe('Expressions parser', function() 'DoubleQuotedString(val="\\0X"):0:0:"\\U0X"', }, }, { - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), hl('DoubleQuotedEscape', '\\U0'), hl('DoubleQuotedBody', 'X'), - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), }) check_parsing('"\\x00X"', 0, { @@ -5347,10 +5347,10 @@ describe('Expressions parser', function() 'DoubleQuotedString(val="\\0X"):0:0:"\\x00X"', }, }, { - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), hl('DoubleQuotedEscape', '\\x00'), hl('DoubleQuotedBody', 'X'), - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), }) check_parsing('"\\X00X"', 0, { @@ -5359,10 +5359,10 @@ describe('Expressions parser', function() 'DoubleQuotedString(val="\\0X"):0:0:"\\X00X"', }, }, { - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), hl('DoubleQuotedEscape', '\\X00'), hl('DoubleQuotedBody', 'X'), - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), }) check_parsing('"\\u00X"', 0, { @@ -5371,10 +5371,10 @@ describe('Expressions parser', function() 'DoubleQuotedString(val="\\0X"):0:0:"\\u00X"', }, }, { - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), hl('DoubleQuotedEscape', '\\u00'), hl('DoubleQuotedBody', 'X'), - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), }) check_parsing('"\\U00X"', 0, { @@ -5383,10 +5383,10 @@ describe('Expressions parser', function() 'DoubleQuotedString(val="\\0X"):0:0:"\\U00X"', }, }, { - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), hl('DoubleQuotedEscape', '\\U00'), hl('DoubleQuotedBody', 'X'), - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), }) check_parsing('"\\u000X"', 0, { @@ -5395,10 +5395,10 @@ describe('Expressions parser', function() 'DoubleQuotedString(val="\\0X"):0:0:"\\u000X"', }, }, { - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), hl('DoubleQuotedEscape', '\\u000'), hl('DoubleQuotedBody', 'X'), - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), }) check_parsing('"\\U000X"', 0, { @@ -5407,10 +5407,10 @@ describe('Expressions parser', function() 'DoubleQuotedString(val="\\0X"):0:0:"\\U000X"', }, }, { - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), hl('DoubleQuotedEscape', '\\U000'), hl('DoubleQuotedBody', 'X'), - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), }) check_parsing('"\\u0000X"', 0, { @@ -5419,10 +5419,10 @@ describe('Expressions parser', function() 'DoubleQuotedString(val="\\0X"):0:0:"\\u0000X"', }, }, { - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), hl('DoubleQuotedEscape', '\\u0000'), hl('DoubleQuotedBody', 'X'), - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), }) check_parsing('"\\U0000X"', 0, { @@ -5431,10 +5431,10 @@ describe('Expressions parser', function() 'DoubleQuotedString(val="\\0X"):0:0:"\\U0000X"', }, }, { - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), hl('DoubleQuotedEscape', '\\U0000'), hl('DoubleQuotedBody', 'X'), - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), }) check_parsing('"\\U00000X"', 0, { @@ -5443,10 +5443,10 @@ describe('Expressions parser', function() 'DoubleQuotedString(val="\\0X"):0:0:"\\U00000X"', }, }, { - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), hl('DoubleQuotedEscape', '\\U00000'), hl('DoubleQuotedBody', 'X'), - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), }) check_parsing('"\\U000000X"', 0, { @@ -5456,10 +5456,10 @@ describe('Expressions parser', function() 'DoubleQuotedString(val="\\0X"):0:0:"\\U000000X"', }, }, { - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), hl('DoubleQuotedEscape', '\\U000000'), hl('DoubleQuotedBody', 'X'), - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), }) check_parsing('"\\U0000000X"', 0, { @@ -5469,10 +5469,10 @@ describe('Expressions parser', function() 'DoubleQuotedString(val="\\0X"):0:0:"\\U0000000X"', }, }, { - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), hl('DoubleQuotedEscape', '\\U0000000'), hl('DoubleQuotedBody', 'X'), - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), }) check_parsing('"\\U00000000X"', 0, { @@ -5482,10 +5482,10 @@ describe('Expressions parser', function() 'DoubleQuotedString(val="\\0X"):0:0:"\\U00000000X"', }, }, { - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), hl('DoubleQuotedEscape', '\\U00000000'), hl('DoubleQuotedBody', 'X'), - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), }) check_parsing('"\\x000X"', 0, { @@ -5494,10 +5494,10 @@ describe('Expressions parser', function() 'DoubleQuotedString(val="\\0000X"):0:0:"\\x000X"', }, }, { - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), hl('DoubleQuotedEscape', '\\x00'), hl('DoubleQuotedBody', '0X'), - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), }) check_parsing('"\\X000X"', 0, { @@ -5506,10 +5506,10 @@ describe('Expressions parser', function() 'DoubleQuotedString(val="\\0000X"):0:0:"\\X000X"', }, }, { - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), hl('DoubleQuotedEscape', '\\X00'), hl('DoubleQuotedBody', '0X'), - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), }) check_parsing('"\\u00000X"', 0, { @@ -5518,10 +5518,10 @@ describe('Expressions parser', function() 'DoubleQuotedString(val="\\0000X"):0:0:"\\u00000X"', }, }, { - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), hl('DoubleQuotedEscape', '\\u0000'), hl('DoubleQuotedBody', '0X'), - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), }) check_parsing('"\\U000000000X"', 0, { @@ -5531,10 +5531,10 @@ describe('Expressions parser', function() 'DoubleQuotedString(val="\\0000X"):0:0:"\\U000000000X"', }, }, { - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), hl('DoubleQuotedEscape', '\\U00000000'), hl('DoubleQuotedBody', '0X'), - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), }) check_parsing('"\\0"', 0, { @@ -5543,9 +5543,9 @@ describe('Expressions parser', function() 'DoubleQuotedString(val="\\0"):0:0:"\\0"', }, }, { - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), hl('DoubleQuotedEscape', '\\0'), - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), }) check_parsing('"\\00"', 0, { @@ -5554,9 +5554,9 @@ describe('Expressions parser', function() 'DoubleQuotedString(val="\\0"):0:0:"\\00"', }, }, { - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), hl('DoubleQuotedEscape', '\\00'), - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), }) check_parsing('"\\000"', 0, { @@ -5565,9 +5565,9 @@ describe('Expressions parser', function() 'DoubleQuotedString(val="\\0"):0:0:"\\000"', }, }, { - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), hl('DoubleQuotedEscape', '\\000'), - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), }) check_parsing('"\\0000"', 0, { @@ -5576,10 +5576,10 @@ describe('Expressions parser', function() 'DoubleQuotedString(val="\\0000"):0:0:"\\0000"', }, }, { - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), hl('DoubleQuotedEscape', '\\000'), hl('DoubleQuotedBody', '0'), - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), }) check_parsing('"\\8"', 0, { @@ -5588,9 +5588,9 @@ describe('Expressions parser', function() 'DoubleQuotedString(val="8"):0:0:"\\8"', }, }, { - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), hl('DoubleQuotedUnknownEscape', '\\8'), - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), }) check_parsing('"\\08"', 0, { @@ -5599,10 +5599,10 @@ describe('Expressions parser', function() 'DoubleQuotedString(val="\\0008"):0:0:"\\08"', }, }, { - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), hl('DoubleQuotedEscape', '\\0'), hl('DoubleQuotedBody', '8'), - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), }) check_parsing('"\\008"', 0, { @@ -5611,10 +5611,10 @@ describe('Expressions parser', function() 'DoubleQuotedString(val="\\0008"):0:0:"\\008"', }, }, { - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), hl('DoubleQuotedEscape', '\\00'), hl('DoubleQuotedBody', '8'), - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), }) check_parsing('"\\0008"', 0, { @@ -5623,10 +5623,10 @@ describe('Expressions parser', function() 'DoubleQuotedString(val="\\0008"):0:0:"\\0008"', }, }, { - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), hl('DoubleQuotedEscape', '\\000'), hl('DoubleQuotedBody', '8'), - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), }) check_parsing('"\\777"', 0, { @@ -5635,9 +5635,9 @@ describe('Expressions parser', function() 'DoubleQuotedString(val="\255"):0:0:"\\777"', }, }, { - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), hl('DoubleQuotedEscape', '\\777'), - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), }) check_parsing('"\\050"', 0, { @@ -5646,9 +5646,9 @@ describe('Expressions parser', function() 'DoubleQuotedString(val="\40"):0:0:"\\050"', }, }, { - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), hl('DoubleQuotedEscape', '\\050'), - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), }) check_parsing('"\\"', 0, { @@ -5657,9 +5657,9 @@ describe('Expressions parser', function() 'DoubleQuotedString(val="\\21"):0:0:"\\"', }, }, { - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), hl('DoubleQuotedEscape', '\\'), - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), }) check_parsing('"\\<', 0, { @@ -5672,7 +5672,7 @@ describe('Expressions parser', function() msg = 'E114: Missing double quote: %.*s', }, }, { - hl('InvalidDoubleQuotedString', '"'), + hl('InvalidDoubleQuote', '"'), hl('InvalidDoubleQuotedUnknownEscape', '\\<'), }) @@ -5682,9 +5682,9 @@ describe('Expressions parser', function() 'DoubleQuotedString(val="<"):0:0:"\\<"', }, }, { - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), hl('DoubleQuotedUnknownEscape', '\\<'), - hl('DoubleQuotedString', '"'), + hl('DoubleQuote', '"'), }) check_parsing('"\\ Date: Mon, 30 Oct 2017 01:32:10 +0300 Subject: *: Fix linter errors Big function in expressions.c may be refactored, if I ever catch the idea how to split it right. --- src/nvim/edit.c | 2 +- src/nvim/keymap.c | 24 ++++++++++++------------ src/nvim/mbyte.c | 26 +++++++++++++++++--------- src/nvim/syntax.c | 25 ++++++++++++------------- src/nvim/viml/parser/expressions.c | 8 ++++---- src/nvim/viml/parser/expressions.h | 2 +- 6 files changed, 47 insertions(+), 40 deletions(-) diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 28722a4d10..859f98d2ad 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -6080,7 +6080,7 @@ char_u *add_char2buf(int c, char_u *s) { char_u temp[MB_MAXBYTES + 1]; const int len = utf_char2bytes(c, temp); - for (int i = 0; i < len; ++i) { + for (int i = 0; i < len; i++) { c = temp[i]; // Need to escape K_SPECIAL and CSI like in the typeahead buffer. if (c == K_SPECIAL) { diff --git a/src/nvim/keymap.c b/src/nvim/keymap.c index 6ed54464e8..0c8e47b02e 100644 --- a/src/nvim/keymap.c +++ b/src/nvim/keymap.c @@ -25,21 +25,21 @@ */ static const struct modmasktable { - short mod_mask; ///< Bit-mask for particular key modifier. - short mod_flag; ///< Bit(s) for particular key modifier. + uint16_t mod_mask; ///< Bit-mask for particular key modifier. + uint16_t mod_flag; ///< Bit(s) for particular key modifier. char_u name; ///< Single letter name of modifier. } mod_mask_table[] = { - {MOD_MASK_ALT, MOD_MASK_ALT, (char_u)'M'}, - {MOD_MASK_META, MOD_MASK_META, (char_u)'T'}, - {MOD_MASK_CTRL, MOD_MASK_CTRL, (char_u)'C'}, - {MOD_MASK_SHIFT, MOD_MASK_SHIFT, (char_u)'S'}, - {MOD_MASK_MULTI_CLICK, MOD_MASK_2CLICK, (char_u)'2'}, - {MOD_MASK_MULTI_CLICK, MOD_MASK_3CLICK, (char_u)'3'}, - {MOD_MASK_MULTI_CLICK, MOD_MASK_4CLICK, (char_u)'4'}, - {MOD_MASK_CMD, MOD_MASK_CMD, (char_u)'D'}, + { MOD_MASK_ALT, MOD_MASK_ALT, (char_u)'M' }, + { MOD_MASK_META, MOD_MASK_META, (char_u)'T' }, + { MOD_MASK_CTRL, MOD_MASK_CTRL, (char_u)'C' }, + { MOD_MASK_SHIFT, MOD_MASK_SHIFT, (char_u)'S' }, + { MOD_MASK_MULTI_CLICK, MOD_MASK_2CLICK, (char_u)'2' }, + { MOD_MASK_MULTI_CLICK, MOD_MASK_3CLICK, (char_u)'3' }, + { MOD_MASK_MULTI_CLICK, MOD_MASK_4CLICK, (char_u)'4' }, + { MOD_MASK_CMD, MOD_MASK_CMD, (char_u)'D' }, // 'A' must be the last one - {MOD_MASK_ALT, MOD_MASK_ALT, (char_u)'A'}, - {0, 0, NUL} + { MOD_MASK_ALT, MOD_MASK_ALT, (char_u)'A' }, + { 0, 0, NUL } }; /* diff --git a/src/nvim/mbyte.c b/src/nvim/mbyte.c index 843007b97b..008bce6df6 100644 --- a/src/nvim/mbyte.c +++ b/src/nvim/mbyte.c @@ -98,15 +98,23 @@ const uint8_t utf8len_tab[] = { // Like utf8len_tab above, but using a zero for illegal lead bytes. const uint8_t utf8len_tab_zero[] = { - //1 2 3 4 5 6 7 8 9 A B C D E F 0 1 2 3 4 5 6 7 8 9 A B C D E F - 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 0 - 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 2 - 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 4 - 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 6 - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 8 - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // A - 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, // C - 3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,5,5,5,5,6,6,0,0, // E + // ?1 ?2 ?3 ?4 ?5 ?6 ?7 ?8 ?9 ?A ?B ?C ?D ?E ?F + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 1? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 2? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 3? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 4? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 5? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 6? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 7? + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 8? + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 9? + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // A? + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // B? + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // C? + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // D? + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, // E? + 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 0, 0, // F? }; /* diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index d787790bc3..e0bf74567d 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -5930,7 +5930,8 @@ static void syntime_report(void) // When making changes here, also change runtime/colors/default.vim! static const char *highlight_init_both[] = { - "Conceal ctermbg=DarkGrey ctermfg=LightGrey guibg=DarkGrey guifg=LightGrey", + "Conceal " + "ctermbg=DarkGrey ctermfg=LightGrey guibg=DarkGrey guifg=LightGrey", "Cursor guibg=fg guifg=bg", "lCursor guibg=fg guifg=bg", "DiffText cterm=bold ctermbg=Red gui=bold guibg=Red", @@ -6211,11 +6212,9 @@ void syn_init_cmdline_highlight(bool reset, bool init) /// /// @param both include groups where 'bg' doesn't matter /// @param reset clear groups first -void -init_highlight(int both, int reset) +void init_highlight(bool both, bool reset) { - int i; - static int had_both = FALSE; + static int had_both = false; // Try finding the color scheme file. Used when a color file was loaded // and 'background' or 't_Co' is changed. @@ -6235,9 +6234,9 @@ init_highlight(int both, int reset) * Didn't use a color file, use the compiled-in colors. */ if (both) { - had_both = TRUE; + had_both = true; const char *const *const pp = highlight_init_both; - for (i = 0; pp[i] != NULL; i++) { + for (size_t i = 0; pp[i] != NULL; i++) { do_highlight(pp[i], reset, true); } } else if (!had_both) { @@ -6250,7 +6249,7 @@ init_highlight(int both, int reset) const char *const *const pp = ((*p_bg == 'l') ? highlight_init_light : highlight_init_dark); - for (i = 0; pp[i] != NULL; i++) { + for (size_t i = 0; pp[i] != NULL; i++) { do_highlight(pp[i], reset, true); } @@ -6266,8 +6265,9 @@ init_highlight(int both, int reset) : "Visual cterm=NONE ctermbg=DarkGrey"), false, true); } else { do_highlight("Visual cterm=reverse ctermbg=NONE", false, true); - if (*p_bg == 'l') + if (*p_bg == 'l') { do_highlight("Search ctermfg=black", false, true); + } } /* @@ -6452,7 +6452,7 @@ void do_highlight(const char *line, const bool forceit, const bool init) restore_cterm_colors(); // Clear all default highlight groups and load the defaults. - for (int idx = 0; idx < highlight_ga.ga_len; ++idx) { + for (int idx = 0; idx < highlight_ga.ga_len; idx++) { highlight_clear(idx); } init_highlight(true, true); @@ -6690,9 +6690,8 @@ void do_highlight(const char *line, const bool forceit, const bool init) } } if (i < 0) { - emsgf(_( - "E421: Color name or number not recognized: %s"), - key_start); + emsgf(_("E421: Color name or number not recognized: %s"), + key_start); error = true; break; } diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index f5bc547d54..fc184f56f5 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -1814,8 +1814,8 @@ ExprAST viml_pexpr_parse(ParserState *const pstate, const int flags) || ((*kv_Z(ast_stack, 1))->type != kExprNodeConcat && ((*kv_Z(ast_stack, 1))->type != kExprNodeConcatOrSubscript)))) - ? kELFlagAllowFloat - : 0)); + ? kELFlagAllowFloat + : 0)); LexExprToken cur_token = viml_pexpr_next_token( pstate, want_node_to_lexer_flags[want_node] | lexer_additional_flags); if (cur_token.type == kExprLexEOC) { @@ -1876,7 +1876,7 @@ viml_pexpr_parse_process_token: // time. // // Here example will always contain a concat with "a:2" sucking colon, - // making expression invalid both because there is no longer a spare colon + // making expression invalid both because there is no longer a spare colon // for ternary and because concatenating dictionary with anything is not // valid. There are more cases when this will make a difference though. const bool node_is_key = ( @@ -2853,7 +2853,7 @@ viml_pexpr_parse_end: } kvi_destroy(ast_stack); return ast; -} +} // NOLINT(readability/fn_size) #undef NEW_NODE #undef HL diff --git a/src/nvim/viml/parser/expressions.h b/src/nvim/viml/parser/expressions.h index d00d4855f3..668c2a4c84 100644 --- a/src/nvim/viml/parser/expressions.h +++ b/src/nvim/viml/parser/expressions.h @@ -189,7 +189,7 @@ typedef enum { kExprNodeCall, ///< Function call. /// Plain identifier: simple variable/function name /// - /// Looks like "string", "g:Foo", etc: consists from a single + /// Looks like "string", "g:Foo", etc: consists from a single /// kExprLexPlainIdentifier token. kExprNodePlainIdentifier, /// Plain dictionary key, for use with kExprNodeConcatOrSubscript -- cgit From 0356dbbb36ffa7934c35e9dbfc6667f9118c244f Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 30 Oct 2017 01:38:02 +0300 Subject: ex_getln: Fix variable name which is wrong after the merge --- src/nvim/ex_getln.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 785038f5d6..f64efe08a1 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -2500,7 +2500,7 @@ static bool color_cmdline(CmdlineInfo *colored_ccline) tl_ret = try_leave(&tstate, &err); can_free_cb = true; } else if (colored_ccline->cmdfirstc == '=') { - color_expr_cmdline(colored_ccline, ret_ccline_colors); + color_expr_cmdline(colored_ccline, ccline_colors); can_free_cb = false; } if (!tl_ret || !dgc_ret) { -- cgit From 3ecb95298ffd9ef6ee681876f2d32553fd222b96 Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 30 Oct 2017 01:48:32 +0300 Subject: tests: Fix testlint errors --- test/functional/ui/cmdline_highlight_spec.lua | 4 ++++ test/helpers.lua | 4 ++-- test/unit/helpers.lua | 6 +++--- test/unit/viml/expressions/lexer_spec.lua | 3 +-- test/unit/viml/expressions/parser_spec.lua | 9 ++------- test/unit/viml/helpers.lua | 6 ------ 6 files changed, 12 insertions(+), 20 deletions(-) diff --git a/test/functional/ui/cmdline_highlight_spec.lua b/test/functional/ui/cmdline_highlight_spec.lua index 60a4a815e7..b16b4a5602 100644 --- a/test/functional/ui/cmdline_highlight_spec.lua +++ b/test/functional/ui/cmdline_highlight_spec.lua @@ -898,4 +898,8 @@ describe('Expressions coloring support', function() ={NUM:1}^ | ]]) end) + -- FIXME: Test expr coloring when using -u NORC and -u NONE. + -- FIXME: Test all highlight groups, using long expression. + -- FIXME: Test different ways of triggering expression highlighting (:=, + -- i=, :e, "=). end) diff --git a/test/helpers.lua b/test/helpers.lua index 83e78ba059..10f2f80191 100644 --- a/test/helpers.lua +++ b/test/helpers.lua @@ -376,8 +376,8 @@ local function format_string(fmt, ...) return args[i] end local ret = fmt:gsub('%%[0-9*]*%.?[0-9*]*[cdEefgGiouXxqsr%%]', function(match) - local subfmt = match:gsub('%*', function(match) - return getarg() + local subfmt = match:gsub('%*', function() + return tostring(getarg()) end) local arg = nil if subfmt:sub(-1) ~= '%' then diff --git a/test/unit/helpers.lua b/test/unit/helpers.lua index 68ce9eed62..96aa505739 100644 --- a/test/unit/helpers.lua +++ b/test/unit/helpers.lua @@ -791,7 +791,7 @@ local function kvi_new(ct) return kvi_init(ffi.new(ct)) end -local function make_enum_conv_tab(lib, values, skip_pref, set_cb) +local function make_enum_conv_tab(m, values, skip_pref, set_cb) child_call_once(function() local ret = {} for _, v in ipairs(values) do @@ -799,7 +799,7 @@ local function make_enum_conv_tab(lib, values, skip_pref, set_cb) if v:sub(1, #skip_pref) == skip_pref then str_v = v:sub(#skip_pref + 1) end - ret[tonumber(lib[v])] = str_v + ret[tonumber(m[v])] = str_v end set_cb(ret) end) @@ -837,7 +837,7 @@ local module = { child_cleanup_once = child_cleanup_once, sc = sc, conv_enum = conv_enum, - array_size = array_sive, + array_size = array_size, kvi_destroy = kvi_destroy, kvi_size = kvi_size, kvi_init = kvi_init, diff --git a/test/unit/viml/expressions/lexer_spec.lua b/test/unit/viml/expressions/lexer_spec.lua index 5910468017..d4ec870a4e 100644 --- a/test/unit/viml/expressions/lexer_spec.lua +++ b/test/unit/viml/expressions/lexer_spec.lua @@ -10,7 +10,6 @@ local ffi = helpers.ffi local eq = helpers.eq local conv_ccs = viml_helpers.conv_ccs -local pline2lua = viml_helpers.pline2lua local new_pstate = viml_helpers.new_pstate local intchar2lua = viml_helpers.intchar2lua local conv_cmp_type = viml_helpers.conv_cmp_type @@ -183,7 +182,7 @@ describe('Expressions lexer', function() end local function simple_test(pstate_arg, exp_type, exp_len, exp) local pstate = new_pstate(pstate_arg) - local exp = shallowcopy(exp) + exp = shallowcopy(exp) exp.type = exp_type exp.len = exp_len or #(pstate_arg[0]) exp.start = { col = 0, line = 0 } diff --git a/test/unit/viml/expressions/parser_spec.lua b/test/unit/viml/expressions/parser_spec.lua index 019fe69046..df69c60dd0 100644 --- a/test/unit/viml/expressions/parser_spec.lua +++ b/test/unit/viml/expressions/parser_spec.lua @@ -14,7 +14,6 @@ local ffi = helpers.ffi local eq = helpers.eq local conv_ccs = viml_helpers.conv_ccs -local pline2lua = viml_helpers.pline2lua local new_pstate = viml_helpers.new_pstate local intchar2lua = viml_helpers.intchar2lua local conv_cmp_type = viml_helpers.conv_cmp_type @@ -174,10 +173,7 @@ local function eastnode2lua(pstate, eastnode, checked_nodes) ffi.string(eastnode.data.env.ident, eastnode.data.env.ident_len)) end ret_str = typ .. ':' .. ret_str - local can_simplify = true - for k, v in pairs(ret) do - can_simplify = false - end + local can_simplify = not ret.children if can_simplify then ret = ret_str else @@ -218,12 +214,11 @@ local function phl2lua(pstate) pstate, chunk.start, chunk.end_col - chunk.start.col, { group = ffi.string(chunk.group), }) - chunk_str = ('%s:%u:%u:%s'):format( + ret[i + 1] = ('%s:%u:%u:%s'):format( chunk_tbl.group, chunk_tbl.start.line, chunk_tbl.start.col, chunk_tbl.str) - ret[i + 1] = chunk_str end return ret end diff --git a/test/unit/viml/helpers.lua b/test/unit/viml/helpers.lua index c965cacb29..70949e8278 100644 --- a/test/unit/viml/helpers.lua +++ b/test/unit/viml/helpers.lua @@ -5,7 +5,6 @@ local cimport = helpers.cimport local kvi_new = helpers.kvi_new local kvi_init = helpers.kvi_init local conv_enum = helpers.conv_enum -local child_call_once = helpers.child_call_once local make_enum_conv_tab = helpers.make_enum_conv_tab local lib = cimport('./src/nvim/viml/parser/expressions.h') @@ -33,11 +32,6 @@ local function new_pstate(strings) ret_pline.size = size ret_pline.allocated = false end - local pline_init = { - data = nil, - size = 0, - allocated = false, - } local state = { reader = { get_line = get_line, -- cgit From d98199de9c2969d430ba489187ffa02d8c489dea Mon Sep 17 00:00:00 2001 From: ZyX Date: Thu, 2 Nov 2017 10:44:20 +0300 Subject: charset: Refactor vim_str2nr --- src/nvim/charset.c | 135 ++++++++++++++++++++++------------ test/symbolic/klee/nvim/charset.c | 129 ++++++++++++++++++++------------ test/unit/charset/vim_str2nr_spec.lua | 1 + 3 files changed, 173 insertions(+), 92 deletions(-) create mode 100644 test/unit/charset/vim_str2nr_spec.lua diff --git a/src/nvim/charset.c b/src/nvim/charset.c index c895d65eb7..5aebf90194 100644 --- a/src/nvim/charset.c +++ b/src/nvim/charset.c @@ -1611,8 +1611,9 @@ bool vim_isblankline(char_u *lbuf) /// If maxlen > 0, check at a maximum maxlen chars. /// /// @param start -/// @param prep Returns type of number 0 = decimal, 'x' or 'X' is hex, -/// '0' = octal, 'b' or 'B' is bin +/// @param prep Returns guessed type of number 0 = decimal, 'x' or 'X' is +/// hexadecimal, '0' = octal, 'b' or 'B' is binary. When using +/// STR2NR_FORCE is always zero. /// @param len Returns the detected length of number. /// @param what Recognizes what number passed. /// @param nptr Returns the signed result. @@ -1627,55 +1628,84 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, #define STRING_ENDED(ptr) \ (!(maxlen == 0 || (int)((ptr) - (const char *)start) < maxlen)) int pre = 0; // default is decimal - bool negative = false; + const bool negative = (ptr[0] == '-'); + uvarnumber_T un = 0; - if (ptr[0] == '-') { - negative = true; + if (negative) { ptr++; } - // Recognize hex, octal and bin. - if ((what & (STR2NR_HEX|STR2NR_OCT|STR2NR_BIN)) - && !STRING_ENDED(ptr + 1) - && ptr[0] == '0' && ptr[1] != '8' && ptr[1] != '9') { + if (what & STR2NR_FORCE) { + // When forcing main consideration is skipping the prefix. Octal and decimal + // numbers have no prefixes to skip. pre is not set. + switch ((unsigned)what & (~(unsigned)STR2NR_FORCE)) { + case STR2NR_HEX: { + if (!STRING_ENDED(ptr + 2) + && ptr[0] == '0' + && (ptr[1] == 'x' || ptr[1] == 'X') + && ascii_isxdigit(ptr[2])) { + ptr += 2; + } + goto vim_str2nr_hex; + } + case STR2NR_BIN: { + if (!STRING_ENDED(ptr + 2) + && ptr[0] == '0' + && (ptr[1] == 'b' || ptr[1] == 'B') + && ascii_isbdigit(ptr[2])) { + ptr += 2; + } + goto vim_str2nr_bin; + } + case STR2NR_OCT: { + goto vim_str2nr_oct; + } + case 0: { + goto vim_str2nr_dec; + } + default: { + assert(false); + } + } + } else if ((what & (STR2NR_HEX|STR2NR_OCT|STR2NR_BIN)) + && !STRING_ENDED(ptr + 1) + && ptr[0] == '0' && ptr[1] != '8' && ptr[1] != '9') { pre = ptr[1]; - + // Detect hexadecimal: 0x or 0X follwed by hex digit if ((what & STR2NR_HEX) && !STRING_ENDED(ptr + 2) && (pre == 'X' || pre == 'x') && ascii_isxdigit(ptr[2])) { - // hexadecimal ptr += 2; - } else if ((what & STR2NR_BIN) - && !STRING_ENDED(ptr + 2) - && (pre == 'B' || pre == 'b') - && ascii_isbdigit(ptr[2])) { - // binary + goto vim_str2nr_hex; + } + // Detect binary: 0b or 0B follwed by 0 or 1 + if ((what & STR2NR_BIN) + && !STRING_ENDED(ptr + 2) + && (pre == 'B' || pre == 'b') + && ascii_isbdigit(ptr[2])) { ptr += 2; - } else { - // decimal or octal, default is decimal - pre = 0; - - if (what & STR2NR_OCT - && !STRING_ENDED(ptr + 1) - && ('0' <= ptr[1] && ptr[1] <= '7')) { - // Assume octal now: what we already know is that string starts with - // zero and some octal digit. - pre = '0'; - // Don’t interpret "0", "008" or "0129" as octal. - for (int i = 2; !STRING_ENDED(ptr + i) && ascii_isdigit(ptr[i]); i++) { - if (ptr[i] > '7') { - // Can’t be octal. - pre = 0; - break; - } - } + goto vim_str2nr_bin; + } + // Detect octal number: zero followed by octal digits without '8' or '9' + pre = 0; + if (!(what & STR2NR_OCT) + || !('0' <= ptr[1] && ptr[1] <= '7')) { + goto vim_str2nr_dec; + } + for (int i = 2; !STRING_ENDED(ptr + i) && ascii_isdigit(ptr[i]); i++) { + if (ptr[i] > '7') { + goto vim_str2nr_dec; } } + pre = '0'; + goto vim_str2nr_oct; + } else { + goto vim_str2nr_dec; } // Do the string-to-numeric conversion "manually" to avoid sscanf quirks. - uvarnumber_T un = 0; + assert(false); // Should’ve used goto earlier. #define PARSE_NUMBER(base, cond, conv) \ do { \ while (!STRING_ENDED(ptr) && (cond)) { \ @@ -1688,18 +1718,29 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, ptr++; \ } \ } while (0) - if (pre == 'B' || pre == 'b' || what == (STR2NR_BIN|STR2NR_FORCE)) { - // Binary number. - PARSE_NUMBER(2, (*ptr == '0' || *ptr == '1'), (*ptr - '0')); - } else if (pre == '0' || what == (STR2NR_OCT|STR2NR_FORCE)) { - // Octal number. - PARSE_NUMBER(8, ('0' <= *ptr && *ptr <= '7'), (*ptr - '0')); - } else if (pre == 'X' || pre == 'x' || what == (STR2NR_HEX|STR2NR_FORCE)) { - // Hexadecimal number. - PARSE_NUMBER(16, (ascii_isxdigit(*ptr)), (hex2nr(*ptr))); - } else { - // Decimal number. - PARSE_NUMBER(10, (ascii_isdigit(*ptr)), (*ptr - '0')); + switch (pre) { + case 'b': + case 'B': { +vim_str2nr_bin: + PARSE_NUMBER(2, (*ptr == '0' || *ptr == '1'), (*ptr - '0')); + break; + } + case '0': { +vim_str2nr_oct: + PARSE_NUMBER(8, ('0' <= *ptr && *ptr <= '7'), (*ptr - '0')); + break; + } + case 0: { +vim_str2nr_dec: + PARSE_NUMBER(10, (ascii_isdigit(*ptr)), (*ptr - '0')); + break; + } + case 'x': + case 'X': { +vim_str2nr_hex: + PARSE_NUMBER(16, (ascii_isxdigit(*ptr)), (hex2nr(*ptr))); + break; + } } #undef PARSE_NUMBER diff --git a/test/symbolic/klee/nvim/charset.c b/test/symbolic/klee/nvim/charset.c index f3a218949f..77f690f08d 100644 --- a/test/symbolic/klee/nvim/charset.c +++ b/test/symbolic/klee/nvim/charset.c @@ -31,55 +31,83 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, #define STRING_ENDED(ptr) \ (!(maxlen == 0 || (int)((ptr) - (const char *)start) < maxlen)) int pre = 0; // default is decimal - bool negative = false; + const bool negative = (ptr[0] == '-'); + uvarnumber_T un = 0; - if (ptr[0] == '-') { - negative = true; + if (negative) { ptr++; } - // Recognize hex, octal and bin. - if ((what & (STR2NR_HEX|STR2NR_OCT|STR2NR_BIN)) - && !STRING_ENDED(ptr + 1) - && ptr[0] == '0' && ptr[1] != '8' && ptr[1] != '9') { + if (what & STR2NR_FORCE) { + // When forcing main consideration is skipping the prefix. Octal and decimal + // numbers have no prefixes to skip. pre is not set. + switch ((unsigned)what & (~(unsigned)STR2NR_FORCE)) { + case STR2NR_HEX: { + if (!STRING_ENDED(ptr + 2) + && ptr[0] == '0' + && (ptr[1] == 'x' || ptr[1] == 'X') + && ascii_isxdigit(ptr[2])) { + ptr += 2; + } + goto vim_str2nr_hex; + } + case STR2NR_BIN: { + if (!STRING_ENDED(ptr + 2) + && ptr[0] == '0' + && (ptr[1] == 'b' || ptr[1] == 'B') + && ascii_isbdigit(ptr[2])) { + ptr += 2; + } + goto vim_str2nr_bin; + } + case STR2NR_OCT: { + goto vim_str2nr_oct; + } + case 0: { + goto vim_str2nr_dec; + } + default: { + assert(false); + } + } + } else if ((what & (STR2NR_HEX|STR2NR_OCT|STR2NR_BIN)) + && !STRING_ENDED(ptr + 1) + && ptr[0] == '0' && ptr[1] != '8' && ptr[1] != '9') { pre = ptr[1]; - + // Detect hexadecimal: 0x or 0X follwed by hex digit if ((what & STR2NR_HEX) && !STRING_ENDED(ptr + 2) && (pre == 'X' || pre == 'x') && ascii_isxdigit(ptr[2])) { - // hexadecimal ptr += 2; - } else if ((what & STR2NR_BIN) - && !STRING_ENDED(ptr + 2) - && (pre == 'B' || pre == 'b') - && ascii_isbdigit(ptr[2])) { - // binary + goto vim_str2nr_hex; + } + // Detect binary: 0b or 0B follwed by 0 or 1 + if ((what & STR2NR_BIN) + && !STRING_ENDED(ptr + 2) + && (pre == 'B' || pre == 'b') + && ascii_isbdigit(ptr[2])) { ptr += 2; - } else { - // decimal or octal, default is decimal - pre = 0; - - if (what & STR2NR_OCT - && !STRING_ENDED(ptr + 1) - && ('0' <= ptr[1] && ptr[1] <= '7')) { - // Assume octal now: what we already know is that string starts with - // zero and some octal digit. - pre = '0'; - // Don’t interpret "0", "008" or "0129" as octal. - for (int i = 2; !STRING_ENDED(ptr + i) && ascii_isdigit(ptr[i]); i++) { - if (ptr[i] > '7') { - // Can’t be octal. - pre = 0; - break; - } - } + goto vim_str2nr_bin; + } + // Detect octal number: zero followed by octal digits without '8' or '9' + pre = 0; + if (!(what & STR2NR_OCT)) { + goto vim_str2nr_dec; + } + for (int i = 2; !STRING_ENDED(ptr + i) && ascii_isdigit(ptr[i]); i++) { + if (ptr[i] > '7') { + goto vim_str2nr_dec; } } + pre = '0'; + goto vim_str2nr_oct; + } else { + goto vim_str2nr_dec; } // Do the string-to-numeric conversion "manually" to avoid sscanf quirks. - uvarnumber_T un = 0; + assert(false); // Should’ve used goto earlier. #define PARSE_NUMBER(base, cond, conv) \ do { \ while (!STRING_ENDED(ptr) && (cond)) { \ @@ -92,18 +120,29 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, ptr++; \ } \ } while (0) - if (pre == 'B' || pre == 'b' || what == (STR2NR_BIN|STR2NR_FORCE)) { - // Binary number. - PARSE_NUMBER(2, (*ptr == '0' || *ptr == '1'), (*ptr - '0')); - } else if (pre == '0' || what == (STR2NR_OCT|STR2NR_FORCE)) { - // Octal number. - PARSE_NUMBER(8, ('0' <= *ptr && *ptr <= '7'), (*ptr - '0')); - } else if (pre == 'X' || pre == 'x' || what == (STR2NR_HEX|STR2NR_FORCE)) { - // Hexadecimal number. - PARSE_NUMBER(16, (ascii_isxdigit(*ptr)), (hex2nr(*ptr))); - } else { - // Decimal number. - PARSE_NUMBER(10, (ascii_isdigit(*ptr)), (*ptr - '0')); + switch (pre) { + case 'b': + case 'B': { +vim_str2nr_bin: + PARSE_NUMBER(2, (*ptr == '0' || *ptr == '1'), (*ptr - '0')); + break; + } + case '0': { +vim_str2nr_oct: + PARSE_NUMBER(8, ('0' <= *ptr && *ptr <= '7'), (*ptr - '0')); + break; + } + case 0: { +vim_str2nr_dec: + PARSE_NUMBER(10, (ascii_isdigit(*ptr)), (*ptr - '0')); + break; + } + case 'x': + case 'X': { +vim_str2nr_hex: + PARSE_NUMBER(16, (ascii_isxdigit(*ptr)), (hex2nr(*ptr))); + break; + } } #undef PARSE_NUMBER diff --git a/test/unit/charset/vim_str2nr_spec.lua b/test/unit/charset/vim_str2nr_spec.lua new file mode 100644 index 0000000000..cfbc77bc2a --- /dev/null +++ b/test/unit/charset/vim_str2nr_spec.lua @@ -0,0 +1 @@ +-- FIXME -- cgit From b9d5aea073521f3278bea257be306b7ac2b8d83c Mon Sep 17 00:00:00 2001 From: ZyX Date: Fri, 3 Nov 2017 11:38:59 +0300 Subject: api/vim: Create part of nvim_parse_expression function --- src/nvim/api/vim.c | 106 ++++++++++++++++++++++++++++++++++++++- test/functional/api/vim_spec.lua | 5 ++ 2 files changed, 110 insertions(+), 1 deletion(-) diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 9daa2fb398..446a049897 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -33,6 +33,8 @@ #include "nvim/syntax.h" #include "nvim/getchar.h" #include "nvim/os/input.h" +#include "nvim/viml/parser/expressions.h" +#include "nvim/viml/parser/parser.h" #define LINE_BUFFER_SIZE 4096 @@ -897,6 +899,12 @@ theend: /// /// Use only "m" to parse like for "=", only "E" to /// parse like for ":echo", empty string for ":let". +/// @param[in] highlight If true, return value will also include "highlight" +/// key containing array of 4-tuples (arrays) (Integer, +/// Integer, Integer, String), where first three numbers +/// define the highlighted region and represent line, +/// starting column and ending column (latter exclusive: +/// one should highlight region [start_col, end_col)). /// /// @return AST: top-level dectionary holds keys /// @@ -946,9 +954,105 @@ theend: /// "fvalue": Float, floating-point value for "Float" nodes. /// "svalue": String, value for "SingleQuotedString" and /// "DoubleQuotedString" nodes. -Dictionary nvim_parse_expression(String expr, String flags, Error *err) +Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, + Error *err) FUNC_API_SINCE(4) { + int pflags = 0; + for (size_t i = 0 ; i < flags.size ; i++) { + switch (flags.data[i]) { + case 'm': { pflags |= kExprFlagsMulti; break; } + case 'E': { pflags |= kExprFlagsDisallowEOC; break; } + default: { + api_set_error(err, kErrorTypeValidation, "Invalid flag: '%c' (%u)", + flags.data[i], (unsigned)flags.data[i]); + return (Dictionary)ARRAY_DICT_INIT; + } + } + } + ParserLine plines[] = { + { + .data = expr.data, + .size = expr.size, + .allocated = false, + }, + { NULL, 0, false }, + }; + ParserLine *plines_p = plines; + ParserHighlight colors; + kvi_init(colors); + ParserHighlight *const colors_p = (highlight ? &colors : NULL); + ParserState pstate; + viml_parser_init( + &pstate, parser_simple_get_line, &plines_p, colors_p); + ExprAST east = viml_pexpr_parse(&pstate, pflags); + + const size_t dict_size = ( + 1 // "ast" + + (size_t)(east.err.arg != NULL) // "error" + + (size_t)highlight // "highlight" + ); + Dictionary ret = { + .items = xmalloc(dict_size * sizeof(ret.items[0])), + .size = 0, + .capacity = dict_size, + }; + ret.items[ret.size++] = (KeyValuePair) { + .key = STATIC_CSTR_AS_STRING("ast"), + .value = NIL, + }; + if (east.err.arg != NULL) { + Dictionary err_dict = { + .items = xmalloc(2 * sizeof(err_dict.items[0])), + .size = 2, + .capacity = 2, + }; + err_dict.items[0] = (KeyValuePair) { + .key = STATIC_CSTR_AS_STRING("message"), + .value = STRING_OBJ(cstr_to_string(east.err.arg)), + }; + err_dict.items[1] = (KeyValuePair) { + .key = STATIC_CSTR_AS_STRING("arg"), + .value = STRING_OBJ(((String) { + .data = xmemdup(east.err.arg, (size_t)east.err.arg_len), + .size = (size_t)east.err.arg_len, + })), + }; + ret.items[ret.size++] = (KeyValuePair) { + .key = STATIC_CSTR_AS_STRING("error"), + .value = DICTIONARY_OBJ(err_dict), + }; + } + if (highlight) { + Array hl = (Array) { + .items = xmalloc(kv_size(colors) * sizeof(hl.items[0])), + .capacity = kv_size(colors), + .size = kv_size(colors), + }; + for (size_t i = 0 ; i < kv_size(colors) ; i++) { + const ParserHighlightChunk chunk = kv_A(colors, i); + Array chunk_arr = (Array) { + .items = xmalloc(4), + .capacity = 4, + .size = 4, + }; + chunk_arr.items[0] = INTEGER_OBJ((Integer)chunk.start.line); + chunk_arr.items[1] = INTEGER_OBJ((Integer)chunk.start.col); + chunk_arr.items[2] = INTEGER_OBJ((Integer)chunk.end_col); + chunk_arr.items[3] = STRING_OBJ(cstr_to_string(chunk.group)); + hl.items[i] = ARRAY_OBJ(chunk_arr); + } + ret.items[ret.size++] = (KeyValuePair) { + .key = STATIC_CSTR_AS_STRING("highlight"), + .value = ARRAY_OBJ(hl), + }; + } + + // FIXME: populate AST + + assert(ret.size == ret.capacity); + viml_pexpr_free_ast(east); + viml_parser_destroy(&pstate); return (Dictionary)ARRAY_DICT_INIT; } diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index b849304d45..6b44698638 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -710,4 +710,9 @@ describe('api', function() ok(err:match(': Wrong type for argument 1, expecting String') ~= nil) end) + describe('nvim_parse_expression', function() + -- FIXME + -- FIXME Test error + end) + end) -- cgit From 07ec709141886c6db4f944665e07a36ef7302eb4 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 5 Nov 2017 01:33:44 +0300 Subject: vim/api: Actually dump AST, fix some bugs in nvim_parse_expression --- src/nvim/api/vim.c | 264 ++++++++++++++++++++++++++++++++++--- src/nvim/viml/parser/expressions.c | 10 +- src/nvim/viml/parser/expressions.h | 9 ++ 3 files changed, 262 insertions(+), 21 deletions(-) diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 446a049897..1aab87010e 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -888,6 +888,13 @@ theend: return rv; } +typedef struct { + ExprASTNode **node_p; + Object *ret_node_p; +} ExprASTConvStackItem; + +typedef kvec_withinit_t(ExprASTConvStackItem, 16) ExprASTConvStack; + /// Parse a VimL expression /// /// @param[in] expr Expression to parse. Is always treated as a single line. @@ -915,7 +922,8 @@ theend: /// Must contain exactly one "%.*s". /// "arg": String, error message argument. /// -/// "ast": actual AST, a dictionary with the following keys: +/// "ast": actual AST, either nil or a dictionary with the following +/// keys: /// /// "type": node type, one of the value names from ExprASTNodeType /// stringified without "kExprNode" prefix. @@ -927,11 +935,9 @@ theend: /// debugging purposes primary (debugging parser and providing /// debug information). /// "children": a list of nodes described in top/"ast". There always -/// is zero, one or two children, key will contain an -/// empty array if node can have children, but has no and -/// will not be present at all if node can’t have any -/// children. Maximum number of children may be found in -/// node_maxchildren array. +/// is zero, one or two children, key will not be present +/// if node has no children. Maximum number of children +/// may be found in node_maxchildren array. /// /// Local values (present only for certain nodes): /// @@ -950,6 +956,8 @@ theend: /// value names from ExprCaseCompareStrategy, /// stringified without "kCCStrategy" prefix. Only /// present for "Comparison" nodes. +/// "invert": Boolean, true if result of comparison needs to be +/// inverted. Only present for "Comparison" nodes. /// "ivalue": Integer, integer value for "Integer" nodes. /// "fvalue": Float, floating-point value for "Float" nodes. /// "svalue": String, value for "SingleQuotedString" and @@ -998,7 +1006,7 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, .capacity = dict_size, }; ret.items[ret.size++] = (KeyValuePair) { - .key = STATIC_CSTR_AS_STRING("ast"), + .key = STATIC_CSTR_TO_STRING("ast"), .value = NIL, }; if (east.err.arg != NULL) { @@ -1008,18 +1016,18 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, .capacity = 2, }; err_dict.items[0] = (KeyValuePair) { - .key = STATIC_CSTR_AS_STRING("message"), + .key = STATIC_CSTR_TO_STRING("message"), .value = STRING_OBJ(cstr_to_string(east.err.arg)), }; err_dict.items[1] = (KeyValuePair) { - .key = STATIC_CSTR_AS_STRING("arg"), + .key = STATIC_CSTR_TO_STRING("arg"), .value = STRING_OBJ(((String) { - .data = xmemdup(east.err.arg, (size_t)east.err.arg_len), + .data = xmemdupz(east.err.arg, (size_t)east.err.arg_len), .size = (size_t)east.err.arg_len, })), }; ret.items[ret.size++] = (KeyValuePair) { - .key = STATIC_CSTR_AS_STRING("error"), + .key = STATIC_CSTR_TO_STRING("error"), .value = DICTIONARY_OBJ(err_dict), }; } @@ -1032,7 +1040,7 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, for (size_t i = 0 ; i < kv_size(colors) ; i++) { const ParserHighlightChunk chunk = kv_A(colors, i); Array chunk_arr = (Array) { - .items = xmalloc(4), + .items = xmalloc(4 * sizeof(chunk_arr.items[0])), .capacity = 4, .size = 4, }; @@ -1043,17 +1051,243 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, hl.items[i] = ARRAY_OBJ(chunk_arr); } ret.items[ret.size++] = (KeyValuePair) { - .key = STATIC_CSTR_AS_STRING("highlight"), + .key = STATIC_CSTR_TO_STRING("highlight"), .value = ARRAY_OBJ(hl), }; } - // FIXME: populate AST + // Walk over the AST, freeing nodes in process. + ExprASTConvStack ast_conv_stack; + kvi_init(ast_conv_stack); + kvi_push(ast_conv_stack, ((ExprASTConvStackItem) { + .node_p = &east.root, + .ret_node_p = &ret.items[0].value, + })); + while (kv_size(ast_conv_stack)) { + ExprASTConvStackItem cur_item = kv_last(ast_conv_stack); + if (*cur_item.node_p == NULL) { + assert(kv_size(ast_conv_stack) == 1); + kv_drop(ast_conv_stack, 1); + } else { + ExprASTNode *const node = *cur_item.node_p; + if (cur_item.ret_node_p->type == kObjectTypeNil) { + const size_t ret_node_items_size = (size_t)( + 3 // "type", "start" and "len" + + (node->children != NULL) // "children" + + (node->type == kExprNodeOption + || node->type == kExprNodePlainIdentifier) // "scope" + + (node->type == kExprNodeOption + || node->type == kExprNodePlainIdentifier + || node->type == kExprNodePlainKey + || node->type == kExprNodeEnvironment) // "ident" + + (node->type == kExprNodeRegister) // "name" + + (3 // "cmp_type", "ccs_strategy", "invert" + * (node->type == kExprNodeComparison)) + + (node->type == kExprNodeInteger) // "ivalue" + + (node->type == kExprNodeFloat) // "fvalue" + + (node->type == kExprNodeDoubleQuotedString + || node->type == kExprNodeSingleQuotedString) // "svalue" + + 0); + Dictionary ret_node = { + .items = xmalloc(ret_node_items_size * sizeof(ret_node.items[0])), + .capacity = ret_node_items_size, + .size = 0, + }; + *cur_item.ret_node_p = DICTIONARY_OBJ(ret_node); + } + Dictionary *ret_node = &cur_item.ret_node_p->data.dictionary; + if (node->children != NULL) { + const size_t num_children = 1 + (node->children->next != NULL); + Array children_array = { + .items = xmalloc(num_children * sizeof(children_array.items[0])), + .capacity = num_children, + .size = num_children, + }; + for (size_t i = 0; i < num_children; i++) { + children_array.items[i] = NIL; + } + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("children"), + .value = ARRAY_OBJ(children_array), + }; + kvi_push(ast_conv_stack, ((ExprASTConvStackItem) { + .node_p = &node->children, + .ret_node_p = &children_array.items[0], + })); + } else if (node->next != NULL) { + kvi_push(ast_conv_stack, ((ExprASTConvStackItem) { + .node_p = &node->children, + .ret_node_p = cur_item.ret_node_p + 1, + })); + } else if (node != NULL) { + kv_drop(ast_conv_stack, 1); + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("type"), + .value = STRING_OBJ(cstr_to_string(east_node_type_tab[node->type])), + }; + Array start_array = { + .items = xmalloc(2 * sizeof(start_array.items[0])), + .capacity = 2, + .size = 2, + }; + start_array.items[0] = INTEGER_OBJ((Integer)node->start.line); + start_array.items[1] = INTEGER_OBJ((Integer)node->start.col); + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("start"), + .value = ARRAY_OBJ(start_array), + }; + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("len"), + .value = INTEGER_OBJ((Integer)node->len), + }; + switch (node->type) { + case kExprNodeDoubleQuotedString: + case kExprNodeSingleQuotedString: { + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("svalue"), + .value = STRING_OBJ(cstr_as_string(node->data.str.value)), + }; + break; + } + case kExprNodeOption: { + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("scope"), + .value = INTEGER_OBJ(node->data.opt.scope), + }; + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("ident"), + .value = STRING_OBJ(((String) { + .data = xmemdupz(node->data.opt.ident, + node->data.opt.ident_len), + .size = node->data.opt.ident_len, + })), + }; + break; + } + case kExprNodePlainIdentifier: { + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("scope"), + .value = INTEGER_OBJ(node->data.var.scope), + }; + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("ident"), + .value = STRING_OBJ(((String) { + .data = xmemdupz(node->data.var.ident, + node->data.var.ident_len), + .size = node->data.var.ident_len, + })), + }; + break; + } + case kExprNodePlainKey: { + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("ident"), + .value = STRING_OBJ(((String) { + .data = xmemdupz(node->data.var.ident, + node->data.var.ident_len), + .size = node->data.var.ident_len, + })), + }; + break; + } + case kExprNodeEnvironment: { + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("ident"), + .value = STRING_OBJ(((String) { + .data = xmemdupz(node->data.env.ident, + node->data.env.ident_len), + .size = node->data.env.ident_len, + })), + }; + break; + } + case kExprNodeRegister: { + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("name"), + .value = INTEGER_OBJ(node->data.reg.name), + }; + break; + } + case kExprNodeComparison: { + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("cmp_type"), + .value = STRING_OBJ(cstr_to_string( + eltkn_cmp_type_tab[node->data.cmp.type])), + }; + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("ccs_strategy"), + .value = STRING_OBJ(cstr_to_string( + eltkn_cmp_type_tab[node->data.cmp.ccs])), + }; + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("invert"), + .value = BOOLEAN_OBJ(node->data.cmp.inv), + }; + break; + } + case kExprNodeFloat: { + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("fvalue"), + .value = FLOAT_OBJ(node->data.flt.value), + }; + break; + } + case kExprNodeInteger: { + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("ivalue"), + .value = INTEGER_OBJ((Integer)( + node->data.num.value > API_INTEGER_MAX + ? API_INTEGER_MAX + : (Integer)node->data.num.value)), + }; + break; + } + case kExprNodeMissing: + case kExprNodeOpMissing: + case kExprNodeTernary: + case kExprNodeTernaryValue: + case kExprNodeSubscript: + case kExprNodeListLiteral: + case kExprNodeUnaryPlus: + case kExprNodeBinaryPlus: + case kExprNodeNested: + case kExprNodeCall: + case kExprNodeComplexIdentifier: + case kExprNodeUnknownFigure: + case kExprNodeLambda: + case kExprNodeDictLiteral: + case kExprNodeCurlyBracesIdentifier: + case kExprNodeComma: + case kExprNodeColon: + case kExprNodeArrow: + case kExprNodeConcat: + case kExprNodeConcatOrSubscript: + case kExprNodeOr: + case kExprNodeAnd: + case kExprNodeUnaryMinus: + case kExprNodeBinaryMinus: + case kExprNodeNot: + case kExprNodeMultiplication: + case kExprNodeDivision: + case kExprNodeMod: { + break; + } + } + assert(cur_item.ret_node_p->data.dictionary.size + == cur_item.ret_node_p->data.dictionary.capacity); + xfree(*cur_item.node_p); + *cur_item.node_p = NULL; + } + } + } + kvi_destroy(ast_conv_stack); assert(ret.size == ret.capacity); + // Should be a no-op actually, leaving it in case non-nodes will need to be + // freed later. viml_pexpr_free_ast(east); viml_parser_destroy(&pstate); - return (Dictionary)ARRAY_DICT_INIT; + return ret; } diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index fc184f56f5..b19aab22af 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -616,7 +616,7 @@ static const char *const eltkn_type_tab[] = { [kExprLexArrow] = "Arrow", }; -static const char *const eltkn_cmp_type_tab[] = { +const char *const eltkn_cmp_type_tab[] = { [kExprCmpEqual] = "Equal", [kExprCmpMatches] = "Matches", [kExprCmpGreater] = "Greater", @@ -624,7 +624,7 @@ static const char *const eltkn_cmp_type_tab[] = { [kExprCmpIdentical] = "Identical", }; -static const char *const ccs_tab[] = { +const char *const ccs_tab[] = { [kCCStrategyUseOption] = "UseOption", [kCCStrategyMatchCase] = "MatchCase", [kCCStrategyIgnoreCase] = "IgnoreCase", @@ -725,8 +725,7 @@ viml_pexpr_repr_token_end: return ret; } -#ifdef UNIT_TESTING -static const char *const east_node_type_tab[] = { +const char *const east_node_type_tab[] = { [kExprNodeMissing] = "Missing", [kExprNodeOpMissing] = "OpMissing", [kExprNodeTernary] = "Ternary", @@ -766,7 +765,6 @@ static const char *const east_node_type_tab[] = { [kExprNodeOption] = "Option", [kExprNodeEnvironment] = "Environment", }; -#endif /// Represent `int` character as a string /// @@ -2148,10 +2146,10 @@ viml_pexpr_parse_invalid_comma: } #define EXP_VAL_COLON "E15: Expected value, got colon: %.*s" case kExprLexColon: { + bool is_ternary = false; if (kv_size(ast_stack) < 2) { goto viml_pexpr_parse_invalid_colon; } - bool is_ternary = false; bool can_be_ternary = true; bool is_subscript = false; for (size_t i = 1; i < kv_size(ast_stack); i++) { diff --git a/src/nvim/viml/parser/expressions.h b/src/nvim/viml/parser/expressions.h index 668c2a4c84..648f8cbc1f 100644 --- a/src/nvim/viml/parser/expressions.h +++ b/src/nvim/viml/parser/expressions.h @@ -341,6 +341,15 @@ typedef struct { /// Array mapping ExprASTNodeType to maximum amount of children node may have extern const uint8_t node_maxchildren[]; +/// Array mapping ExprASTNodeType values to their stringified versions +extern const char *const east_node_type_tab[]; + +/// Array mapping ExprComparisonType values to their stringified versions +extern const char *const eltkn_cmp_type_tab[]; + +/// Array mapping ExprCaseCompareStrategy values to their stringified versions +extern const char *const ccs_tab[]; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "viml/parser/expressions.h.generated.h" #endif -- cgit From 7bc6de75263f58c6c4f999bc86a6454ae9f28b80 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 5 Nov 2017 02:41:44 +0300 Subject: api/vim,functests: Add tests for nvim_parse_expression, fix found bugs --- src/nvim/api/vim.c | 47 +- src/nvim/viml/parser/expressions.c | 2 +- test/functional/api/vim_spec.lua | 6868 ++++++++++++++++++++++++++++ test/helpers.lua | 6 + test/unit/viml/expressions/lexer_spec.lua | 2 +- test/unit/viml/expressions/parser_spec.lua | 5 +- test/unit/viml/helpers.lua | 5 - 7 files changed, 6908 insertions(+), 27 deletions(-) diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 1aab87010e..5f725acaf7 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -995,21 +995,21 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, &pstate, parser_simple_get_line, &plines_p, colors_p); ExprAST east = viml_pexpr_parse(&pstate, pflags); - const size_t dict_size = ( + const size_t ret_size = ( 1 // "ast" - + (size_t)(east.err.arg != NULL) // "error" + + (size_t)(east.err.msg != NULL) // "error" + (size_t)highlight // "highlight" ); Dictionary ret = { - .items = xmalloc(dict_size * sizeof(ret.items[0])), + .items = xmalloc(ret_size * sizeof(ret.items[0])), .size = 0, - .capacity = dict_size, + .capacity = ret_size, }; ret.items[ret.size++] = (KeyValuePair) { .key = STATIC_CSTR_TO_STRING("ast"), .value = NIL, }; - if (east.err.arg != NULL) { + if (east.err.msg != NULL) { Dictionary err_dict = { .items = xmalloc(2 * sizeof(err_dict.items[0])), .size = 2, @@ -1017,15 +1017,22 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, }; err_dict.items[0] = (KeyValuePair) { .key = STATIC_CSTR_TO_STRING("message"), - .value = STRING_OBJ(cstr_to_string(east.err.arg)), - }; - err_dict.items[1] = (KeyValuePair) { - .key = STATIC_CSTR_TO_STRING("arg"), - .value = STRING_OBJ(((String) { - .data = xmemdupz(east.err.arg, (size_t)east.err.arg_len), - .size = (size_t)east.err.arg_len, - })), + .value = STRING_OBJ(cstr_to_string(east.err.msg)), }; + if (east.err.arg == NULL) { + err_dict.items[1] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("arg"), + .value = STRING_OBJ(STRING_INIT), + }; + } else { + err_dict.items[1] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("arg"), + .value = STRING_OBJ(((String) { + .data = xmemdupz(east.err.arg, (size_t)east.err.arg_len), + .size = (size_t)east.err.arg_len, + })), + }; + } ret.items[ret.size++] = (KeyValuePair) { .key = STATIC_CSTR_TO_STRING("error"), .value = DICTIONARY_OBJ(err_dict), @@ -1055,6 +1062,7 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, .value = ARRAY_OBJ(hl), }; } + kvi_destroy(colors); // Walk over the AST, freeing nodes in process. ExprASTConvStack ast_conv_stack; @@ -1065,11 +1073,11 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, })); while (kv_size(ast_conv_stack)) { ExprASTConvStackItem cur_item = kv_last(ast_conv_stack); - if (*cur_item.node_p == NULL) { + ExprASTNode *const node = *cur_item.node_p; + if (node == NULL) { assert(kv_size(ast_conv_stack) == 1); kv_drop(ast_conv_stack, 1); } else { - ExprASTNode *const node = *cur_item.node_p; if (cur_item.ret_node_p->type == kObjectTypeNil) { const size_t ret_node_items_size = (size_t)( 3 // "type", "start" and "len" @@ -1116,7 +1124,7 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, })); } else if (node->next != NULL) { kvi_push(ast_conv_stack, ((ExprASTConvStackItem) { - .node_p = &node->children, + .node_p = &node->next, .ret_node_p = cur_item.ret_node_p + 1, })); } else if (node != NULL) { @@ -1145,7 +1153,10 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, case kExprNodeSingleQuotedString: { ret_node->items[ret_node->size++] = (KeyValuePair) { .key = STATIC_CSTR_TO_STRING("svalue"), - .value = STRING_OBJ(cstr_as_string(node->data.str.value)), + .value = STRING_OBJ(((String) { + .data = node->data.str.value, + .size = node->data.str.size, + })), }; break; } @@ -1217,7 +1228,7 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, ret_node->items[ret_node->size++] = (KeyValuePair) { .key = STATIC_CSTR_TO_STRING("ccs_strategy"), .value = STRING_OBJ(cstr_to_string( - eltkn_cmp_type_tab[node->data.cmp.ccs])), + ccs_tab[node->data.cmp.ccs])), }; ret_node->items[ret_node->size++] = (KeyValuePair) { .key = STATIC_CSTR_TO_STRING("invert"), diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index b19aab22af..b10952a8ac 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -1492,7 +1492,7 @@ static void parse_quoted_string(ParserState *const pstate, node->data.str.value = NULL; } else { char *v_p; - v_p = node->data.str.value = xmalloc(size); + v_p = node->data.str.value = xmallocz(size); p = s + 1; while (p < e) { const char *const chunk_e = memchr(p, '\'', (size_t)(e - p)); diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index 6b44698638..bb7785657d 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -1,5 +1,7 @@ local helpers = require('test.functional.helpers')(after_each) local Screen = require('test.functional.ui.screen') +local global_helpers = require('test.helpers') + local NIL = helpers.NIL local clear, nvim, eq, neq = helpers.clear, helpers.nvim, helpers.eq, helpers.neq local ok, nvim_async, feed = helpers.ok, helpers.nvim_async, helpers.feed @@ -10,6 +12,9 @@ local request = helpers.request local meth_pcall = helpers.meth_pcall local command = helpers.command +local intchar2lua = global_helpers.intchar2lua +local format_string = global_helpers.format_string + describe('api', function() before_each(clear) @@ -711,8 +716,6871 @@ describe('api', function() end) describe('nvim_parse_expression', function() + local function simplify_east_api_node(line, east_api_node) + if east_api_node.children then + for k, v in pairs(east_api_node.children) do + east_api_node.children[k] = simplify_east_api_node(line, v) + end + end + local typ = east_api_node.type + if typ == 'Register' then + typ = typ .. ('(name=%s)'):format( + tostring(intchar2lua(east_api_node.name))) + east_api_node.name = nil + elseif typ == 'PlainIdentifier' then + typ = typ .. ('(scope=%s,ident=%s)'):format( + tostring(intchar2lua(east_api_node.scope)), east_api_node.ident) + east_api_node.scope = nil + east_api_node.ident = nil + elseif typ == 'PlainKey' then + typ = typ .. ('(key=%s)'):format(east_api_node.ident) + east_api_node.ident = nil + elseif typ == 'Comparison' then + typ = typ .. ('(type=%s,inv=%u,ccs=%s)'):format( + east_api_node.cmp_type, east_api_node.invert and 1 or 0, + east_api_node.ccs_strategy) + east_api_node.ccs_strategy = nil + east_api_node.cmp_type = nil + east_api_node.invert = nil + elseif typ == 'Integer' then + typ = typ .. ('(val=%u)'):format(east_api_node.ivalue) + east_api_node.ivalue = nil + elseif typ == 'Float' then + typ = typ .. ('(val=%e)'):format(east_api_node.fvalue) + east_api_node.fvalue = nil + elseif typ == 'SingleQuotedString' or typ == 'DoubleQuotedString' then + typ = format_string('%s(val=%q)', typ, east_api_node.svalue) + east_api_node.svalue = nil + elseif typ == 'Option' then + typ = ('%s(scope=%s,ident=%s)'):format( + typ, + tostring(intchar2lua(east_api_node.scope)), + east_api_node.ident) + east_api_node.ident = nil + east_api_node.scope = nil + elseif typ == 'Environment' then + typ = ('%s(ident=%s)'):format(typ, east_api_node.ident) + east_api_node.ident = nil + end + typ = ('%s:%u:%u:%s'):format( + typ, east_api_node.start[1], east_api_node.start[2], + line:sub(east_api_node.start[2] + 1, + east_api_node.start[2] + 1 + east_api_node.len - 1)) + assert(east_api_node.start[2] + east_api_node.len - 1 <= #line) + for k, _ in pairs(east_api_node.start) do + assert(({true, true})[k]) + end + east_api_node.start = nil + east_api_node.type = nil + east_api_node.len = nil + local can_simplify = true + for _, _ in pairs(east_api_node) do + if can_simplify then can_simplify = false end + end + if can_simplify then + return typ + else + east_api_node[1] = typ + return east_api_node + end + end + local function simplify_east_api(line, east_api) + if east_api.error then + east_api.err = east_api.error + east_api.error = nil + east_api.err.msg = east_api.err.message + east_api.err.message = nil + end + if east_api.ast then + east_api.ast = {simplify_east_api_node(line, east_api.ast)} + end + return east_api + end + local function simplify_east_hl(line, east_hl) + for i, v in ipairs(east_hl) do + east_hl[i] = ('%s:%u:%u:%s'):format( + v[4], + v[1], + v[2], + line:sub(v[2] + 1, v[3])) + end + return east_hl + end + local function check_parsing(str, flags, exp_ast, exp_highlighting_fs) + if flags == 0 then + flags = "" + end + + local err, msg = pcall(function() + local east_api = meths.parse_expression(str, flags, true) + local east_hl = east_api.highlight + east_api.highlight = nil + local ast = simplify_east_api(str, east_api) + local hls = simplify_east_hl(str, east_hl) + eq(exp_ast, ast) + if exp_highlighting_fs then + local exp_highlighting = {} + local next_col = 0 + for i, h in ipairs(exp_highlighting_fs) do + exp_highlighting[i], next_col = h(next_col) + end + eq(exp_highlighting, hls) + end + end) + if not err then + msg = format_string('Error while processing test (%r, %s):\n%s', + str, flags, msg) + error(msg) + end + end + local function hl(group, str, shift) + return function(next_col) + local col = next_col + (shift or 0) + return (('%s:%u:%u:%s'):format( + 'NVim' .. group, + 0, + col, + str)), (col + #str) + end + end + it('works with + and @a', function() + check_parsing('@a', 0, { + ast = { + 'Register(name=a):0:0:@a', + }, + }, { + hl('Register', '@a'), + }) + check_parsing('+@a', 0, { + ast = { + { + 'UnaryPlus:0:0:+', + children = { + 'Register(name=a):0:1:@a', + }, + }, + }, + }, { + hl('UnaryPlus', '+'), + hl('Register', '@a'), + }) + check_parsing('@a+@b', 0, { + ast = { + { + 'BinaryPlus:0:2:+', + children = { + 'Register(name=a):0:0:@a', + 'Register(name=b):0:3:@b', + }, + }, + }, + }, { + hl('Register', '@a'), + hl('BinaryPlus', '+'), + hl('Register', '@b'), + }) + check_parsing('@a+@b+@c', 0, { + ast = { + { + 'BinaryPlus:0:5:+', + children = { + { + 'BinaryPlus:0:2:+', + children = { + 'Register(name=a):0:0:@a', + 'Register(name=b):0:3:@b', + }, + }, + 'Register(name=c):0:6:@c', + }, + }, + }, + }, { + hl('Register', '@a'), + hl('BinaryPlus', '+'), + hl('Register', '@b'), + hl('BinaryPlus', '+'), + hl('Register', '@c'), + }) + check_parsing('+@a+@b', 0, { + ast = { + { + 'BinaryPlus:0:3:+', + children = { + { + 'UnaryPlus:0:0:+', + children = { + 'Register(name=a):0:1:@a', + }, + }, + 'Register(name=b):0:4:@b', + }, + }, + }, + }, { + hl('UnaryPlus', '+'), + hl('Register', '@a'), + hl('BinaryPlus', '+'), + hl('Register', '@b'), + }) + check_parsing('+@a++@b', 0, { + ast = { + { + 'BinaryPlus:0:3:+', + children = { + { + 'UnaryPlus:0:0:+', + children = { + 'Register(name=a):0:1:@a', + }, + }, + { + 'UnaryPlus:0:4:+', + children = { + 'Register(name=b):0:5:@b', + }, + }, + }, + }, + }, + }, { + hl('UnaryPlus', '+'), + hl('Register', '@a'), + hl('BinaryPlus', '+'), + hl('UnaryPlus', '+'), + hl('Register', '@b'), + }) + check_parsing('@a@b', 0, { + ast = { + { + 'OpMissing:0:2:', + children = { + 'Register(name=a):0:0:@a', + 'Register(name=b):0:2:@b', + }, + }, + }, + err = { + arg = '@b', + msg = 'E15: Missing operator: %.*s', + }, + }, { + hl('Register', '@a'), + hl('InvalidRegister', '@b'), + }) + check_parsing(' @a \t @b', 0, { + ast = { + { + 'OpMissing:0:3:', + children = { + 'Register(name=a):0:0: @a', + 'Register(name=b):0:3: \t @b', + }, + }, + }, + err = { + arg = '@b', + msg = 'E15: Missing operator: %.*s', + }, + }, { + hl('Register', '@a', 1), + hl('InvalidSpacing', ' \t '), + hl('Register', '@b'), + }) + check_parsing('+', 0, { + ast = { + 'UnaryPlus:0:0:+', + }, + err = { + arg = '', + msg = 'E15: Expected value, got EOC: %.*s', + }, + }, { + hl('UnaryPlus', '+'), + }) + check_parsing(' +', 0, { + ast = { + 'UnaryPlus:0:0: +', + }, + err = { + arg = '', + msg = 'E15: Expected value, got EOC: %.*s', + }, + }, { + hl('UnaryPlus', '+', 1), + }) + check_parsing('@a+ ', 0, { + ast = { + { + 'BinaryPlus:0:2:+', + children = { + 'Register(name=a):0:0:@a', + }, + }, + }, + err = { + arg = '', + msg = 'E15: Expected value, got EOC: %.*s', + }, + }, { + hl('Register', '@a'), + hl('BinaryPlus', '+'), + }) + end) + it('works with @a, + and parenthesis', function() + check_parsing('(@a)', 0, { + ast = { + { + 'Nested:0:0:(', + children = { + 'Register(name=a):0:1:@a', + }, + }, + }, + }, { + hl('NestingParenthesis', '('), + hl('Register', '@a'), + hl('NestingParenthesis', ')'), + }) + check_parsing('()', 0, { + ast = { + { + 'Nested:0:0:(', + children = { + 'Missing:0:1:', + }, + }, + }, + err = { + arg = ')', + msg = 'E15: Expected value, got parenthesis: %.*s', + }, + }, { + hl('NestingParenthesis', '('), + hl('InvalidNestingParenthesis', ')'), + }) + check_parsing(')', 0, { + ast = { + { + 'Nested:0:0:', + children = { + 'Missing:0:0:', + }, + }, + }, + err = { + arg = ')', + msg = 'E15: Expected value, got parenthesis: %.*s', + }, + }, { + hl('InvalidNestingParenthesis', ')'), + }) + check_parsing('+)', 0, { + ast = { + { + 'Nested:0:1:', + children = { + { + 'UnaryPlus:0:0:+', + children = { + 'Missing:0:1:', + }, + }, + }, + }, + }, + err = { + arg = ')', + msg = 'E15: Expected value, got parenthesis: %.*s', + }, + }, { + hl('UnaryPlus', '+'), + hl('InvalidNestingParenthesis', ')'), + }) + check_parsing('+@a(@b)', 0, { + ast = { + { + 'UnaryPlus:0:0:+', + children = { + { + 'Call:0:3:(', + children = { + 'Register(name=a):0:1:@a', + 'Register(name=b):0:4:@b', + }, + }, + }, + }, + }, + }, { + hl('UnaryPlus', '+'), + hl('Register', '@a'), + hl('CallingParenthesis', '('), + hl('Register', '@b'), + hl('CallingParenthesis', ')'), + }) + check_parsing('@a+@b(@c)', 0, { + ast = { + { + 'BinaryPlus:0:2:+', + children = { + 'Register(name=a):0:0:@a', + { + 'Call:0:5:(', + children = { + 'Register(name=b):0:3:@b', + 'Register(name=c):0:6:@c', + }, + }, + }, + }, + }, + }, { + hl('Register', '@a'), + hl('BinaryPlus', '+'), + hl('Register', '@b'), + hl('CallingParenthesis', '('), + hl('Register', '@c'), + hl('CallingParenthesis', ')'), + }) + check_parsing('@a()', 0, { + ast = { + { + 'Call:0:2:(', + children = { + 'Register(name=a):0:0:@a', + }, + }, + }, + }, { + hl('Register', '@a'), + hl('CallingParenthesis', '('), + hl('CallingParenthesis', ')'), + }) + check_parsing('@a ()', 0, { + ast = { + { + 'OpMissing:0:2:', + children = { + 'Register(name=a):0:0:@a', + { + 'Nested:0:2: (', + children = { + 'Missing:0:4:', + }, + }, + }, + }, + }, + err = { + arg = '()', + msg = 'E15: Missing operator: %.*s', + }, + }, { + hl('Register', '@a'), + hl('InvalidSpacing', ' '), + hl('NestingParenthesis', '('), + hl('InvalidNestingParenthesis', ')'), + }) + check_parsing( + '@a + (@b)', 0, { + ast = { + { + 'BinaryPlus:0:2: +', + children = { + 'Register(name=a):0:0:@a', + { + 'Nested:0:4: (', + children = { + 'Register(name=b):0:6:@b', + }, + }, + }, + }, + }, + }, { + hl('Register', '@a'), + hl('BinaryPlus', '+', 1), + hl('NestingParenthesis', '(', 1), + hl('Register', '@b'), + hl('NestingParenthesis', ')'), + }) + check_parsing( + '@a + (+@b)', 0, { + ast = { + { + 'BinaryPlus:0:2: +', + children = { + 'Register(name=a):0:0:@a', + { + 'Nested:0:4: (', + children = { + { + 'UnaryPlus:0:6:+', + children = { + 'Register(name=b):0:7:@b', + }, + }, + }, + }, + }, + }, + }, + }, { + hl('Register', '@a'), + hl('BinaryPlus', '+', 1), + hl('NestingParenthesis', '(', 1), + hl('UnaryPlus', '+'), + hl('Register', '@b'), + hl('NestingParenthesis', ')'), + }) + check_parsing( + '@a + (@b + @c)', 0, { + ast = { + { + 'BinaryPlus:0:2: +', + children = { + 'Register(name=a):0:0:@a', + { + 'Nested:0:4: (', + children = { + { + 'BinaryPlus:0:8: +', + children = { + 'Register(name=b):0:6:@b', + 'Register(name=c):0:10: @c', + }, + }, + }, + }, + }, + }, + }, + }, { + hl('Register', '@a'), + hl('BinaryPlus', '+', 1), + hl('NestingParenthesis', '(', 1), + hl('Register', '@b'), + hl('BinaryPlus', '+', 1), + hl('Register', '@c', 1), + hl('NestingParenthesis', ')'), + }) + check_parsing('(@a)+@b', 0, { + ast = { + { + 'BinaryPlus:0:4:+', + children = { + { + 'Nested:0:0:(', + children = { + 'Register(name=a):0:1:@a', + }, + }, + 'Register(name=b):0:5:@b', + }, + }, + }, + }, { + hl('NestingParenthesis', '('), + hl('Register', '@a'), + hl('NestingParenthesis', ')'), + hl('BinaryPlus', '+'), + hl('Register', '@b'), + }) + check_parsing('@a+(@b)(@c)', 0, { + -- 01234567890 + ast = { + { + 'BinaryPlus:0:2:+', + children = { + 'Register(name=a):0:0:@a', + { + 'Call:0:7:(', + children = { + { + 'Nested:0:3:(', + children = { 'Register(name=b):0:4:@b' }, + }, + 'Register(name=c):0:8:@c', + }, + }, + }, + }, + }, + }, { + hl('Register', '@a'), + hl('BinaryPlus', '+'), + hl('NestingParenthesis', '('), + hl('Register', '@b'), + hl('NestingParenthesis', ')'), + hl('CallingParenthesis', '('), + hl('Register', '@c'), + hl('CallingParenthesis', ')'), + }) + check_parsing('@a+((@b))(@c)', 0, { + -- 01234567890123456890123456789 + -- 0 1 2 + ast = { + { + 'BinaryPlus:0:2:+', + children = { + 'Register(name=a):0:0:@a', + { + 'Call:0:9:(', + children = { + { + 'Nested:0:3:(', + children = { + { + 'Nested:0:4:(', + children = { 'Register(name=b):0:5:@b' } + }, + }, + }, + 'Register(name=c):0:10:@c', + }, + }, + }, + }, + }, + }, { + hl('Register', '@a'), + hl('BinaryPlus', '+'), + hl('NestingParenthesis', '('), + hl('NestingParenthesis', '('), + hl('Register', '@b'), + hl('NestingParenthesis', ')'), + hl('NestingParenthesis', ')'), + hl('CallingParenthesis', '('), + hl('Register', '@c'), + hl('CallingParenthesis', ')'), + }) + check_parsing('@a+((@b))+@c', 0, { + -- 01234567890123456890123456789 + -- 0 1 2 + ast = { + { + 'BinaryPlus:0:9:+', + children = { + { + 'BinaryPlus:0:2:+', + children = { + 'Register(name=a):0:0:@a', + { + 'Nested:0:3:(', + children = { + { + 'Nested:0:4:(', + children = { 'Register(name=b):0:5:@b' } + }, + }, + }, + }, + }, + 'Register(name=c):0:10:@c', + }, + }, + }, + }, { + hl('Register', '@a'), + hl('BinaryPlus', '+'), + hl('NestingParenthesis', '('), + hl('NestingParenthesis', '('), + hl('Register', '@b'), + hl('NestingParenthesis', ')'), + hl('NestingParenthesis', ')'), + hl('BinaryPlus', '+'), + hl('Register', '@c'), + }) + check_parsing( + '@a + (@b + @c) + @d(@e) + (+@f) + ((+@g(@h))(@j)(@k))(@l)', 0, {--[[ + | | | | | | | | || | | || | | ||| || || || || + 000000000011111111112222222222333333333344444444445555555 + 012345678901234567890123456789012345678901234567890123456 + ]] + ast = {{ + 'BinaryPlus:0:31: +', + children = { + { + 'BinaryPlus:0:23: +', + children = { + { + 'BinaryPlus:0:14: +', + children = { + { + 'BinaryPlus:0:2: +', + children = { + 'Register(name=a):0:0:@a', + { + 'Nested:0:4: (', + children = { + { + 'BinaryPlus:0:8: +', + children = { + 'Register(name=b):0:6:@b', + 'Register(name=c):0:10: @c', + }, + }, + }, + }, + }, + }, + { + 'Call:0:19:(', + children = { + 'Register(name=d):0:16: @d', + 'Register(name=e):0:20:@e', + }, + }, + }, + }, + { + 'Nested:0:25: (', + children = { + { + 'UnaryPlus:0:27:+', + children = { + 'Register(name=f):0:28:@f', + }, + }, + }, + }, + }, + }, + { + 'Call:0:53:(', + children = { + { + 'Nested:0:33: (', + children = { + { + 'Call:0:48:(', + children = { + { + 'Call:0:44:(', + children = { + { + 'Nested:0:35:(', + children = { + { + 'UnaryPlus:0:36:+', + children = { + { + 'Call:0:39:(', + children = { + 'Register(name=g):0:37:@g', + 'Register(name=h):0:40:@h', + }, + }, + }, + }, + }, + }, + 'Register(name=j):0:45:@j', + }, + }, + 'Register(name=k):0:49:@k', + }, + }, + }, + }, + 'Register(name=l):0:54:@l', + }, + }, + }, + }}, + }, { + hl('Register', '@a'), + hl('BinaryPlus', '+', 1), + hl('NestingParenthesis', '(', 1), + hl('Register', '@b'), + hl('BinaryPlus', '+', 1), + hl('Register', '@c', 1), + hl('NestingParenthesis', ')'), + hl('BinaryPlus', '+', 1), + hl('Register', '@d', 1), + hl('CallingParenthesis', '('), + hl('Register', '@e'), + hl('CallingParenthesis', ')'), + hl('BinaryPlus', '+', 1), + hl('NestingParenthesis', '(', 1), + hl('UnaryPlus', '+'), + hl('Register', '@f'), + hl('NestingParenthesis', ')'), + hl('BinaryPlus', '+', 1), + hl('NestingParenthesis', '(', 1), + hl('NestingParenthesis', '('), + hl('UnaryPlus', '+'), + hl('Register', '@g'), + hl('CallingParenthesis', '('), + hl('Register', '@h'), + hl('CallingParenthesis', ')'), + hl('NestingParenthesis', ')'), + hl('CallingParenthesis', '('), + hl('Register', '@j'), + hl('CallingParenthesis', ')'), + hl('CallingParenthesis', '('), + hl('Register', '@k'), + hl('CallingParenthesis', ')'), + hl('NestingParenthesis', ')'), + hl('CallingParenthesis', '('), + hl('Register', '@l'), + hl('CallingParenthesis', ')'), + }) + check_parsing('@a)', 0, { + -- 012 + ast = { + { + 'Nested:0:2:', + children = { + 'Register(name=a):0:0:@a', + }, + }, + }, + err = { + arg = ')', + msg = 'E15: Unexpected closing parenthesis: %.*s', + }, + }, { + hl('Register', '@a'), + hl('InvalidNestingParenthesis', ')'), + }) + check_parsing('(@a', 0, { + -- 012 + ast = { + { + 'Nested:0:0:(', + children = { + 'Register(name=a):0:1:@a', + }, + }, + }, + err = { + arg = '(@a', + msg = 'E110: Missing closing parenthesis for nested expression: %.*s', + }, + }, { + hl('NestingParenthesis', '('), + hl('Register', '@a'), + }) + check_parsing('@a(@b', 0, { + -- 01234 + ast = { + { + 'Call:0:2:(', + children = { + 'Register(name=a):0:0:@a', + 'Register(name=b):0:3:@b', + }, + }, + }, + err = { + arg = '(@b', + msg = 'E116: Missing closing parenthesis for function call: %.*s', + }, + }, { + hl('Register', '@a'), + hl('CallingParenthesis', '('), + hl('Register', '@b'), + }) + check_parsing('@a(@b, @c, @d, @e)', 0, { + -- 012345678901234567 + -- 0 1 + ast = { + { + 'Call:0:2:(', + children = { + 'Register(name=a):0:0:@a', + { + 'Comma:0:5:,', + children = { + 'Register(name=b):0:3:@b', + { + 'Comma:0:9:,', + children = { + 'Register(name=c):0:6: @c', + { + 'Comma:0:13:,', + children = { + 'Register(name=d):0:10: @d', + 'Register(name=e):0:14: @e', + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, { + hl('Register', '@a'), + hl('CallingParenthesis', '('), + hl('Register', '@b'), + hl('Comma', ','), + hl('Register', '@c', 1), + hl('Comma', ','), + hl('Register', '@d', 1), + hl('Comma', ','), + hl('Register', '@e', 1), + hl('CallingParenthesis', ')'), + }) + check_parsing('@a(@b(@c))', 0, { + -- 01234567890123456789012345678901234567 + -- 0 1 2 3 + ast = { + { + 'Call:0:2:(', + children = { + 'Register(name=a):0:0:@a', + { + 'Call:0:5:(', + children = { + 'Register(name=b):0:3:@b', + 'Register(name=c):0:6:@c', + }, + }, + }, + }, + }, + }, { + hl('Register', '@a'), + hl('CallingParenthesis', '('), + hl('Register', '@b'), + hl('CallingParenthesis', '('), + hl('Register', '@c'), + hl('CallingParenthesis', ')'), + hl('CallingParenthesis', ')'), + }) + check_parsing('@a(@b(@c(@d(@e), @f(@g(@h), @i(@j)))))', 0, { + -- 01234567890123456789012345678901234567 + -- 0 1 2 3 + ast = { + { + 'Call:0:2:(', + children = { + 'Register(name=a):0:0:@a', + { + 'Call:0:5:(', + children = { + 'Register(name=b):0:3:@b', + { + 'Call:0:8:(', + children = { + 'Register(name=c):0:6:@c', + { + 'Comma:0:15:,', + children = { + { + 'Call:0:11:(', + children = { + 'Register(name=d):0:9:@d', + 'Register(name=e):0:12:@e', + }, + }, + { + 'Call:0:19:(', + children = { + 'Register(name=f):0:16: @f', + { + 'Comma:0:26:,', + children = { + { + 'Call:0:22:(', + children = { + 'Register(name=g):0:20:@g', + 'Register(name=h):0:23:@h', + }, + }, + { + 'Call:0:30:(', + children = { + 'Register(name=i):0:27: @i', + 'Register(name=j):0:31:@j', + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, { + hl('Register', '@a'), + hl('CallingParenthesis', '('), + hl('Register', '@b'), + hl('CallingParenthesis', '('), + hl('Register', '@c'), + hl('CallingParenthesis', '('), + hl('Register', '@d'), + hl('CallingParenthesis', '('), + hl('Register', '@e'), + hl('CallingParenthesis', ')'), + hl('Comma', ','), + hl('Register', '@f', 1), + hl('CallingParenthesis', '('), + hl('Register', '@g'), + hl('CallingParenthesis', '('), + hl('Register', '@h'), + hl('CallingParenthesis', ')'), + hl('Comma', ','), + hl('Register', '@i', 1), + hl('CallingParenthesis', '('), + hl('Register', '@j'), + hl('CallingParenthesis', ')'), + hl('CallingParenthesis', ')'), + hl('CallingParenthesis', ')'), + hl('CallingParenthesis', ')'), + hl('CallingParenthesis', ')'), + }) + end) + it('works with variable names, including curly braces ones', function() + check_parsing('var', 0, { + ast = { + 'PlainIdentifier(scope=0,ident=var):0:0:var', + }, + }, { + hl('IdentifierName', 'var'), + }) + check_parsing('g:var', 0, { + ast = { + 'PlainIdentifier(scope=g,ident=var):0:0:g:var', + }, + }, { + hl('IdentifierScope', 'g'), + hl('IdentifierScopeDelimiter', ':'), + hl('IdentifierName', 'var'), + }) + check_parsing('g:', 0, { + ast = { + 'PlainIdentifier(scope=g,ident=):0:0:g:', + }, + }, { + hl('IdentifierScope', 'g'), + hl('IdentifierScopeDelimiter', ':'), + }) + check_parsing('{a}', 0, { + -- 012 + ast = { + { + 'CurlyBracesIdentifier:0:0:{', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + }, + }, + }, + }, { + hl('Curly', '{'), + hl('IdentifierName', 'a'), + hl('Curly', '}'), + }) + check_parsing('{a:b}', 0, { + -- 012 + ast = { + { + 'CurlyBracesIdentifier:0:0:{', + children = { + 'PlainIdentifier(scope=a,ident=b):0:1:a:b', + }, + }, + }, + }, { + hl('Curly', '{'), + hl('IdentifierScope', 'a'), + hl('IdentifierScopeDelimiter', ':'), + hl('IdentifierName', 'b'), + hl('Curly', '}'), + }) + check_parsing('{a:@b}', 0, { + -- 012345 + ast = { + { + 'CurlyBracesIdentifier:0:0:{', + children = { + { + 'OpMissing:0:3:', + children={ + 'PlainIdentifier(scope=a,ident=):0:1:a:', + 'Register(name=b):0:3:@b', + }, + }, + }, + }, + }, + err = { + arg = '@b}', + msg = 'E15: Missing operator: %.*s', + }, + }, { + hl('Curly', '{'), + hl('IdentifierScope', 'a'), + hl('IdentifierScopeDelimiter', ':'), + hl('InvalidRegister', '@b'), + hl('Curly', '}'), + }) + check_parsing('{@a}', 0, { + ast = { + { + 'CurlyBracesIdentifier:0:0:{', + children = { + 'Register(name=a):0:1:@a', + }, + }, + }, + }, { + hl('Curly', '{'), + hl('Register', '@a'), + hl('Curly', '}'), + }) + check_parsing('{@a}{@b}', 0, { + -- 01234567 + ast = { + { + 'ComplexIdentifier:0:4:', + children = { + { + 'CurlyBracesIdentifier:0:0:{', + children = { + 'Register(name=a):0:1:@a', + }, + }, + { + 'CurlyBracesIdentifier:0:4:{', + children = { + 'Register(name=b):0:5:@b', + }, + }, + }, + }, + }, + }, { + hl('Curly', '{'), + hl('Register', '@a'), + hl('Curly', '}'), + hl('Curly', '{'), + hl('Register', '@b'), + hl('Curly', '}'), + }) + check_parsing('g:{@a}', 0, { + -- 01234567 + ast = { + { + 'ComplexIdentifier:0:2:', + children = { + 'PlainIdentifier(scope=g,ident=):0:0:g:', + { + 'CurlyBracesIdentifier:0:2:{', + children = { + 'Register(name=a):0:3:@a', + }, + }, + }, + }, + }, + }, { + hl('IdentifierScope', 'g'), + hl('IdentifierScopeDelimiter', ':'), + hl('Curly', '{'), + hl('Register', '@a'), + hl('Curly', '}'), + }) + check_parsing('{@a}_test', 0, { + -- 012345678 + ast = { + { + 'ComplexIdentifier:0:4:', + children = { + { + 'CurlyBracesIdentifier:0:0:{', + children = { + 'Register(name=a):0:1:@a', + }, + }, + 'PlainIdentifier(scope=0,ident=_test):0:4:_test', + }, + }, + }, + }, { + hl('Curly', '{'), + hl('Register', '@a'), + hl('Curly', '}'), + hl('IdentifierName', '_test'), + }) + check_parsing('g:{@a}_test', 0, { + -- 01234567890 + ast = { + { + 'ComplexIdentifier:0:2:', + children = { + 'PlainIdentifier(scope=g,ident=):0:0:g:', + { + 'ComplexIdentifier:0:6:', + children = { + { + 'CurlyBracesIdentifier:0:2:{', + children = { + 'Register(name=a):0:3:@a', + }, + }, + 'PlainIdentifier(scope=0,ident=_test):0:6:_test', + }, + }, + }, + }, + }, + }, { + hl('IdentifierScope', 'g'), + hl('IdentifierScopeDelimiter', ':'), + hl('Curly', '{'), + hl('Register', '@a'), + hl('Curly', '}'), + hl('IdentifierName', '_test'), + }) + check_parsing('g:{@a}_test()', 0, { + -- 0123456789012 + ast = { + { + 'Call:0:11:(', + children = { + { + 'ComplexIdentifier:0:2:', + children = { + 'PlainIdentifier(scope=g,ident=):0:0:g:', + { + 'ComplexIdentifier:0:6:', + children = { + { + 'CurlyBracesIdentifier:0:2:{', + children = { + 'Register(name=a):0:3:@a', + }, + }, + 'PlainIdentifier(scope=0,ident=_test):0:6:_test', + }, + }, + }, + }, + }, + }, + }, + }, { + hl('IdentifierScope', 'g'), + hl('IdentifierScopeDelimiter', ':'), + hl('Curly', '{'), + hl('Register', '@a'), + hl('Curly', '}'), + hl('IdentifierName', '_test'), + hl('CallingParenthesis', '('), + hl('CallingParenthesis', ')'), + }) + check_parsing('{@a} ()', 0, { + -- 0123456789012 + ast = { + { + 'Call:0:4: (', + children = { + { + 'CurlyBracesIdentifier:0:0:{', + children = { + 'Register(name=a):0:1:@a', + }, + }, + }, + }, + }, + }, { + hl('Curly', '{'), + hl('Register', '@a'), + hl('Curly', '}'), + hl('CallingParenthesis', '(', 1), + hl('CallingParenthesis', ')'), + }) + check_parsing('g:{@a} ()', 0, { + -- 0123456789012 + ast = { + { + 'Call:0:6: (', + children = { + { + 'ComplexIdentifier:0:2:', + children = { + 'PlainIdentifier(scope=g,ident=):0:0:g:', + { + 'CurlyBracesIdentifier:0:2:{', + children = { + 'Register(name=a):0:3:@a', + }, + }, + }, + }, + }, + }, + }, + }, { + hl('IdentifierScope', 'g'), + hl('IdentifierScopeDelimiter', ':'), + hl('Curly', '{'), + hl('Register', '@a'), + hl('Curly', '}'), + hl('CallingParenthesis', '(', 1), + hl('CallingParenthesis', ')'), + }) + check_parsing('{@a', 0, { + -- 012 + ast = { + { + 'UnknownFigure:0:0:{', + children = { + 'Register(name=a):0:1:@a', + }, + }, + }, + err = { + arg = '{@a', + msg = 'E15: Missing closing figure brace: %.*s', + }, + }, { + hl('FigureBrace', '{'), + hl('Register', '@a'), + }) + end) + it('works with lambdas and dictionaries', function() + check_parsing('{}', 0, { + ast = { + 'DictLiteral:0:0:{', + }, + }, { + hl('Dict', '{'), + hl('Dict', '}'), + }) + check_parsing('{->@a}', 0, { + ast = { + { + 'Lambda:0:0:{', + children = { + { + 'Arrow:0:1:->', + children = { + 'Register(name=a):0:3:@a', + }, + }, + }, + }, + }, + }, { + hl('Lambda', '{'), + hl('Arrow', '->'), + hl('Register', '@a'), + hl('Lambda', '}'), + }) + check_parsing('{->@a+@b}', 0, { + -- 012345678 + ast = { + { + 'Lambda:0:0:{', + children = { + { + 'Arrow:0:1:->', + children = { + { + 'BinaryPlus:0:5:+', + children = { + 'Register(name=a):0:3:@a', + 'Register(name=b):0:6:@b', + }, + }, + }, + }, + }, + }, + }, + }, { + hl('Lambda', '{'), + hl('Arrow', '->'), + hl('Register', '@a'), + hl('BinaryPlus', '+'), + hl('Register', '@b'), + hl('Lambda', '}'), + }) + check_parsing('{a->@a}', 0, { + -- 012345678 + ast = { + { + 'Lambda:0:0:{', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + { + 'Arrow:0:2:->', + children = { + 'Register(name=a):0:4:@a', + }, + }, + }, + }, + }, + }, { + hl('Lambda', '{'), + hl('IdentifierName', 'a'), + hl('Arrow', '->'), + hl('Register', '@a'), + hl('Lambda', '}'), + }) + check_parsing('{a,b->@a}', 0, { + -- 012345678 + ast = { + { + 'Lambda:0:0:{', + children = { + { + 'Comma:0:2:,', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + 'PlainIdentifier(scope=0,ident=b):0:3:b', + }, + }, + { + 'Arrow:0:4:->', + children = { + 'Register(name=a):0:6:@a', + }, + }, + }, + }, + }, + }, { + hl('Lambda', '{'), + hl('IdentifierName', 'a'), + hl('Comma', ','), + hl('IdentifierName', 'b'), + hl('Arrow', '->'), + hl('Register', '@a'), + hl('Lambda', '}'), + }) + check_parsing('{a,b,c->@a}', 0, { + -- 01234567890 + ast = { + { + 'Lambda:0:0:{', + children = { + { + 'Comma:0:2:,', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + { + 'Comma:0:4:,', + children = { + 'PlainIdentifier(scope=0,ident=b):0:3:b', + 'PlainIdentifier(scope=0,ident=c):0:5:c', + }, + }, + }, + }, + { + 'Arrow:0:6:->', + children = { + 'Register(name=a):0:8:@a', + }, + }, + }, + }, + }, + }, { + hl('Lambda', '{'), + hl('IdentifierName', 'a'), + hl('Comma', ','), + hl('IdentifierName', 'b'), + hl('Comma', ','), + hl('IdentifierName', 'c'), + hl('Arrow', '->'), + hl('Register', '@a'), + hl('Lambda', '}'), + }) + check_parsing('{a,b,c,d->@a}', 0, { + -- 0123456789012 + ast = { + { + 'Lambda:0:0:{', + children = { + { + 'Comma:0:2:,', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + { + 'Comma:0:4:,', + children = { + 'PlainIdentifier(scope=0,ident=b):0:3:b', + { + 'Comma:0:6:,', + children = { + 'PlainIdentifier(scope=0,ident=c):0:5:c', + 'PlainIdentifier(scope=0,ident=d):0:7:d', + }, + }, + }, + }, + }, + }, + { + 'Arrow:0:8:->', + children = { + 'Register(name=a):0:10:@a', + }, + }, + }, + }, + }, + }, { + hl('Lambda', '{'), + hl('IdentifierName', 'a'), + hl('Comma', ','), + hl('IdentifierName', 'b'), + hl('Comma', ','), + hl('IdentifierName', 'c'), + hl('Comma', ','), + hl('IdentifierName', 'd'), + hl('Arrow', '->'), + hl('Register', '@a'), + hl('Lambda', '}'), + }) + check_parsing('{a,b,c,d,->@a}', 0, { + -- 01234567890123 + ast = { + { + 'Lambda:0:0:{', + children = { + { + 'Comma:0:2:,', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + { + 'Comma:0:4:,', + children = { + 'PlainIdentifier(scope=0,ident=b):0:3:b', + { + 'Comma:0:6:,', + children = { + 'PlainIdentifier(scope=0,ident=c):0:5:c', + { + 'Comma:0:8:,', + children = { + 'PlainIdentifier(scope=0,ident=d):0:7:d', + }, + }, + }, + }, + }, + }, + }, + }, + { + 'Arrow:0:9:->', + children = { + 'Register(name=a):0:11:@a', + }, + }, + }, + }, + }, + }, { + hl('Lambda', '{'), + hl('IdentifierName', 'a'), + hl('Comma', ','), + hl('IdentifierName', 'b'), + hl('Comma', ','), + hl('IdentifierName', 'c'), + hl('Comma', ','), + hl('IdentifierName', 'd'), + hl('Comma', ','), + hl('Arrow', '->'), + hl('Register', '@a'), + hl('Lambda', '}'), + }) + check_parsing('{a,b->{c,d->{e,f->@a}}}', 0, { + -- 01234567890123456789012 + -- 0 1 2 + ast = { + { + 'Lambda:0:0:{', + children = { + { + 'Comma:0:2:,', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + 'PlainIdentifier(scope=0,ident=b):0:3:b', + }, + }, + { + 'Arrow:0:4:->', + children = { + { + 'Lambda:0:6:{', + children = { + { + 'Comma:0:8:,', + children = { + 'PlainIdentifier(scope=0,ident=c):0:7:c', + 'PlainIdentifier(scope=0,ident=d):0:9:d', + }, + }, + { + 'Arrow:0:10:->', + children = { + { + 'Lambda:0:12:{', + children = { + { + 'Comma:0:14:,', + children = { + 'PlainIdentifier(scope=0,ident=e):0:13:e', + 'PlainIdentifier(scope=0,ident=f):0:15:f', + }, + }, + { + 'Arrow:0:16:->', + children = { + 'Register(name=a):0:18:@a', + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, { + hl('Lambda', '{'), + hl('IdentifierName', 'a'), + hl('Comma', ','), + hl('IdentifierName', 'b'), + hl('Arrow', '->'), + hl('Lambda', '{'), + hl('IdentifierName', 'c'), + hl('Comma', ','), + hl('IdentifierName', 'd'), + hl('Arrow', '->'), + hl('Lambda', '{'), + hl('IdentifierName', 'e'), + hl('Comma', ','), + hl('IdentifierName', 'f'), + hl('Arrow', '->'), + hl('Register', '@a'), + hl('Lambda', '}'), + hl('Lambda', '}'), + hl('Lambda', '}'), + }) + check_parsing('{a,b->c,d}', 0, { + -- 0123456789 + ast = { + { + 'Lambda:0:0:{', + children = { + { + 'Comma:0:2:,', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + 'PlainIdentifier(scope=0,ident=b):0:3:b', + }, + }, + { + 'Arrow:0:4:->', + children = { + { + 'Comma:0:7:,', + children = { + 'PlainIdentifier(scope=0,ident=c):0:6:c', + 'PlainIdentifier(scope=0,ident=d):0:8:d', + }, + }, + }, + }, + }, + }, + }, + err = { + arg = ',d}', + msg = 'E15: Comma outside of call, lambda or literal: %.*s', + }, + }, { + hl('Lambda', '{'), + hl('IdentifierName', 'a'), + hl('Comma', ','), + hl('IdentifierName', 'b'), + hl('Arrow', '->'), + hl('IdentifierName', 'c'), + hl('InvalidComma', ','), + hl('IdentifierName', 'd'), + hl('Lambda', '}'), + }) + check_parsing('a,b,c,d', 0, { + -- 0123456789 + ast = { + { + 'Comma:0:1:,', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'Comma:0:3:,', + children = { + 'PlainIdentifier(scope=0,ident=b):0:2:b', + { + 'Comma:0:5:,', + children = { + 'PlainIdentifier(scope=0,ident=c):0:4:c', + 'PlainIdentifier(scope=0,ident=d):0:6:d', + }, + }, + }, + }, + }, + }, + }, + err = { + arg = ',b,c,d', + msg = 'E15: Comma outside of call, lambda or literal: %.*s', + }, + }, { + hl('IdentifierName', 'a'), + hl('InvalidComma', ','), + hl('IdentifierName', 'b'), + hl('InvalidComma', ','), + hl('IdentifierName', 'c'), + hl('InvalidComma', ','), + hl('IdentifierName', 'd'), + }) + check_parsing('a,b,c,d,', 0, { + -- 0123456789 + ast = { + { + 'Comma:0:1:,', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'Comma:0:3:,', + children = { + 'PlainIdentifier(scope=0,ident=b):0:2:b', + { + 'Comma:0:5:,', + children = { + 'PlainIdentifier(scope=0,ident=c):0:4:c', + { + 'Comma:0:7:,', + children = { + 'PlainIdentifier(scope=0,ident=d):0:6:d', + }, + }, + }, + }, + }, + }, + }, + }, + }, + err = { + arg = ',b,c,d,', + msg = 'E15: Comma outside of call, lambda or literal: %.*s', + }, + }, { + hl('IdentifierName', 'a'), + hl('InvalidComma', ','), + hl('IdentifierName', 'b'), + hl('InvalidComma', ','), + hl('IdentifierName', 'c'), + hl('InvalidComma', ','), + hl('IdentifierName', 'd'), + hl('InvalidComma', ','), + }) + check_parsing(',', 0, { + -- 0123456789 + ast = { + { + 'Comma:0:0:,', + children = { + 'Missing:0:0:', + }, + }, + }, + err = { + arg = ',', + msg = 'E15: Expected value, got comma: %.*s', + }, + }, { + hl('InvalidComma', ','), + }) + check_parsing('{,a->@a}', 0, { + -- 0123456789 + ast = { + { + 'CurlyBracesIdentifier:0:0:{', + children = { + { + 'Arrow:0:3:->', + children = { + { + 'Comma:0:1:,', + children = { + 'Missing:0:1:', + 'PlainIdentifier(scope=0,ident=a):0:2:a', + }, + }, + 'Register(name=a):0:5:@a', + }, + }, + }, + }, + }, + err = { + arg = ',a->@a}', + msg = 'E15: Expected value, got comma: %.*s', + }, + }, { + hl('Curly', '{'), + hl('InvalidComma', ','), + hl('IdentifierName', 'a'), + hl('InvalidArrow', '->'), + hl('Register', '@a'), + hl('Curly', '}'), + }) + check_parsing('}', 0, { + -- 0123456789 + ast = { + 'UnknownFigure:0:0:', + }, + err = { + arg = '}', + msg = 'E15: Unexpected closing figure brace: %.*s', + }, + }, { + hl('InvalidFigureBrace', '}'), + }) + check_parsing('{->}', 0, { + -- 0123456789 + ast = { + { + 'Lambda:0:0:{', + children = { + 'Arrow:0:1:->', + }, + }, + }, + err = { + arg = '}', + msg = 'E15: Expected value, got closing figure brace: %.*s', + }, + }, { + hl('Lambda', '{'), + hl('Arrow', '->'), + hl('InvalidLambda', '}'), + }) + check_parsing('{a,b}', 0, { + -- 0123456789 + ast = { + { + 'Lambda:0:0:{', + children = { + { + 'Comma:0:2:,', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + 'PlainIdentifier(scope=0,ident=b):0:3:b', + }, + }, + }, + }, + }, + err = { + arg = '}', + msg = 'E15: Expected lambda arguments list or arrow: %.*s', + }, + }, { + hl('Lambda', '{'), + hl('IdentifierName', 'a'), + hl('Comma', ','), + hl('IdentifierName', 'b'), + hl('InvalidLambda', '}'), + }) + check_parsing('{a,}', 0, { + -- 0123456789 + ast = { + { + 'Lambda:0:0:{', + children = { + { + 'Comma:0:2:,', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + }, + }, + }, + }, + }, + err = { + arg = '}', + msg = 'E15: Expected lambda arguments list or arrow: %.*s', + }, + }, { + hl('Lambda', '{'), + hl('IdentifierName', 'a'), + hl('Comma', ','), + hl('InvalidLambda', '}'), + }) + check_parsing('{@a:@b}', 0, { + -- 0123456789 + ast = { + { + 'DictLiteral:0:0:{', + children = { + { + 'Colon:0:3::', + children = { + 'Register(name=a):0:1:@a', + 'Register(name=b):0:4:@b', + }, + }, + }, + }, + }, + }, { + hl('Dict', '{'), + hl('Register', '@a'), + hl('Colon', ':'), + hl('Register', '@b'), + hl('Dict', '}'), + }) + check_parsing('{@a:@b,@c:@d}', 0, { + -- 0123456789012 + -- 0 1 + ast = { + { + 'DictLiteral:0:0:{', + children = { + { + 'Comma:0:6:,', + children = { + { + 'Colon:0:3::', + children = { + 'Register(name=a):0:1:@a', + 'Register(name=b):0:4:@b', + }, + }, + { + 'Colon:0:9::', + children = { + 'Register(name=c):0:7:@c', + 'Register(name=d):0:10:@d', + }, + }, + }, + }, + }, + }, + }, + }, { + hl('Dict', '{'), + hl('Register', '@a'), + hl('Colon', ':'), + hl('Register', '@b'), + hl('Comma', ','), + hl('Register', '@c'), + hl('Colon', ':'), + hl('Register', '@d'), + hl('Dict', '}'), + }) + check_parsing('{@a:@b,@c:@d,@e:@f,}', 0, { + -- 01234567890123456789 + -- 0 1 + ast = { + { + 'DictLiteral:0:0:{', + children = { + { + 'Comma:0:6:,', + children = { + { + 'Colon:0:3::', + children = { + 'Register(name=a):0:1:@a', + 'Register(name=b):0:4:@b', + }, + }, + { + 'Comma:0:12:,', + children = { + { + 'Colon:0:9::', + children = { + 'Register(name=c):0:7:@c', + 'Register(name=d):0:10:@d', + }, + }, + { + 'Comma:0:18:,', + children = { + { + 'Colon:0:15::', + children = { + 'Register(name=e):0:13:@e', + 'Register(name=f):0:16:@f', + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, { + hl('Dict', '{'), + hl('Register', '@a'), + hl('Colon', ':'), + hl('Register', '@b'), + hl('Comma', ','), + hl('Register', '@c'), + hl('Colon', ':'), + hl('Register', '@d'), + hl('Comma', ','), + hl('Register', '@e'), + hl('Colon', ':'), + hl('Register', '@f'), + hl('Comma', ','), + hl('Dict', '}'), + }) + check_parsing('{@a:@b,@c:@d,@e:@f,@g:}', 0, { + -- 01234567890123456789012 + -- 0 1 2 + ast = { + { + 'DictLiteral:0:0:{', + children = { + { + 'Comma:0:6:,', + children = { + { + 'Colon:0:3::', + children = { + 'Register(name=a):0:1:@a', + 'Register(name=b):0:4:@b', + }, + }, + { + 'Comma:0:12:,', + children = { + { + 'Colon:0:9::', + children = { + 'Register(name=c):0:7:@c', + 'Register(name=d):0:10:@d', + }, + }, + { + 'Comma:0:18:,', + children = { + { + 'Colon:0:15::', + children = { + 'Register(name=e):0:13:@e', + 'Register(name=f):0:16:@f', + }, + }, + { + 'Colon:0:21::', + children = { + 'Register(name=g):0:19:@g', + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + err = { + arg = '}', + msg = 'E15: Expected value, got closing figure brace: %.*s', + }, + }, { + hl('Dict', '{'), + hl('Register', '@a'), + hl('Colon', ':'), + hl('Register', '@b'), + hl('Comma', ','), + hl('Register', '@c'), + hl('Colon', ':'), + hl('Register', '@d'), + hl('Comma', ','), + hl('Register', '@e'), + hl('Colon', ':'), + hl('Register', '@f'), + hl('Comma', ','), + hl('Register', '@g'), + hl('Colon', ':'), + hl('InvalidDict', '}'), + }) + check_parsing('{@a:@b,}', 0, { + -- 01234567890123 + -- 0 1 + ast = { + { + 'DictLiteral:0:0:{', + children = { + { + 'Comma:0:6:,', + children = { + { + 'Colon:0:3::', + children = { + 'Register(name=a):0:1:@a', + 'Register(name=b):0:4:@b', + }, + }, + }, + }, + }, + }, + }, + }, { + hl('Dict', '{'), + hl('Register', '@a'), + hl('Colon', ':'), + hl('Register', '@b'), + hl('Comma', ','), + hl('Dict', '}'), + }) + check_parsing('{({f -> g})(@h)(@i)}', 0, { + -- 01234567890123456789 + -- 0 1 + ast = { + { + 'CurlyBracesIdentifier:0:0:{', + children = { + { + 'Call:0:15:(', + children = { + { + 'Call:0:11:(', + children = { + { + 'Nested:0:1:(', + children = { + { + 'Lambda:0:2:{', + children = { + 'PlainIdentifier(scope=0,ident=f):0:3:f', + { + 'Arrow:0:4: ->', + children = { + 'PlainIdentifier(scope=0,ident=g):0:7: g', + }, + }, + }, + }, + }, + }, + 'Register(name=h):0:12:@h', + }, + }, + 'Register(name=i):0:16:@i', + }, + }, + }, + }, + }, + }, { + hl('Curly', '{'), + hl('NestingParenthesis', '('), + hl('Lambda', '{'), + hl('IdentifierName', 'f'), + hl('Arrow', '->', 1), + hl('IdentifierName', 'g', 1), + hl('Lambda', '}'), + hl('NestingParenthesis', ')'), + hl('CallingParenthesis', '('), + hl('Register', '@h'), + hl('CallingParenthesis', ')'), + hl('CallingParenthesis', '('), + hl('Register', '@i'), + hl('CallingParenthesis', ')'), + hl('Curly', '}'), + }) + check_parsing('a:{b()}c', 0, { + -- 01234567 + ast = { + { + 'ComplexIdentifier:0:2:', + children = { + 'PlainIdentifier(scope=a,ident=):0:0:a:', + { + 'ComplexIdentifier:0:7:', + children = { + { + 'CurlyBracesIdentifier:0:2:{', + children = { + { + 'Call:0:4:(', + children = { + 'PlainIdentifier(scope=0,ident=b):0:3:b', + }, + }, + }, + }, + 'PlainIdentifier(scope=0,ident=c):0:7:c', + }, + }, + }, + }, + }, + }, { + hl('IdentifierScope', 'a'), + hl('IdentifierScopeDelimiter', ':'), + hl('Curly', '{'), + hl('IdentifierName', 'b'), + hl('CallingParenthesis', '('), + hl('CallingParenthesis', ')'), + hl('Curly', '}'), + hl('IdentifierName', 'c'), + }) + check_parsing('a:{{b, c -> @d + @e + ({f -> g})(@h)}(@i)}j', 0, { + -- 01234567890123456789012345678901234567890123456 + -- 0 1 2 3 4 + ast = { + { + 'ComplexIdentifier:0:2:', + children = { + 'PlainIdentifier(scope=a,ident=):0:0:a:', + { + 'ComplexIdentifier:0:42:', + children = { + { + 'CurlyBracesIdentifier:0:2:{', + children = { + { + 'Call:0:37:(', + children = { + { + 'Lambda:0:3:{', + children = { + { + 'Comma:0:5:,', + children = { + 'PlainIdentifier(scope=0,ident=b):0:4:b', + 'PlainIdentifier(scope=0,ident=c):0:6: c', + }, + }, + { + 'Arrow:0:8: ->', + children = { + { + 'BinaryPlus:0:19: +', + children = { + { + 'BinaryPlus:0:14: +', + children = { + 'Register(name=d):0:11: @d', + 'Register(name=e):0:16: @e', + }, + }, + { + 'Call:0:32:(', + children = { + { + 'Nested:0:21: (', + children = { + { + 'Lambda:0:23:{', + children = { + 'PlainIdentifier(scope=0,ident=f):0:24:f', + { + 'Arrow:0:25: ->', + children = { + 'PlainIdentifier(scope=0,ident=g):0:28: g', + }, + }, + }, + }, + }, + }, + 'Register(name=h):0:33:@h', + }, + }, + }, + }, + }, + }, + }, + }, + 'Register(name=i):0:38:@i', + }, + }, + }, + }, + 'PlainIdentifier(scope=0,ident=j):0:42:j', + }, + }, + }, + }, + }, + }, { + hl('IdentifierScope', 'a'), + hl('IdentifierScopeDelimiter', ':'), + hl('Curly', '{'), + hl('Lambda', '{'), + hl('IdentifierName', 'b'), + hl('Comma', ','), + hl('IdentifierName', 'c', 1), + hl('Arrow', '->', 1), + hl('Register', '@d', 1), + hl('BinaryPlus', '+', 1), + hl('Register', '@e', 1), + hl('BinaryPlus', '+', 1), + hl('NestingParenthesis', '(', 1), + hl('Lambda', '{'), + hl('IdentifierName', 'f'), + hl('Arrow', '->', 1), + hl('IdentifierName', 'g', 1), + hl('Lambda', '}'), + hl('NestingParenthesis', ')'), + hl('CallingParenthesis', '('), + hl('Register', '@h'), + hl('CallingParenthesis', ')'), + hl('Lambda', '}'), + hl('CallingParenthesis', '('), + hl('Register', '@i'), + hl('CallingParenthesis', ')'), + hl('Curly', '}'), + hl('IdentifierName', 'j'), + }) + check_parsing('{@a + @b : @c + @d, @e + @f : @g + @i}', 0, { + -- 01234567890123456789012345678901234567 + -- 0 1 2 3 + ast = { + { + 'DictLiteral:0:0:{', + children = { + { + 'Comma:0:18:,', + children = { + { + 'Colon:0:8: :', + children = { + { + 'BinaryPlus:0:3: +', + children = { + 'Register(name=a):0:1:@a', + 'Register(name=b):0:5: @b', + }, + }, + { + 'BinaryPlus:0:13: +', + children = { + 'Register(name=c):0:10: @c', + 'Register(name=d):0:15: @d', + }, + }, + }, + }, + { + 'Colon:0:27: :', + children = { + { + 'BinaryPlus:0:22: +', + children = { + 'Register(name=e):0:19: @e', + 'Register(name=f):0:24: @f', + }, + }, + { + 'BinaryPlus:0:32: +', + children = { + 'Register(name=g):0:29: @g', + 'Register(name=i):0:34: @i', + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, { + hl('Dict', '{'), + hl('Register', '@a'), + hl('BinaryPlus', '+', 1), + hl('Register', '@b', 1), + hl('Colon', ':', 1), + hl('Register', '@c', 1), + hl('BinaryPlus', '+', 1), + hl('Register', '@d', 1), + hl('Comma', ','), + hl('Register', '@e', 1), + hl('BinaryPlus', '+', 1), + hl('Register', '@f', 1), + hl('Colon', ':', 1), + hl('Register', '@g', 1), + hl('BinaryPlus', '+', 1), + hl('Register', '@i', 1), + hl('Dict', '}'), + }) + check_parsing('-> -> ->', 0, { + -- 01234567 + ast = { + { + 'Arrow:0:0:->', + children = { + 'Missing:0:0:', + { + 'Arrow:0:2: ->', + children = { + 'Missing:0:2:', + { + 'Arrow:0:5: ->', + children = { + 'Missing:0:5:', + }, + }, + }, + }, + }, + }, + }, + err = { + arg = '-> -> ->', + msg = 'E15: Unexpected arrow: %.*s', + }, + }, { + hl('InvalidArrow', '->'), + hl('InvalidArrow', '->', 1), + hl('InvalidArrow', '->', 1), + }) + check_parsing('a -> b -> c -> d', 0, { + -- 0123456789012345 + -- 0 1 + ast = { + { + 'Arrow:0:1: ->', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'Arrow:0:6: ->', + children = { + 'PlainIdentifier(scope=0,ident=b):0:4: b', + { + 'Arrow:0:11: ->', + children = { + 'PlainIdentifier(scope=0,ident=c):0:9: c', + 'PlainIdentifier(scope=0,ident=d):0:14: d', + }, + }, + }, + }, + }, + }, + }, + err = { + arg = '-> b -> c -> d', + msg = 'E15: Arrow outside of lambda: %.*s', + }, + }, { + hl('IdentifierName', 'a'), + hl('InvalidArrow', '->', 1), + hl('IdentifierName', 'b', 1), + hl('InvalidArrow', '->', 1), + hl('IdentifierName', 'c', 1), + hl('InvalidArrow', '->', 1), + hl('IdentifierName', 'd', 1), + }) + check_parsing('{a -> b -> c}', 0, { + -- 0123456789012 + -- 0 1 + ast = { + { + 'Lambda:0:0:{', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + { + 'Arrow:0:2: ->', + children = { + { + 'Arrow:0:7: ->', + children = { + 'PlainIdentifier(scope=0,ident=b):0:5: b', + 'PlainIdentifier(scope=0,ident=c):0:10: c', + }, + }, + }, + }, + }, + }, + }, + err = { + arg = '-> c}', + msg = 'E15: Arrow outside of lambda: %.*s', + }, + }, { + hl('Lambda', '{'), + hl('IdentifierName', 'a'), + hl('Arrow', '->', 1), + hl('IdentifierName', 'b', 1), + hl('InvalidArrow', '->', 1), + hl('IdentifierName', 'c', 1), + hl('Lambda', '}'), + }) + check_parsing('{a: -> b}', 0, { + -- 012345678 + ast = { + { + 'CurlyBracesIdentifier:0:0:{', + children = { + { + 'Arrow:0:3: ->', + children = { + 'PlainIdentifier(scope=a,ident=):0:1:a:', + 'PlainIdentifier(scope=0,ident=b):0:6: b', + }, + }, + }, + }, + }, + err = { + arg = '-> b}', + msg = 'E15: Arrow outside of lambda: %.*s', + }, + }, { + hl('Curly', '{'), + hl('IdentifierScope', 'a'), + hl('IdentifierScopeDelimiter', ':'), + hl('InvalidArrow', '->', 1), + hl('IdentifierName', 'b', 1), + hl('Curly', '}'), + }) + + check_parsing('{a:b -> b}', 0, { + -- 0123456789 + ast = { + { + 'CurlyBracesIdentifier:0:0:{', + children = { + { + 'Arrow:0:4: ->', + children = { + 'PlainIdentifier(scope=a,ident=b):0:1:a:b', + 'PlainIdentifier(scope=0,ident=b):0:7: b', + }, + }, + }, + }, + }, + err = { + arg = '-> b}', + msg = 'E15: Arrow outside of lambda: %.*s', + }, + }, { + hl('Curly', '{'), + hl('IdentifierScope', 'a'), + hl('IdentifierScopeDelimiter', ':'), + hl('IdentifierName', 'b'), + hl('InvalidArrow', '->', 1), + hl('IdentifierName', 'b', 1), + hl('Curly', '}'), + }) + + check_parsing('{a#b -> b}', 0, { + -- 0123456789 + ast = { + { + 'CurlyBracesIdentifier:0:0:{', + children = { + { + 'Arrow:0:4: ->', + children = { + 'PlainIdentifier(scope=0,ident=a#b):0:1:a#b', + 'PlainIdentifier(scope=0,ident=b):0:7: b', + }, + }, + }, + }, + }, + err = { + arg = '-> b}', + msg = 'E15: Arrow outside of lambda: %.*s', + }, + }, { + hl('Curly', '{'), + hl('IdentifierName', 'a#b'), + hl('InvalidArrow', '->', 1), + hl('IdentifierName', 'b', 1), + hl('Curly', '}'), + }) + check_parsing('{a : b : c}', 0, { + -- 01234567890 + -- 0 1 + ast = { + { + 'DictLiteral:0:0:{', + children = { + { + 'Colon:0:2: :', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + { + 'Colon:0:6: :', + children = { + 'PlainIdentifier(scope=0,ident=b):0:4: b', + 'PlainIdentifier(scope=0,ident=c):0:8: c', + }, + }, + }, + }, + }, + }, + }, + err = { + arg = ': c}', + msg = 'E15: Colon outside of dictionary or ternary operator: %.*s', + }, + }, { + hl('Dict', '{'), + hl('IdentifierName', 'a'), + hl('Colon', ':', 1), + hl('IdentifierName', 'b', 1), + hl('InvalidColon', ':', 1), + hl('IdentifierName', 'c', 1), + hl('Dict', '}'), + }) + check_parsing('{', 0, { + -- 0 + ast = { + 'UnknownFigure:0:0:{', + }, + err = { + arg = '{', + msg = 'E15: Missing closing figure brace: %.*s', + }, + }, { + hl('FigureBrace', '{'), + }) + check_parsing('{a', 0, { + -- 01 + ast = { + { + 'UnknownFigure:0:0:{', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + }, + }, + }, + err = { + arg = '{a', + msg = 'E15: Missing closing figure brace: %.*s', + }, + }, { + hl('FigureBrace', '{'), + hl('IdentifierName', 'a'), + }) + check_parsing('{a,b', 0, { + -- 0123 + ast = { + { + 'Lambda:0:0:{', + children = { + { + 'Comma:0:2:,', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + 'PlainIdentifier(scope=0,ident=b):0:3:b', + }, + }, + }, + }, + }, + err = { + arg = '{a,b', + msg = 'E15: Missing closing figure brace for lambda: %.*s', + }, + }, { + hl('Lambda', '{'), + hl('IdentifierName', 'a'), + hl('Comma', ','), + hl('IdentifierName', 'b'), + }) + check_parsing('{a,b->', 0, { + -- 012345 + ast = { + { + 'Lambda:0:0:{', + children = { + { + 'Comma:0:2:,', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + 'PlainIdentifier(scope=0,ident=b):0:3:b', + }, + }, + 'Arrow:0:4:->', + }, + }, + }, + err = { + arg = '', + msg = 'E15: Expected value, got EOC: %.*s', + }, + }, { + hl('Lambda', '{'), + hl('IdentifierName', 'a'), + hl('Comma', ','), + hl('IdentifierName', 'b'), + hl('Arrow', '->'), + }) + check_parsing('{a,b->c', 0, { + -- 0123456 + ast = { + { + 'Lambda:0:0:{', + children = { + { + 'Comma:0:2:,', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + 'PlainIdentifier(scope=0,ident=b):0:3:b', + }, + }, + { + 'Arrow:0:4:->', + children = { + 'PlainIdentifier(scope=0,ident=c):0:6:c', + }, + }, + }, + }, + }, + err = { + arg = '{a,b->c', + msg = 'E15: Missing closing figure brace for lambda: %.*s', + }, + }, { + hl('Lambda', '{'), + hl('IdentifierName', 'a'), + hl('Comma', ','), + hl('IdentifierName', 'b'), + hl('Arrow', '->'), + hl('IdentifierName', 'c'), + }) + check_parsing('{a : b', 0, { + -- 012345 + ast = { + { + 'DictLiteral:0:0:{', + children = { + { + 'Colon:0:2: :', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + 'PlainIdentifier(scope=0,ident=b):0:4: b', + }, + }, + }, + }, + }, + err = { + arg = '{a : b', + msg = 'E723: Missing end of Dictionary \'}\': %.*s', + }, + }, { + hl('Dict', '{'), + hl('IdentifierName', 'a'), + hl('Colon', ':', 1), + hl('IdentifierName', 'b', 1), + }) + check_parsing('{a : b,', 0, { + -- 0123456 + ast = { + { + 'DictLiteral:0:0:{', + children = { + { + 'Comma:0:6:,', + children = { + { + 'Colon:0:2: :', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + 'PlainIdentifier(scope=0,ident=b):0:4: b', + }, + }, + }, + }, + }, + }, + }, + err = { + arg = '', + msg = 'E15: Expected value, got EOC: %.*s', + }, + }, { + hl('Dict', '{'), + hl('IdentifierName', 'a'), + hl('Colon', ':', 1), + hl('IdentifierName', 'b', 1), + hl('Comma', ','), + }) + end) + it('works with ternary operator', function() + check_parsing('a ? b : c', 0, { + -- 012345678 + ast = { + { + 'Ternary:0:1: ?', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'TernaryValue:0:5: :', + children = { + 'PlainIdentifier(scope=0,ident=b):0:3: b', + 'PlainIdentifier(scope=0,ident=c):0:7: c', + }, + }, + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('Ternary', '?', 1), + hl('IdentifierName', 'b', 1), + hl('TernaryColon', ':', 1), + hl('IdentifierName', 'c', 1), + }) + check_parsing('@a?@b?@c:@d:@e', 0, { + -- 01234567890123 + -- 0 1 + ast = { + { + 'Ternary:0:2:?', + children = { + 'Register(name=a):0:0:@a', + { + 'TernaryValue:0:11::', + children = { + { + 'Ternary:0:5:?', + children = { + 'Register(name=b):0:3:@b', + { + 'TernaryValue:0:8::', + children = { + 'Register(name=c):0:6:@c', + 'Register(name=d):0:9:@d', + }, + }, + }, + }, + 'Register(name=e):0:12:@e', + }, + }, + }, + }, + }, + }, { + hl('Register', '@a'), + hl('Ternary', '?'), + hl('Register', '@b'), + hl('Ternary', '?'), + hl('Register', '@c'), + hl('TernaryColon', ':'), + hl('Register', '@d'), + hl('TernaryColon', ':'), + hl('Register', '@e'), + }) + check_parsing('@a?@b:@c?@d:@e', 0, { + -- 01234567890123 + -- 0 1 + ast = { + { + 'Ternary:0:2:?', + children = { + 'Register(name=a):0:0:@a', + { + 'TernaryValue:0:5::', + children = { + 'Register(name=b):0:3:@b', + { + 'Ternary:0:8:?', + children = { + 'Register(name=c):0:6:@c', + { + 'TernaryValue:0:11::', + children = { + 'Register(name=d):0:9:@d', + 'Register(name=e):0:12:@e', + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, { + hl('Register', '@a'), + hl('Ternary', '?'), + hl('Register', '@b'), + hl('TernaryColon', ':'), + hl('Register', '@c'), + hl('Ternary', '?'), + hl('Register', '@d'), + hl('TernaryColon', ':'), + hl('Register', '@e'), + }) + check_parsing('@a?@b?@c?@d:@e?@f:@g:@h?@i:@j:@k', 0, { + -- 01234567890123456789012345678901 + -- 0 1 2 3 + ast = { + { + 'Ternary:0:2:?', + children = { + 'Register(name=a):0:0:@a', + { + 'TernaryValue:0:29::', + children = { + { + 'Ternary:0:5:?', + children = { + 'Register(name=b):0:3:@b', + { + 'TernaryValue:0:20::', + children = { + { + 'Ternary:0:8:?', + children = { + 'Register(name=c):0:6:@c', + { + 'TernaryValue:0:11::', + children = { + 'Register(name=d):0:9:@d', + { + 'Ternary:0:14:?', + children = { + 'Register(name=e):0:12:@e', + { + 'TernaryValue:0:17::', + children = { + 'Register(name=f):0:15:@f', + 'Register(name=g):0:18:@g', + }, + }, + }, + }, + }, + }, + }, + }, + { + 'Ternary:0:23:?', + children = { + 'Register(name=h):0:21:@h', + { + 'TernaryValue:0:26::', + children = { + 'Register(name=i):0:24:@i', + 'Register(name=j):0:27:@j', + }, + }, + }, + }, + }, + }, + }, + }, + 'Register(name=k):0:30:@k', + }, + }, + }, + }, + }, + }, { + hl('Register', '@a'), + hl('Ternary', '?'), + hl('Register', '@b'), + hl('Ternary', '?'), + hl('Register', '@c'), + hl('Ternary', '?'), + hl('Register', '@d'), + hl('TernaryColon', ':'), + hl('Register', '@e'), + hl('Ternary', '?'), + hl('Register', '@f'), + hl('TernaryColon', ':'), + hl('Register', '@g'), + hl('TernaryColon', ':'), + hl('Register', '@h'), + hl('Ternary', '?'), + hl('Register', '@i'), + hl('TernaryColon', ':'), + hl('Register', '@j'), + hl('TernaryColon', ':'), + hl('Register', '@k'), + }) + check_parsing('?', 0, { + -- 0 + ast = { + { + 'Ternary:0:0:?', + children = { + 'Missing:0:0:', + 'TernaryValue:0:0:?', + }, + }, + }, + err = { + arg = '?', + msg = 'E15: Expected value, got question mark: %.*s', + }, + }, { + hl('InvalidTernary', '?'), + }) + + check_parsing('?:', 0, { + -- 01 + ast = { + { + 'Ternary:0:0:?', + children = { + 'Missing:0:0:', + { + 'TernaryValue:0:1::', + children = { + 'Missing:0:1:', + }, + }, + }, + }, + }, + err = { + arg = '?:', + msg = 'E15: Expected value, got question mark: %.*s', + }, + }, { + hl('InvalidTernary', '?'), + hl('InvalidTernaryColon', ':'), + }) + + check_parsing('?::', 0, { + -- 012 + ast = { + { + 'Colon:0:2::', + children = { + { + 'Ternary:0:0:?', + children = { + 'Missing:0:0:', + { + 'TernaryValue:0:1::', + children = { + 'Missing:0:1:', + 'Missing:0:2:', + }, + }, + }, + }, + }, + }, + }, + err = { + arg = '?::', + msg = 'E15: Expected value, got question mark: %.*s', + }, + }, { + hl('InvalidTernary', '?'), + hl('InvalidTernaryColon', ':'), + hl('InvalidColon', ':'), + }) + + check_parsing('a?b', 0, { + -- 012 + ast = { + { + 'Ternary:0:1:?', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'TernaryValue:0:1:?', + children = { + 'PlainIdentifier(scope=0,ident=b):0:2:b', + }, + }, + }, + }, + }, + err = { + arg = '?b', + msg = 'E109: Missing \':\' after \'?\': %.*s', + }, + }, { + hl('IdentifierName', 'a'), + hl('Ternary', '?'), + hl('IdentifierName', 'b'), + }) + check_parsing('a?b:', 0, { + -- 0123 + ast = { + { + 'Ternary:0:1:?', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'TernaryValue:0:1:?', + children = { + 'PlainIdentifier(scope=b,ident=):0:2:b:', + }, + }, + }, + }, + }, + err = { + arg = '?b:', + msg = 'E109: Missing \':\' after \'?\': %.*s', + }, + }, { + hl('IdentifierName', 'a'), + hl('Ternary', '?'), + hl('IdentifierScope', 'b'), + hl('IdentifierScopeDelimiter', ':'), + }) + + check_parsing('a?b::c', 0, { + -- 012345 + ast = { + { + 'Ternary:0:1:?', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'TernaryValue:0:4::', + children = { + 'PlainIdentifier(scope=b,ident=):0:2:b:', + 'PlainIdentifier(scope=0,ident=c):0:5:c', + }, + }, + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('Ternary', '?'), + hl('IdentifierScope', 'b'), + hl('IdentifierScopeDelimiter', ':'), + hl('TernaryColon', ':'), + hl('IdentifierName', 'c'), + }) + + check_parsing('a?b :', 0, { + -- 01234 + ast = { + { + 'Ternary:0:1:?', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'TernaryValue:0:3: :', + children = { + 'PlainIdentifier(scope=0,ident=b):0:2:b', + }, + }, + }, + }, + }, + err = { + arg = '', + msg = 'E15: Expected value, got EOC: %.*s', + }, + }, { + hl('IdentifierName', 'a'), + hl('Ternary', '?'), + hl('IdentifierName', 'b'), + hl('TernaryColon', ':', 1), + }) + + check_parsing('(@a?@b:@c)?@d:@e', 0, { + -- 0123456789012345 + -- 0 1 + ast = { + { + 'Ternary:0:10:?', + children = { + { + 'Nested:0:0:(', + children = { + { + 'Ternary:0:3:?', + children = { + 'Register(name=a):0:1:@a', + { + 'TernaryValue:0:6::', + children = { + 'Register(name=b):0:4:@b', + 'Register(name=c):0:7:@c', + }, + }, + }, + }, + }, + }, + { + 'TernaryValue:0:13::', + children = { + 'Register(name=d):0:11:@d', + 'Register(name=e):0:14:@e', + }, + }, + }, + }, + }, + }, { + hl('NestingParenthesis', '('), + hl('Register', '@a'), + hl('Ternary', '?'), + hl('Register', '@b'), + hl('TernaryColon', ':'), + hl('Register', '@c'), + hl('NestingParenthesis', ')'), + hl('Ternary', '?'), + hl('Register', '@d'), + hl('TernaryColon', ':'), + hl('Register', '@e'), + }) + + check_parsing('(@a?@b:@c)?(@d?@e:@f):(@g?@h:@i)', 0, { + -- 01234567890123456789012345678901 + -- 0 1 2 3 + ast = { + { + 'Ternary:0:10:?', + children = { + { + 'Nested:0:0:(', + children = { + { + 'Ternary:0:3:?', + children = { + 'Register(name=a):0:1:@a', + { + 'TernaryValue:0:6::', + children = { + 'Register(name=b):0:4:@b', + 'Register(name=c):0:7:@c', + }, + }, + }, + }, + }, + }, + { + 'TernaryValue:0:21::', + children = { + { + 'Nested:0:11:(', + children = { + { + 'Ternary:0:14:?', + children = { + 'Register(name=d):0:12:@d', + { + 'TernaryValue:0:17::', + children = { + 'Register(name=e):0:15:@e', + 'Register(name=f):0:18:@f', + }, + }, + }, + }, + }, + }, + { + 'Nested:0:22:(', + children = { + { + 'Ternary:0:25:?', + children = { + 'Register(name=g):0:23:@g', + { + 'TernaryValue:0:28::', + children = { + 'Register(name=h):0:26:@h', + 'Register(name=i):0:29:@i', + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, { + hl('NestingParenthesis', '('), + hl('Register', '@a'), + hl('Ternary', '?'), + hl('Register', '@b'), + hl('TernaryColon', ':'), + hl('Register', '@c'), + hl('NestingParenthesis', ')'), + hl('Ternary', '?'), + hl('NestingParenthesis', '('), + hl('Register', '@d'), + hl('Ternary', '?'), + hl('Register', '@e'), + hl('TernaryColon', ':'), + hl('Register', '@f'), + hl('NestingParenthesis', ')'), + hl('TernaryColon', ':'), + hl('NestingParenthesis', '('), + hl('Register', '@g'), + hl('Ternary', '?'), + hl('Register', '@h'), + hl('TernaryColon', ':'), + hl('Register', '@i'), + hl('NestingParenthesis', ')'), + }) + + check_parsing('(@a?@b:@c)?@d?@e:@f:@g?@h:@i', 0, { + -- 0123456789012345678901234567 + -- 0 1 2 + ast = { + { + 'Ternary:0:10:?', + children = { + { + 'Nested:0:0:(', + children = { + { + 'Ternary:0:3:?', + children = { + 'Register(name=a):0:1:@a', + { + 'TernaryValue:0:6::', + children = { + 'Register(name=b):0:4:@b', + 'Register(name=c):0:7:@c', + }, + }, + }, + }, + }, + }, + { + 'TernaryValue:0:19::', + children = { + { + 'Ternary:0:13:?', + children = { + 'Register(name=d):0:11:@d', + { + 'TernaryValue:0:16::', + children = { + 'Register(name=e):0:14:@e', + 'Register(name=f):0:17:@f', + }, + }, + }, + }, + { + 'Ternary:0:22:?', + children = { + 'Register(name=g):0:20:@g', + { + 'TernaryValue:0:25::', + children = { + 'Register(name=h):0:23:@h', + 'Register(name=i):0:26:@i', + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, { + hl('NestingParenthesis', '('), + hl('Register', '@a'), + hl('Ternary', '?'), + hl('Register', '@b'), + hl('TernaryColon', ':'), + hl('Register', '@c'), + hl('NestingParenthesis', ')'), + hl('Ternary', '?'), + hl('Register', '@d'), + hl('Ternary', '?'), + hl('Register', '@e'), + hl('TernaryColon', ':'), + hl('Register', '@f'), + hl('TernaryColon', ':'), + hl('Register', '@g'), + hl('Ternary', '?'), + hl('Register', '@h'), + hl('TernaryColon', ':'), + hl('Register', '@i'), + }) + check_parsing('a?b{cdef}g:h', 0, { + -- 012345678901 + -- 0 1 + ast = { + { + 'Ternary:0:1:?', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'TernaryValue:0:10::', + children = { + { + 'ComplexIdentifier:0:3:', + children = { + 'PlainIdentifier(scope=0,ident=b):0:2:b', + { + 'ComplexIdentifier:0:9:', + children = { + { + 'CurlyBracesIdentifier:0:3:{', + children = { + 'PlainIdentifier(scope=0,ident=cdef):0:4:cdef', + }, + }, + 'PlainIdentifier(scope=0,ident=g):0:9:g', + }, + }, + }, + }, + 'PlainIdentifier(scope=0,ident=h):0:11:h', + }, + }, + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('Ternary', '?'), + hl('IdentifierName', 'b'), + hl('Curly', '{'), + hl('IdentifierName', 'cdef'), + hl('Curly', '}'), + hl('IdentifierName', 'g'), + hl('TernaryColon', ':'), + hl('IdentifierName', 'h'), + }) + check_parsing('a ? b : c : d', 0, { + -- 0123456789012 + -- 0 1 + ast = { + { + 'Colon:0:9: :', + children = { + { + 'Ternary:0:1: ?', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'TernaryValue:0:5: :', + children = { + 'PlainIdentifier(scope=0,ident=b):0:3: b', + 'PlainIdentifier(scope=0,ident=c):0:7: c', + }, + }, + }, + }, + 'PlainIdentifier(scope=0,ident=d):0:11: d', + }, + }, + }, + err = { + arg = ': d', + msg = 'E15: Colon outside of dictionary or ternary operator: %.*s', + }, + }, { + hl('IdentifierName', 'a'), + hl('Ternary', '?', 1), + hl('IdentifierName', 'b', 1), + hl('TernaryColon', ':', 1), + hl('IdentifierName', 'c', 1), + hl('InvalidColon', ':', 1), + hl('IdentifierName', 'd', 1), + }) + end) + it('works with comparison operators', function() + check_parsing('a == b', 0, { + -- 012345 + ast = { + { + 'Comparison(type=Equal,inv=0,ccs=UseOption):0:1: ==', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainIdentifier(scope=0,ident=b):0:4: b', + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('Comparison', '==', 1), + hl('IdentifierName', 'b', 1), + }) + + check_parsing('a ==? b', 0, { + -- 0123456 + ast = { + { + 'Comparison(type=Equal,inv=0,ccs=IgnoreCase):0:1: ==?', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainIdentifier(scope=0,ident=b):0:5: b', + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('Comparison', '==', 1), + hl('ComparisonModifier', '?'), + hl('IdentifierName', 'b', 1), + }) + + check_parsing('a ==# b', 0, { + -- 0123456 + ast = { + { + 'Comparison(type=Equal,inv=0,ccs=MatchCase):0:1: ==#', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainIdentifier(scope=0,ident=b):0:5: b', + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('Comparison', '==', 1), + hl('ComparisonModifier', '#'), + hl('IdentifierName', 'b', 1), + }) + + check_parsing('a !=# b', 0, { + -- 0123456 + ast = { + { + 'Comparison(type=Equal,inv=1,ccs=MatchCase):0:1: !=#', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainIdentifier(scope=0,ident=b):0:5: b', + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('Comparison', '!=', 1), + hl('ComparisonModifier', '#'), + hl('IdentifierName', 'b', 1), + }) + + check_parsing('a <=# b', 0, { + -- 0123456 + ast = { + { + 'Comparison(type=Greater,inv=1,ccs=MatchCase):0:1: <=#', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainIdentifier(scope=0,ident=b):0:5: b', + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('Comparison', '<=', 1), + hl('ComparisonModifier', '#'), + hl('IdentifierName', 'b', 1), + }) + + check_parsing('a >=# b', 0, { + -- 0123456 + ast = { + { + 'Comparison(type=GreaterOrEqual,inv=0,ccs=MatchCase):0:1: >=#', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainIdentifier(scope=0,ident=b):0:5: b', + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('Comparison', '>=', 1), + hl('ComparisonModifier', '#'), + hl('IdentifierName', 'b', 1), + }) + + check_parsing('a ># b', 0, { + -- 012345 + ast = { + { + 'Comparison(type=Greater,inv=0,ccs=MatchCase):0:1: >#', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainIdentifier(scope=0,ident=b):0:4: b', + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('Comparison', '>', 1), + hl('ComparisonModifier', '#'), + hl('IdentifierName', 'b', 1), + }) + + check_parsing('a <# b', 0, { + -- 012345 + ast = { + { + 'Comparison(type=GreaterOrEqual,inv=1,ccs=MatchCase):0:1: <#', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainIdentifier(scope=0,ident=b):0:4: b', + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('Comparison', '<', 1), + hl('ComparisonModifier', '#'), + hl('IdentifierName', 'b', 1), + }) + + check_parsing('a is#b', 0, { + -- 012345 + ast = { + { + 'Comparison(type=Identical,inv=0,ccs=MatchCase):0:1: is#', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainIdentifier(scope=0,ident=b):0:5:b', + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('Comparison', 'is', 1), + hl('ComparisonModifier', '#'), + hl('IdentifierName', 'b'), + }) + + check_parsing('a is?b', 0, { + -- 012345 + ast = { + { + 'Comparison(type=Identical,inv=0,ccs=IgnoreCase):0:1: is?', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainIdentifier(scope=0,ident=b):0:5:b', + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('Comparison', 'is', 1), + hl('ComparisonModifier', '?'), + hl('IdentifierName', 'b'), + }) + + check_parsing('a isnot b', 0, { + -- 012345678 + ast = { + { + 'Comparison(type=Identical,inv=1,ccs=UseOption):0:1: isnot', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainIdentifier(scope=0,ident=b):0:7: b', + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('Comparison', 'isnot', 1), + hl('IdentifierName', 'b', 1), + }) + + check_parsing('a < b < c', 0, { + -- 012345678 + ast = { + { + 'Comparison(type=GreaterOrEqual,inv=1,ccs=UseOption):0:1: <', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'Comparison(type=GreaterOrEqual,inv=1,ccs=UseOption):0:5: <', + children = { + 'PlainIdentifier(scope=0,ident=b):0:3: b', + 'PlainIdentifier(scope=0,ident=c):0:7: c', + }, + }, + }, + }, + }, + err = { + arg = ' < c', + msg = 'E15: Operator is not associative: %.*s', + }, + }, { + hl('IdentifierName', 'a'), + hl('Comparison', '<', 1), + hl('IdentifierName', 'b', 1), + hl('InvalidComparison', '<', 1), + hl('IdentifierName', 'c', 1), + }) + + check_parsing('a < b <# c', 0, { + -- 012345678 + ast = { + { + 'Comparison(type=GreaterOrEqual,inv=1,ccs=UseOption):0:1: <', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'Comparison(type=GreaterOrEqual,inv=1,ccs=MatchCase):0:5: <#', + children = { + 'PlainIdentifier(scope=0,ident=b):0:3: b', + 'PlainIdentifier(scope=0,ident=c):0:8: c', + }, + }, + }, + }, + }, + err = { + arg = ' <# c', + msg = 'E15: Operator is not associative: %.*s', + }, + }, { + hl('IdentifierName', 'a'), + hl('Comparison', '<', 1), + hl('IdentifierName', 'b', 1), + hl('InvalidComparison', '<', 1), + hl('InvalidComparisonModifier', '#'), + hl('IdentifierName', 'c', 1), + }) + + check_parsing('a += b', 0, { + -- 012345 + ast = { + { + 'Comparison(type=Equal,inv=0,ccs=UseOption):0:3:=', + children = { + { + 'BinaryPlus:0:1: +', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'Missing:0:3:', + }, + }, + 'PlainIdentifier(scope=0,ident=b):0:4: b', + }, + }, + }, + err = { + arg = '= b', + msg = 'E15: Expected == or =~: %.*s', + }, + }, { + hl('IdentifierName', 'a'), + hl('BinaryPlus', '+', 1), + hl('InvalidComparison', '='), + hl('IdentifierName', 'b', 1), + }) + check_parsing('a + b == c + d', 0, { + -- 01234567890123 + -- 0 1 + ast = { + { + 'Comparison(type=Equal,inv=0,ccs=UseOption):0:5: ==', + children = { + { + 'BinaryPlus:0:1: +', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainIdentifier(scope=0,ident=b):0:3: b', + }, + }, + { + 'BinaryPlus:0:10: +', + children = { + 'PlainIdentifier(scope=0,ident=c):0:8: c', + 'PlainIdentifier(scope=0,ident=d):0:12: d', + }, + }, + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('BinaryPlus', '+', 1), + hl('IdentifierName', 'b', 1), + hl('Comparison', '==', 1), + hl('IdentifierName', 'c', 1), + hl('BinaryPlus', '+', 1), + hl('IdentifierName', 'd', 1), + }) + check_parsing('+ a == + b', 0, { + -- 0123456789 + ast = { + { + 'Comparison(type=Equal,inv=0,ccs=UseOption):0:3: ==', + children = { + { + 'UnaryPlus:0:0:+', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1: a', + }, + }, + { + 'UnaryPlus:0:6: +', + children = { + 'PlainIdentifier(scope=0,ident=b):0:8: b', + }, + }, + }, + }, + }, + }, { + hl('UnaryPlus', '+'), + hl('IdentifierName', 'a', 1), + hl('Comparison', '==', 1), + hl('UnaryPlus', '+', 1), + hl('IdentifierName', 'b', 1), + }) + end) + it('works with concat/subscript', function() + check_parsing('.', 0, { + -- 0 + ast = { + { + 'ConcatOrSubscript:0:0:.', + children = { + 'Missing:0:0:', + }, + }, + }, + err = { + arg = '.', + msg = 'E15: Unexpected dot: %.*s', + }, + }, { + hl('InvalidConcatOrSubscript', '.'), + }) + + check_parsing('a.', 0, { + -- 01 + ast = { + { + 'ConcatOrSubscript:0:1:.', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + }, + }, + }, + err = { + arg = '', + msg = 'E15: Expected value, got EOC: %.*s', + }, + }, { + hl('IdentifierName', 'a'), + hl('ConcatOrSubscript', '.'), + }) + + check_parsing('a.b', 0, { + -- 012 + ast = { + { + 'ConcatOrSubscript:0:1:.', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainKey(key=b):0:2:b', + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('ConcatOrSubscript', '.'), + hl('IdentifierKey', 'b'), + }) + + check_parsing('1.2', 0, { + -- 012 + ast = { + 'Float(val=1.200000e+00):0:0:1.2', + }, + }, { + hl('Float', '1.2'), + }) + + check_parsing('1.2 + 1.3e-5', 0, { + -- 012345678901 + -- 0 1 + ast = { + { + 'BinaryPlus:0:3: +', + children = { + 'Float(val=1.200000e+00):0:0:1.2', + 'Float(val=1.300000e-05):0:5: 1.3e-5', + }, + }, + }, + }, { + hl('Float', '1.2'), + hl('BinaryPlus', '+', 1), + hl('Float', '1.3e-5', 1), + }) + + check_parsing('a . 1.2 + 1.3e-5', 0, { + -- 0123456789012345 + -- 0 1 + ast = { + { + 'BinaryPlus:0:7: +', + children = { + { + 'Concat:0:1: .', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'ConcatOrSubscript:0:5:.', + children = { + 'Integer(val=1):0:3: 1', + 'PlainKey(key=2):0:6:2', + }, + }, + }, + }, + 'Float(val=1.300000e-05):0:9: 1.3e-5', + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('Concat', '.', 1), + hl('Number', '1', 1), + hl('ConcatOrSubscript', '.'), + hl('IdentifierKey', '2'), + hl('BinaryPlus', '+', 1), + hl('Float', '1.3e-5', 1), + }) + + check_parsing('1.3e-5 + 1.2 . a', 0, { + -- 0123456789012345 + -- 0 1 + ast = { + { + 'Concat:0:12: .', + children = { + { + 'BinaryPlus:0:6: +', + children = { + 'Float(val=1.300000e-05):0:0:1.3e-5', + 'Float(val=1.200000e+00):0:8: 1.2', + }, + }, + 'PlainIdentifier(scope=0,ident=a):0:14: a', + }, + }, + }, + }, { + hl('Float', '1.3e-5'), + hl('BinaryPlus', '+', 1), + hl('Float', '1.2', 1), + hl('Concat', '.', 1), + hl('IdentifierName', 'a', 1), + }) + + check_parsing('1.3e-5 + a . 1.2', 0, { + -- 0123456789012345 + -- 0 1 + ast = { + { + 'Concat:0:10: .', + children = { + { + 'BinaryPlus:0:6: +', + children = { + 'Float(val=1.300000e-05):0:0:1.3e-5', + 'PlainIdentifier(scope=0,ident=a):0:8: a', + }, + }, + { + 'ConcatOrSubscript:0:14:.', + children = { + 'Integer(val=1):0:12: 1', + 'PlainKey(key=2):0:15:2', + }, + }, + }, + }, + }, + }, { + hl('Float', '1.3e-5'), + hl('BinaryPlus', '+', 1), + hl('IdentifierName', 'a', 1), + hl('Concat', '.', 1), + hl('Number', '1', 1), + hl('ConcatOrSubscript', '.'), + hl('IdentifierKey', '2'), + }) + + check_parsing('1.2.3', 0, { + -- 01234 + ast = { + { + 'ConcatOrSubscript:0:3:.', + children = { + { + 'ConcatOrSubscript:0:1:.', + children = { + 'Integer(val=1):0:0:1', + 'PlainKey(key=2):0:2:2', + }, + }, + 'PlainKey(key=3):0:4:3', + }, + }, + }, + }, { + hl('Number', '1'), + hl('ConcatOrSubscript', '.'), + hl('IdentifierKey', '2'), + hl('ConcatOrSubscript', '.'), + hl('IdentifierKey', '3'), + }) + + check_parsing('a.1.2', 0, { + -- 01234 + ast = { + { + 'ConcatOrSubscript:0:3:.', + children = { + { + 'ConcatOrSubscript:0:1:.', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainKey(key=1):0:2:1', + }, + }, + 'PlainKey(key=2):0:4:2', + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('ConcatOrSubscript', '.'), + hl('IdentifierKey', '1'), + hl('ConcatOrSubscript', '.'), + hl('IdentifierKey', '2'), + }) + + check_parsing('a . 1.2', 0, { + -- 0123456 + ast = { + { + 'Concat:0:1: .', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'ConcatOrSubscript:0:5:.', + children = { + 'Integer(val=1):0:3: 1', + 'PlainKey(key=2):0:6:2', + }, + }, + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('Concat', '.', 1), + hl('Number', '1', 1), + hl('ConcatOrSubscript', '.'), + hl('IdentifierKey', '2'), + }) + + check_parsing('+a . +b', 0, { + -- 0123456 + ast = { + { + 'Concat:0:2: .', + children = { + { + 'UnaryPlus:0:0:+', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + }, + }, + { + 'UnaryPlus:0:4: +', + children = { + 'PlainIdentifier(scope=0,ident=b):0:6:b', + }, + }, + }, + }, + }, + }, { + hl('UnaryPlus', '+'), + hl('IdentifierName', 'a'), + hl('Concat', '.', 1), + hl('UnaryPlus', '+', 1), + hl('IdentifierName', 'b'), + }) + + check_parsing('a. b', 0, { + -- 0123 + ast = { + { + 'Concat:0:1:.', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainIdentifier(scope=0,ident=b):0:2: b', + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('ConcatOrSubscript', '.'), + hl('IdentifierName', 'b', 1), + }) + + check_parsing('a. 1', 0, { + -- 0123 + ast = { + { + 'Concat:0:1:.', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'Integer(val=1):0:2: 1', + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('ConcatOrSubscript', '.'), + hl('Number', '1', 1), + }) + end) + it('works with bracket subscripts', function() + check_parsing(':', 0, { + -- 0 + ast = { + { + 'Colon:0:0::', + children = { + 'Missing:0:0:', + }, + }, + }, + err = { + arg = ':', + msg = 'E15: Colon outside of dictionary or ternary operator: %.*s', + }, + }, { + hl('InvalidColon', ':'), + }) + check_parsing('a[]', 0, { + -- 012 + ast = { + { + 'Subscript:0:1:[', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + }, + }, + }, + err = { + arg = ']', + msg = 'E15: Expected value, got closing bracket: %.*s', + }, + }, { + hl('IdentifierName', 'a'), + hl('SubscriptBracket', '['), + hl('InvalidSubscriptBracket', ']'), + }) + check_parsing('a[b:]', 0, { + -- 01234 + ast = { + { + 'Subscript:0:1:[', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainIdentifier(scope=b,ident=):0:2:b:', + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('SubscriptBracket', '['), + hl('IdentifierScope', 'b'), + hl('IdentifierScopeDelimiter', ':'), + hl('SubscriptBracket', ']'), + }) + + check_parsing('a[b:c]', 0, { + -- 012345 + ast = { + { + 'Subscript:0:1:[', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainIdentifier(scope=b,ident=c):0:2:b:c', + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('SubscriptBracket', '['), + hl('IdentifierScope', 'b'), + hl('IdentifierScopeDelimiter', ':'), + hl('IdentifierName', 'c'), + hl('SubscriptBracket', ']'), + }) + check_parsing('a[b : c]', 0, { + -- 01234567 + ast = { + { + 'Subscript:0:1:[', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'Colon:0:3: :', + children = { + 'PlainIdentifier(scope=0,ident=b):0:2:b', + 'PlainIdentifier(scope=0,ident=c):0:5: c', + }, + }, + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('SubscriptBracket', '['), + hl('IdentifierName', 'b'), + hl('SubscriptColon', ':', 1), + hl('IdentifierName', 'c', 1), + hl('SubscriptBracket', ']'), + }) + + check_parsing('a[: b]', 0, { + -- 012345 + ast = { + { + 'Subscript:0:1:[', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'Colon:0:2::', + children = { + 'Missing:0:2:', + 'PlainIdentifier(scope=0,ident=b):0:3: b', + }, + }, + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('SubscriptBracket', '['), + hl('SubscriptColon', ':'), + hl('IdentifierName', 'b', 1), + hl('SubscriptBracket', ']'), + }) + + check_parsing('a[b :]', 0, { + -- 012345 + ast = { + { + 'Subscript:0:1:[', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'Colon:0:3: :', + children = { + 'PlainIdentifier(scope=0,ident=b):0:2:b', + }, + }, + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('SubscriptBracket', '['), + hl('IdentifierName', 'b'), + hl('SubscriptColon', ':', 1), + hl('SubscriptBracket', ']'), + }) + check_parsing('a[b][c][d](e)(f)(g)', 0, { + -- 0123456789012345678 + -- 0 1 + ast = { + { + 'Call:0:16:(', + children = { + { + 'Call:0:13:(', + children = { + { + 'Call:0:10:(', + children = { + { + 'Subscript:0:7:[', + children = { + { + 'Subscript:0:4:[', + children = { + { + 'Subscript:0:1:[', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainIdentifier(scope=0,ident=b):0:2:b', + }, + }, + 'PlainIdentifier(scope=0,ident=c):0:5:c', + }, + }, + 'PlainIdentifier(scope=0,ident=d):0:8:d', + }, + }, + 'PlainIdentifier(scope=0,ident=e):0:11:e', + }, + }, + 'PlainIdentifier(scope=0,ident=f):0:14:f', + }, + }, + 'PlainIdentifier(scope=0,ident=g):0:17:g', + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('SubscriptBracket', '['), + hl('IdentifierName', 'b'), + hl('SubscriptBracket', ']'), + hl('SubscriptBracket', '['), + hl('IdentifierName', 'c'), + hl('SubscriptBracket', ']'), + hl('SubscriptBracket', '['), + hl('IdentifierName', 'd'), + hl('SubscriptBracket', ']'), + hl('CallingParenthesis', '('), + hl('IdentifierName', 'e'), + hl('CallingParenthesis', ')'), + hl('CallingParenthesis', '('), + hl('IdentifierName', 'f'), + hl('CallingParenthesis', ')'), + hl('CallingParenthesis', '('), + hl('IdentifierName', 'g'), + hl('CallingParenthesis', ')'), + }) + check_parsing('{a}{b}{c}[d][e][f]', 0, { + -- 012345678901234567 + -- 0 1 + ast = { + { + 'Subscript:0:15:[', + children = { + { + 'Subscript:0:12:[', + children = { + { + 'Subscript:0:9:[', + children = { + { + 'ComplexIdentifier:0:3:', + children = { + { + 'CurlyBracesIdentifier:0:0:{', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + }, + }, + { + 'ComplexIdentifier:0:6:', + children = { + { + 'CurlyBracesIdentifier:0:3:{', + children = { + 'PlainIdentifier(scope=0,ident=b):0:4:b', + }, + }, + { + 'CurlyBracesIdentifier:0:6:{', + children = { + 'PlainIdentifier(scope=0,ident=c):0:7:c', + }, + }, + }, + }, + }, + }, + 'PlainIdentifier(scope=0,ident=d):0:10:d', + }, + }, + 'PlainIdentifier(scope=0,ident=e):0:13:e', + }, + }, + 'PlainIdentifier(scope=0,ident=f):0:16:f', + }, + }, + }, + }, { + hl('Curly', '{'), + hl('IdentifierName', 'a'), + hl('Curly', '}'), + hl('Curly', '{'), + hl('IdentifierName', 'b'), + hl('Curly', '}'), + hl('Curly', '{'), + hl('IdentifierName', 'c'), + hl('Curly', '}'), + hl('SubscriptBracket', '['), + hl('IdentifierName', 'd'), + hl('SubscriptBracket', ']'), + hl('SubscriptBracket', '['), + hl('IdentifierName', 'e'), + hl('SubscriptBracket', ']'), + hl('SubscriptBracket', '['), + hl('IdentifierName', 'f'), + hl('SubscriptBracket', ']'), + }) + end) + it('supports list literals', function() + check_parsing('[]', 0, { + -- 01 + ast = { + 'ListLiteral:0:0:[', + }, + }, { + hl('List', '['), + hl('List', ']'), + }) + + check_parsing('[a]', 0, { + -- 012 + ast = { + { + 'ListLiteral:0:0:[', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + }, + }, + }, + }, { + hl('List', '['), + hl('IdentifierName', 'a'), + hl('List', ']'), + }) + + check_parsing('[a, b]', 0, { + -- 012345 + ast = { + { + 'ListLiteral:0:0:[', + children = { + { + 'Comma:0:2:,', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + 'PlainIdentifier(scope=0,ident=b):0:3: b', + }, + }, + }, + }, + }, + }, { + hl('List', '['), + hl('IdentifierName', 'a'), + hl('Comma', ','), + hl('IdentifierName', 'b', 1), + hl('List', ']'), + }) + + check_parsing('[a, b, c]', 0, { + -- 012345678 + ast = { + { + 'ListLiteral:0:0:[', + children = { + { + 'Comma:0:2:,', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + { + 'Comma:0:5:,', + children = { + 'PlainIdentifier(scope=0,ident=b):0:3: b', + 'PlainIdentifier(scope=0,ident=c):0:6: c', + }, + }, + }, + }, + }, + }, + }, + }, { + hl('List', '['), + hl('IdentifierName', 'a'), + hl('Comma', ','), + hl('IdentifierName', 'b', 1), + hl('Comma', ','), + hl('IdentifierName', 'c', 1), + hl('List', ']'), + }) + + check_parsing('[a, b, c, ]', 0, { + -- 01234567890 + -- 0 1 + ast = { + { + 'ListLiteral:0:0:[', + children = { + { + 'Comma:0:2:,', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + { + 'Comma:0:5:,', + children = { + 'PlainIdentifier(scope=0,ident=b):0:3: b', + { + 'Comma:0:8:,', + children = { + 'PlainIdentifier(scope=0,ident=c):0:6: c', + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, { + hl('List', '['), + hl('IdentifierName', 'a'), + hl('Comma', ','), + hl('IdentifierName', 'b', 1), + hl('Comma', ','), + hl('IdentifierName', 'c', 1), + hl('Comma', ','), + hl('List', ']', 1), + }) + + check_parsing('[a : b, c : d]', 0, { + -- 01234567890123 + -- 0 1 + ast = { + { + 'ListLiteral:0:0:[', + children = { + { + 'Comma:0:6:,', + children = { + { + 'Colon:0:2: :', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + 'PlainIdentifier(scope=0,ident=b):0:4: b', + }, + }, + { + 'Colon:0:9: :', + children = { + 'PlainIdentifier(scope=0,ident=c):0:7: c', + 'PlainIdentifier(scope=0,ident=d):0:11: d', + }, + }, + }, + }, + }, + }, + }, + err = { + arg = ': b, c : d]', + msg = 'E15: Colon outside of dictionary or ternary operator: %.*s', + }, + }, { + hl('List', '['), + hl('IdentifierName', 'a'), + hl('InvalidColon', ':', 1), + hl('IdentifierName', 'b', 1), + hl('Comma', ','), + hl('IdentifierName', 'c', 1), + hl('InvalidColon', ':', 1), + hl('IdentifierName', 'd', 1), + hl('List', ']'), + }) + + check_parsing(']', 0, { + -- 0 + ast = { + 'ListLiteral:0:0:', + }, + err = { + arg = ']', + msg = 'E15: Unexpected closing figure brace: %.*s', + }, + }, { + hl('InvalidList', ']'), + }) + + check_parsing('a]', 0, { + -- 01 + ast = { + { + 'ListLiteral:0:1:', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + }, + }, + }, + err = { + arg = ']', + msg = 'E15: Unexpected closing figure brace: %.*s', + }, + }, { + hl('IdentifierName', 'a'), + hl('InvalidList', ']'), + }) + + check_parsing('[] []', 0, { + -- 01234 + ast = { + { + 'OpMissing:0:2:', + children = { + 'ListLiteral:0:0:[', + 'ListLiteral:0:2: [', + }, + }, + }, + err = { + arg = '[]', + msg = 'E15: Missing operator: %.*s', + }, + }, { + hl('List', '['), + hl('List', ']'), + hl('InvalidSpacing', ' '), + hl('List', '['), + hl('List', ']'), + }) + + check_parsing('[][]', 0, { + -- 0123 + ast = { + { + 'Subscript:0:2:[', + children = { + 'ListLiteral:0:0:[', + }, + }, + }, + err = { + arg = ']', + msg = 'E15: Expected value, got closing bracket: %.*s', + }, + }, { + hl('List', '['), + hl('List', ']'), + hl('SubscriptBracket', '['), + hl('InvalidSubscriptBracket', ']'), + }) + + check_parsing('[', 0, { + -- 0 + ast = { + 'ListLiteral:0:0:[', + }, + err = { + arg = '', + msg = 'E15: Expected value, got EOC: %.*s', + }, + }, { + hl('List', '['), + }) + + check_parsing('[1', 0, { + -- 01 + ast = { + { + 'ListLiteral:0:0:[', + children = { + 'Integer(val=1):0:1:1', + }, + }, + }, + err = { + arg = '[1', + msg = 'E697: Missing end of List \']\': %.*s', + }, + }, { + hl('List', '['), + hl('Number', '1'), + }) + end) + it('works with strings', function() + check_parsing('\'abc\'', 0, { + -- 01234 + ast = { + 'SingleQuotedString(val="abc"):0:0:\'abc\'', + }, + }, { + hl('SingleQuote', '\''), + hl('SingleQuotedBody', 'abc'), + hl('SingleQuote', '\''), + }) + check_parsing('"abc"', 0, { + -- 01234 + ast = { + 'DoubleQuotedString(val="abc"):0:0:"abc"', + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedBody', 'abc'), + hl('DoubleQuote', '"'), + }) + check_parsing('\'\'', 0, { + -- 01 + ast = { + 'SingleQuotedString(val=""):0:0:\'\'', + }, + }, { + hl('SingleQuote', '\''), + hl('SingleQuote', '\''), + }) + check_parsing('""', 0, { + -- 01 + ast = { + 'DoubleQuotedString(val=""):0:0:""', + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuote', '"'), + }) + check_parsing('"', 0, { + -- 0 + ast = { + 'DoubleQuotedString(val=""):0:0:"', + }, + err = { + arg = '"', + msg = 'E114: Missing double quote: %.*s', + }, + }, { + hl('InvalidDoubleQuote', '"'), + }) + check_parsing('\'', 0, { + -- 0 + ast = { + 'SingleQuotedString(val=""):0:0:\'', + }, + err = { + arg = '\'', + msg = 'E115: Missing single quote: %.*s', + }, + }, { + hl('InvalidSingleQuote', '\''), + }) + check_parsing('"a', 0, { + -- 01 + ast = { + 'DoubleQuotedString(val="a"):0:0:"a', + }, + err = { + arg = '"a', + msg = 'E114: Missing double quote: %.*s', + }, + }, { + hl('InvalidDoubleQuote', '"'), + hl('InvalidDoubleQuotedBody', 'a'), + }) + check_parsing('\'a', 0, { + -- 01 + ast = { + 'SingleQuotedString(val="a"):0:0:\'a', + }, + err = { + arg = '\'a', + msg = 'E115: Missing single quote: %.*s', + }, + }, { + hl('InvalidSingleQuote', '\''), + hl('InvalidSingleQuotedBody', 'a'), + }) + check_parsing('\'abc\'\'def\'', 0, { + -- 0123456789 + ast = { + 'SingleQuotedString(val="abc\'def"):0:0:\'abc\'\'def\'', + }, + }, { + hl('SingleQuote', '\''), + hl('SingleQuotedBody', 'abc'), + hl('SingleQuotedQuote', '\'\''), + hl('SingleQuotedBody', 'def'), + hl('SingleQuote', '\''), + }) + check_parsing('\'abc\'\'', 0, { + -- 012345 + ast = { + 'SingleQuotedString(val="abc\'"):0:0:\'abc\'\'', + }, + err = { + arg = '\'abc\'\'', + msg = 'E115: Missing single quote: %.*s', + }, + }, { + hl('InvalidSingleQuote', '\''), + hl('InvalidSingleQuotedBody', 'abc'), + hl('InvalidSingleQuotedQuote', '\'\''), + }) + check_parsing('\'\'\'\'\'\'\'\'', 0, { + -- 01234567 + ast = { + 'SingleQuotedString(val="\'\'\'"):0:0:\'\'\'\'\'\'\'\'', + }, + }, { + hl('SingleQuote', '\''), + hl('SingleQuotedQuote', '\'\''), + hl('SingleQuotedQuote', '\'\''), + hl('SingleQuotedQuote', '\'\''), + hl('SingleQuote', '\''), + }) + check_parsing('\'\'\'a\'\'\'\'bc\'', 0, { + -- 01234567890 + -- 0 1 + ast = { + 'SingleQuotedString(val="\'a\'\'bc"):0:0:\'\'\'a\'\'\'\'bc\'', + }, + }, { + hl('SingleQuote', '\''), + hl('SingleQuotedQuote', '\'\''), + hl('SingleQuotedBody', 'a'), + hl('SingleQuotedQuote', '\'\''), + hl('SingleQuotedQuote', '\'\''), + hl('SingleQuotedBody', 'bc'), + hl('SingleQuote', '\''), + }) + check_parsing('"\\"\\"\\"\\""', 0, { + -- 0123456789 + ast = { + 'DoubleQuotedString(val="\\"\\"\\"\\""):0:0:"\\"\\"\\"\\""', + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\"'), + hl('DoubleQuotedEscape', '\\"'), + hl('DoubleQuotedEscape', '\\"'), + hl('DoubleQuotedEscape', '\\"'), + hl('DoubleQuote', '"'), + }) + check_parsing('"abc\\"def\\"ghi\\"jkl\\"mno"', 0, { + -- 0123456789012345678901234 + -- 0 1 2 + ast = { + 'DoubleQuotedString(val="abc\\"def\\"ghi\\"jkl\\"mno"):0:0:"abc\\"def\\"ghi\\"jkl\\"mno"', + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedBody', 'abc'), + hl('DoubleQuotedEscape', '\\"'), + hl('DoubleQuotedBody', 'def'), + hl('DoubleQuotedEscape', '\\"'), + hl('DoubleQuotedBody', 'ghi'), + hl('DoubleQuotedEscape', '\\"'), + hl('DoubleQuotedBody', 'jkl'), + hl('DoubleQuotedEscape', '\\"'), + hl('DoubleQuotedBody', 'mno'), + hl('DoubleQuote', '"'), + }) + check_parsing('"\\b\\e\\f\\r\\t\\\\"', 0, { + -- 0123456789012345 + -- 0 1 + ast = { + [[DoubleQuotedString(val="\8\27\12\13\9\\"):0:0:"\b\e\f\r\t\\"]], + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\b'), + hl('DoubleQuotedEscape', '\\e'), + hl('DoubleQuotedEscape', '\\f'), + hl('DoubleQuotedEscape', '\\r'), + hl('DoubleQuotedEscape', '\\t'), + hl('DoubleQuotedEscape', '\\\\'), + hl('DoubleQuote', '"'), + }) + check_parsing('"\\n\n"', 0, { + -- 01234 + ast = { + 'DoubleQuotedString(val="\\\n\\\n"):0:0:"\\n\n"', + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\n'), + hl('DoubleQuotedBody', '\n'), + hl('DoubleQuote', '"'), + }) + check_parsing('"\\x00"', 0, { + -- 012345 + ast = { + 'DoubleQuotedString(val="\\0"):0:0:"\\x00"', + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\x00'), + hl('DoubleQuote', '"'), + }) + check_parsing('"\\xFF"', 0, { + -- 012345 + ast = { + 'DoubleQuotedString(val="\255"):0:0:"\\xFF"', + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\xFF'), + hl('DoubleQuote', '"'), + }) + check_parsing('"\\xF"', 0, { + -- 012345 + ast = { + 'DoubleQuotedString(val="\\15"):0:0:"\\xF"', + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\xF'), + hl('DoubleQuote', '"'), + }) + check_parsing('"\\u00AB"', 0, { + -- 01234567 + ast = { + 'DoubleQuotedString(val="«"):0:0:"\\u00AB"', + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\u00AB'), + hl('DoubleQuote', '"'), + }) + check_parsing('"\\U000000AB"', 0, { + -- 01234567 + ast = { + 'DoubleQuotedString(val="«"):0:0:"\\U000000AB"', + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\U000000AB'), + hl('DoubleQuote', '"'), + }) + check_parsing('"\\x"', 0, { + -- 0123 + ast = { + 'DoubleQuotedString(val="x"):0:0:"\\x"', + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedUnknownEscape', '\\x'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\x', 0, { + -- 012 + ast = { + 'DoubleQuotedString(val="x"):0:0:"\\x', + }, + err = { + arg = '"\\x', + msg = 'E114: Missing double quote: %.*s', + }, + }, { + hl('InvalidDoubleQuote', '"'), + hl('InvalidDoubleQuotedUnknownEscape', '\\x'), + }) + + check_parsing('"\\xF', 0, { + -- 0123 + ast = { + 'DoubleQuotedString(val="\\15"):0:0:"\\xF', + }, + err = { + arg = '"\\xF', + msg = 'E114: Missing double quote: %.*s', + }, + }, { + hl('InvalidDoubleQuote', '"'), + hl('InvalidDoubleQuotedEscape', '\\xF'), + }) + + check_parsing('"\\u"', 0, { + -- 0123 + ast = { + 'DoubleQuotedString(val="u"):0:0:"\\u"', + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedUnknownEscape', '\\u'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\u', 0, { + -- 012 + ast = { + 'DoubleQuotedString(val="u"):0:0:"\\u', + }, + err = { + arg = '"\\u', + msg = 'E114: Missing double quote: %.*s', + }, + }, { + hl('InvalidDoubleQuote', '"'), + hl('InvalidDoubleQuotedUnknownEscape', '\\u'), + }) + + check_parsing('"\\U', 0, { + -- 012 + ast = { + 'DoubleQuotedString(val="U"):0:0:"\\U', + }, + err = { + arg = '"\\U', + msg = 'E114: Missing double quote: %.*s', + }, + }, { + hl('InvalidDoubleQuote', '"'), + hl('InvalidDoubleQuotedUnknownEscape', '\\U'), + }) + + check_parsing('"\\U"', 0, { + -- 0123 + ast = { + 'DoubleQuotedString(val="U"):0:0:"\\U"', + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedUnknownEscape', '\\U'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\xFX"', 0, { + -- 012345 + ast = { + 'DoubleQuotedString(val="\\15X"):0:0:"\\xFX"', + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\xF'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\XFX"', 0, { + -- 012345 + ast = { + 'DoubleQuotedString(val="\\15X"):0:0:"\\XFX"', + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\XF'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\xX"', 0, { + -- 01234 + ast = { + 'DoubleQuotedString(val="xX"):0:0:"\\xX"', + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedUnknownEscape', '\\x'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\XX"', 0, { + -- 01234 + ast = { + 'DoubleQuotedString(val="XX"):0:0:"\\XX"', + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedUnknownEscape', '\\X'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\uX"', 0, { + -- 01234 + ast = { + 'DoubleQuotedString(val="uX"):0:0:"\\uX"', + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedUnknownEscape', '\\u'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\UX"', 0, { + -- 01234 + ast = { + 'DoubleQuotedString(val="UX"):0:0:"\\UX"', + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedUnknownEscape', '\\U'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\x0X"', 0, { + -- 012345 + ast = { + 'DoubleQuotedString(val="\\0X"):0:0:"\\x0X"', + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\x0'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\X0X"', 0, { + -- 012345 + ast = { + 'DoubleQuotedString(val="\\0X"):0:0:"\\X0X"', + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\X0'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\u0X"', 0, { + -- 012345 + ast = { + 'DoubleQuotedString(val="\\0X"):0:0:"\\u0X"', + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\u0'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\U0X"', 0, { + -- 012345 + ast = { + 'DoubleQuotedString(val="\\0X"):0:0:"\\U0X"', + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\U0'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\x00X"', 0, { + -- 0123456 + ast = { + 'DoubleQuotedString(val="\\0X"):0:0:"\\x00X"', + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\x00'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\X00X"', 0, { + -- 0123456 + ast = { + 'DoubleQuotedString(val="\\0X"):0:0:"\\X00X"', + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\X00'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\u00X"', 0, { + -- 0123456 + ast = { + 'DoubleQuotedString(val="\\0X"):0:0:"\\u00X"', + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\u00'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\U00X"', 0, { + -- 0123456 + ast = { + 'DoubleQuotedString(val="\\0X"):0:0:"\\U00X"', + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\U00'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\u000X"', 0, { + -- 01234567 + ast = { + 'DoubleQuotedString(val="\\0X"):0:0:"\\u000X"', + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\u000'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\U000X"', 0, { + -- 01234567 + ast = { + 'DoubleQuotedString(val="\\0X"):0:0:"\\U000X"', + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\U000'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\u0000X"', 0, { + -- 012345678 + ast = { + 'DoubleQuotedString(val="\\0X"):0:0:"\\u0000X"', + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\u0000'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\U0000X"', 0, { + -- 012345678 + ast = { + 'DoubleQuotedString(val="\\0X"):0:0:"\\U0000X"', + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\U0000'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\U00000X"', 0, { + -- 0123456789 + ast = { + 'DoubleQuotedString(val="\\0X"):0:0:"\\U00000X"', + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\U00000'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\U000000X"', 0, { + -- 01234567890 + -- 0 1 + ast = { + 'DoubleQuotedString(val="\\0X"):0:0:"\\U000000X"', + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\U000000'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\U0000000X"', 0, { + -- 012345678901 + -- 0 1 + ast = { + 'DoubleQuotedString(val="\\0X"):0:0:"\\U0000000X"', + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\U0000000'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\U00000000X"', 0, { + -- 0123456789012 + -- 0 1 + ast = { + 'DoubleQuotedString(val="\\0X"):0:0:"\\U00000000X"', + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\U00000000'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\x000X"', 0, { + -- 01234567 + ast = { + 'DoubleQuotedString(val="\\0000X"):0:0:"\\x000X"', + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\x00'), + hl('DoubleQuotedBody', '0X'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\X000X"', 0, { + -- 01234567 + ast = { + 'DoubleQuotedString(val="\\0000X"):0:0:"\\X000X"', + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\X00'), + hl('DoubleQuotedBody', '0X'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\u00000X"', 0, { + -- 0123456789 + ast = { + 'DoubleQuotedString(val="\\0000X"):0:0:"\\u00000X"', + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\u0000'), + hl('DoubleQuotedBody', '0X'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\U000000000X"', 0, { + -- 01234567890123 + -- 0 1 + ast = { + 'DoubleQuotedString(val="\\0000X"):0:0:"\\U000000000X"', + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\U00000000'), + hl('DoubleQuotedBody', '0X'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\0"', 0, { + -- 0123 + ast = { + 'DoubleQuotedString(val="\\0"):0:0:"\\0"', + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\0'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\00"', 0, { + -- 01234 + ast = { + 'DoubleQuotedString(val="\\0"):0:0:"\\00"', + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\00'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\000"', 0, { + -- 012345 + ast = { + 'DoubleQuotedString(val="\\0"):0:0:"\\000"', + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\000'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\0000"', 0, { + -- 0123456 + ast = { + 'DoubleQuotedString(val="\\0000"):0:0:"\\0000"', + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\000'), + hl('DoubleQuotedBody', '0'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\8"', 0, { + -- 0123 + ast = { + 'DoubleQuotedString(val="8"):0:0:"\\8"', + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedUnknownEscape', '\\8'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\08"', 0, { + -- 01234 + ast = { + 'DoubleQuotedString(val="\\0008"):0:0:"\\08"', + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\0'), + hl('DoubleQuotedBody', '8'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\008"', 0, { + -- 012345 + ast = { + 'DoubleQuotedString(val="\\0008"):0:0:"\\008"', + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\00'), + hl('DoubleQuotedBody', '8'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\0008"', 0, { + -- 0123456 + ast = { + 'DoubleQuotedString(val="\\0008"):0:0:"\\0008"', + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\000'), + hl('DoubleQuotedBody', '8'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\777"', 0, { + -- 012345 + ast = { + 'DoubleQuotedString(val="\255"):0:0:"\\777"', + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\777'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\050"', 0, { + -- 012345 + ast = { + 'DoubleQuotedString(val="\40"):0:0:"\\050"', + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\050'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\"', 0, { + -- 012345 + ast = { + 'DoubleQuotedString(val="\\21"):0:0:"\\"', + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\<', 0, { + -- 012 + ast = { + 'DoubleQuotedString(val="<"):0:0:"\\<', + }, + err = { + arg = '"\\<', + msg = 'E114: Missing double quote: %.*s', + }, + }, { + hl('InvalidDoubleQuote', '"'), + hl('InvalidDoubleQuotedUnknownEscape', '\\<'), + }) + + check_parsing('"\\<"', 0, { + -- 0123 + ast = { + 'DoubleQuotedString(val="<"):0:0:"\\<"', + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedUnknownEscape', '\\<'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\ Date: Sun, 5 Nov 2017 21:06:12 +0300 Subject: tests: Add missing test cases --- src/nvim/api/vim.c | 6 + test/functional/api/vim_spec.lua | 1197 ++++++++++++++++++---------- test/helpers.lua | 42 + test/unit/viml/expressions/parser_spec.lua | 1105 ++++++++++++++----------- 4 files changed, 1454 insertions(+), 896 deletions(-) diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 5f725acaf7..b12c595cb5 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -971,6 +971,11 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, switch (flags.data[i]) { case 'm': { pflags |= kExprFlagsMulti; break; } case 'E': { pflags |= kExprFlagsDisallowEOC; break; } + case NUL: { + api_set_error(err, kErrorTypeValidation, "Invalid flag: '\\0' (%u)", + (unsigned)flags.data[i]); + return (Dictionary)ARRAY_DICT_INIT; + } default: { api_set_error(err, kErrorTypeValidation, "Invalid flag: '%c' (%u)", flags.data[i], (unsigned)flags.data[i]); @@ -995,6 +1000,7 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, &pstate, parser_simple_get_line, &plines_p, colors_p); ExprAST east = viml_pexpr_parse(&pstate, pflags); + // FIXME add parse_length key const size_t ret_size = ( 1 // "ast" + (size_t)(east.err.msg != NULL) // "error" diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index bb7785657d..b904bd2a8f 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -12,8 +12,10 @@ local request = helpers.request local meth_pcall = helpers.meth_pcall local command = helpers.command +local REMOVE_THIS = global_helpers.REMOVE_THIS local intchar2lua = global_helpers.intchar2lua local format_string = global_helpers.format_string +local mergedicts_copy = global_helpers.mergedicts_copy describe('api', function() before_each(clear) @@ -717,6 +719,9 @@ describe('api', function() describe('nvim_parse_expression', function() local function simplify_east_api_node(line, east_api_node) + if east_api_node == NIL then + return nil + end if east_api_node.children then for k, v in pairs(east_api_node.children) do east_api_node.children[k] = simplify_east_api_node(line, v) @@ -806,31 +811,53 @@ describe('api', function() end return east_hl end - local function check_parsing(str, flags, exp_ast, exp_highlighting_fs) - if flags == 0 then - flags = "" - end - - local err, msg = pcall(function() - local east_api = meths.parse_expression(str, flags, true) - local east_hl = east_api.highlight - east_api.highlight = nil - local ast = simplify_east_api(str, east_api) - local hls = simplify_east_hl(str, east_hl) - eq(exp_ast, ast) - if exp_highlighting_fs then - local exp_highlighting = {} - local next_col = 0 - for i, h in ipairs(exp_highlighting_fs) do - exp_highlighting[i], next_col = h(next_col) + local FLAGS_TO_STR = { + [0] = "", + [1] = "m", + [2] = "E", + [3] = "mE", + } + local function check_parsing(str, exp_ast, exp_highlighting_fs, + nz_flags_exps) + nz_flags_exps = nz_flags_exps or {} + for _, flags in ipairs({0, 1, 2, 3}) do + local err, msg = pcall(function() + local east_api = meths.parse_expression(str, FLAGS_TO_STR[flags], true) + local east_hl = east_api.highlight + east_api.highlight = nil + local ast = simplify_east_api(str, east_api) + local hls = simplify_east_hl(str, east_hl) + local exps = { + ast = exp_ast, + hl_fs = exp_highlighting_fs, + } + local add_exps = nz_flags_exps[flags] + if not add_exps and flags == 3 then + add_exps = nz_flags_exps[1] or nz_flags_exps[2] end - eq(exp_highlighting, hls) + if add_exps then + if add_exps.ast then + exps.ast = mergedicts_copy(exps.ast, add_exps.ast) + end + if add_exps.hl_fs then + exps.hl_fs = mergedicts_copy(exps.hl_fs, add_exps.hl_fs) + end + end + eq(exps.ast, ast) + if exp_highlighting_fs then + local exp_highlighting = {} + local next_col = 0 + for i, h in ipairs(exps.hl_fs) do + exp_highlighting[i], next_col = h(next_col) + end + eq(exp_highlighting, hls) + end + end) + if not err then + msg = format_string('Error while processing test (%r, %s):\n%s', + str, FLAGS_TO_STR[flags], msg) + error(msg) end - end) - if not err then - msg = format_string('Error while processing test (%r, %s):\n%s', - str, flags, msg) - error(msg) end end local function hl(group, str, shift) @@ -844,14 +871,14 @@ describe('api', function() end end it('works with + and @a', function() - check_parsing('@a', 0, { + check_parsing('@a', { ast = { 'Register(name=a):0:0:@a', }, }, { hl('Register', '@a'), }) - check_parsing('+@a', 0, { + check_parsing('+@a', { ast = { { 'UnaryPlus:0:0:+', @@ -864,7 +891,7 @@ describe('api', function() hl('UnaryPlus', '+'), hl('Register', '@a'), }) - check_parsing('@a+@b', 0, { + check_parsing('@a+@b', { ast = { { 'BinaryPlus:0:2:+', @@ -879,7 +906,7 @@ describe('api', function() hl('BinaryPlus', '+'), hl('Register', '@b'), }) - check_parsing('@a+@b+@c', 0, { + check_parsing('@a+@b+@c', { ast = { { 'BinaryPlus:0:5:+', @@ -902,7 +929,7 @@ describe('api', function() hl('BinaryPlus', '+'), hl('Register', '@c'), }) - check_parsing('+@a+@b', 0, { + check_parsing('+@a+@b', { ast = { { 'BinaryPlus:0:3:+', @@ -923,7 +950,7 @@ describe('api', function() hl('BinaryPlus', '+'), hl('Register', '@b'), }) - check_parsing('+@a++@b', 0, { + check_parsing('+@a++@b', { ast = { { 'BinaryPlus:0:3:+', @@ -950,7 +977,7 @@ describe('api', function() hl('UnaryPlus', '+'), hl('Register', '@b'), }) - check_parsing('@a@b', 0, { + check_parsing('@a@b', { ast = { { 'OpMissing:0:2:', @@ -967,8 +994,20 @@ describe('api', function() }, { hl('Register', '@a'), hl('InvalidRegister', '@b'), + }, { + [1] = { + ast = { + err = REMOVE_THIS, + ast = { + 'Register(name=a):0:0:@a' + }, + }, + hl_fs = { + [2] = REMOVE_THIS, + }, + }, }) - check_parsing(' @a \t @b', 0, { + check_parsing(' @a \t @b', { ast = { { 'OpMissing:0:3:', @@ -986,8 +1025,21 @@ describe('api', function() hl('Register', '@a', 1), hl('InvalidSpacing', ' \t '), hl('Register', '@b'), + }, { + [1] = { + ast = { + err = REMOVE_THIS, + ast = { + 'Register(name=a):0:0: @a' + }, + }, + hl_fs = { + [2] = REMOVE_THIS, + [3] = REMOVE_THIS, + }, + }, }) - check_parsing('+', 0, { + check_parsing('+', { ast = { 'UnaryPlus:0:0:+', }, @@ -998,7 +1050,7 @@ describe('api', function() }, { hl('UnaryPlus', '+'), }) - check_parsing(' +', 0, { + check_parsing(' +', { ast = { 'UnaryPlus:0:0: +', }, @@ -1009,7 +1061,7 @@ describe('api', function() }, { hl('UnaryPlus', '+', 1), }) - check_parsing('@a+ ', 0, { + check_parsing('@a+ ', { ast = { { 'BinaryPlus:0:2:+', @@ -1028,7 +1080,7 @@ describe('api', function() }) end) it('works with @a, + and parenthesis', function() - check_parsing('(@a)', 0, { + check_parsing('(@a)', { ast = { { 'Nested:0:0:(', @@ -1042,7 +1094,7 @@ describe('api', function() hl('Register', '@a'), hl('NestingParenthesis', ')'), }) - check_parsing('()', 0, { + check_parsing('()', { ast = { { 'Nested:0:0:(', @@ -1059,7 +1111,7 @@ describe('api', function() hl('NestingParenthesis', '('), hl('InvalidNestingParenthesis', ')'), }) - check_parsing(')', 0, { + check_parsing(')', { ast = { { 'Nested:0:0:', @@ -1075,7 +1127,7 @@ describe('api', function() }, { hl('InvalidNestingParenthesis', ')'), }) - check_parsing('+)', 0, { + check_parsing('+)', { ast = { { 'Nested:0:1:', @@ -1097,7 +1149,7 @@ describe('api', function() hl('UnaryPlus', '+'), hl('InvalidNestingParenthesis', ')'), }) - check_parsing('+@a(@b)', 0, { + check_parsing('+@a(@b)', { ast = { { 'UnaryPlus:0:0:+', @@ -1119,7 +1171,7 @@ describe('api', function() hl('Register', '@b'), hl('CallingParenthesis', ')'), }) - check_parsing('@a+@b(@c)', 0, { + check_parsing('@a+@b(@c)', { ast = { { 'BinaryPlus:0:2:+', @@ -1143,7 +1195,7 @@ describe('api', function() hl('Register', '@c'), hl('CallingParenthesis', ')'), }) - check_parsing('@a()', 0, { + check_parsing('@a()', { ast = { { 'Call:0:2:(', @@ -1157,7 +1209,7 @@ describe('api', function() hl('CallingParenthesis', '('), hl('CallingParenthesis', ')'), }) - check_parsing('@a ()', 0, { + check_parsing('@a ()', { ast = { { 'OpMissing:0:2:', @@ -1181,91 +1233,102 @@ describe('api', function() hl('InvalidSpacing', ' '), hl('NestingParenthesis', '('), hl('InvalidNestingParenthesis', ')'), - }) - check_parsing( - '@a + (@b)', 0, { + }, { + [1] = { ast = { - { - 'BinaryPlus:0:2: +', - children = { - 'Register(name=a):0:0:@a', - { - 'Nested:0:4: (', - children = { - 'Register(name=b):0:6:@b', - }, + err = REMOVE_THIS, + ast = { + 'Register(name=a):0:0:@a', + }, + }, + hl_fs = { + [2] = REMOVE_THIS, + [3] = REMOVE_THIS, + [4] = REMOVE_THIS, + }, + }, + }) + check_parsing('@a + (@b)', { + ast = { + { + 'BinaryPlus:0:2: +', + children = { + 'Register(name=a):0:0:@a', + { + 'Nested:0:4: (', + children = { + 'Register(name=b):0:6:@b', }, }, }, }, - }, { - hl('Register', '@a'), - hl('BinaryPlus', '+', 1), - hl('NestingParenthesis', '(', 1), - hl('Register', '@b'), - hl('NestingParenthesis', ')'), - }) - check_parsing( - '@a + (+@b)', 0, { - ast = { - { - 'BinaryPlus:0:2: +', - children = { - 'Register(name=a):0:0:@a', - { - 'Nested:0:4: (', - children = { - { - 'UnaryPlus:0:6:+', - children = { - 'Register(name=b):0:7:@b', - }, + }, + }, { + hl('Register', '@a'), + hl('BinaryPlus', '+', 1), + hl('NestingParenthesis', '(', 1), + hl('Register', '@b'), + hl('NestingParenthesis', ')'), + }) + check_parsing('@a + (+@b)', { + ast = { + { + 'BinaryPlus:0:2: +', + children = { + 'Register(name=a):0:0:@a', + { + 'Nested:0:4: (', + children = { + { + 'UnaryPlus:0:6:+', + children = { + 'Register(name=b):0:7:@b', }, }, }, }, }, }, - }, { - hl('Register', '@a'), - hl('BinaryPlus', '+', 1), - hl('NestingParenthesis', '(', 1), - hl('UnaryPlus', '+'), - hl('Register', '@b'), - hl('NestingParenthesis', ')'), - }) - check_parsing( - '@a + (@b + @c)', 0, { - ast = { - { - 'BinaryPlus:0:2: +', - children = { - 'Register(name=a):0:0:@a', - { - 'Nested:0:4: (', - children = { - { - 'BinaryPlus:0:8: +', - children = { - 'Register(name=b):0:6:@b', - 'Register(name=c):0:10: @c', - }, + }, + }, { + hl('Register', '@a'), + hl('BinaryPlus', '+', 1), + hl('NestingParenthesis', '(', 1), + hl('UnaryPlus', '+'), + hl('Register', '@b'), + hl('NestingParenthesis', ')'), + }) + check_parsing('@a + (@b + @c)', { + ast = { + { + 'BinaryPlus:0:2: +', + children = { + 'Register(name=a):0:0:@a', + { + 'Nested:0:4: (', + children = { + { + 'BinaryPlus:0:8: +', + children = { + 'Register(name=b):0:6:@b', + 'Register(name=c):0:10: @c', }, }, }, }, }, }, - }, { - hl('Register', '@a'), - hl('BinaryPlus', '+', 1), - hl('NestingParenthesis', '(', 1), - hl('Register', '@b'), - hl('BinaryPlus', '+', 1), - hl('Register', '@c', 1), - hl('NestingParenthesis', ')'), - }) - check_parsing('(@a)+@b', 0, { + }, + }, { + hl('Register', '@a'), + hl('BinaryPlus', '+', 1), + hl('NestingParenthesis', '(', 1), + hl('Register', '@b'), + hl('BinaryPlus', '+', 1), + hl('Register', '@c', 1), + hl('NestingParenthesis', ')'), + }) + check_parsing('(@a)+@b', { ast = { { 'BinaryPlus:0:4:+', @@ -1287,7 +1350,7 @@ describe('api', function() hl('BinaryPlus', '+'), hl('Register', '@b'), }) - check_parsing('@a+(@b)(@c)', 0, { + check_parsing('@a+(@b)(@c)', { -- 01234567890 ast = { { @@ -1317,7 +1380,7 @@ describe('api', function() hl('Register', '@c'), hl('CallingParenthesis', ')'), }) - check_parsing('@a+((@b))(@c)', 0, { + check_parsing('@a+((@b))(@c)', { -- 01234567890123456890123456789 -- 0 1 2 ast = { @@ -1355,7 +1418,7 @@ describe('api', function() hl('Register', '@c'), hl('CallingParenthesis', ')'), }) - check_parsing('@a+((@b))+@c', 0, { + check_parsing('@a+((@b))+@c', { -- 01234567890123456890123456789 -- 0 1 2 ast = { @@ -1393,7 +1456,7 @@ describe('api', function() hl('Register', '@c'), }) check_parsing( - '@a + (@b + @c) + @d(@e) + (+@f) + ((+@g(@h))(@j)(@k))(@l)', 0, {--[[ + '@a + (@b + @c) + @d(@e) + (+@f) + ((+@g(@h))(@j)(@k))(@l)', {--[[ | | | | | | | | || | | || | | ||| || || || || 000000000011111111112222222222333333333344444444445555555 012345678901234567890123456789012345678901234567890123456 @@ -1527,7 +1590,7 @@ describe('api', function() hl('Register', '@l'), hl('CallingParenthesis', ')'), }) - check_parsing('@a)', 0, { + check_parsing('@a)', { -- 012 ast = { { @@ -1545,7 +1608,7 @@ describe('api', function() hl('Register', '@a'), hl('InvalidNestingParenthesis', ')'), }) - check_parsing('(@a', 0, { + check_parsing('(@a', { -- 012 ast = { { @@ -1563,7 +1626,7 @@ describe('api', function() hl('NestingParenthesis', '('), hl('Register', '@a'), }) - check_parsing('@a(@b', 0, { + check_parsing('@a(@b', { -- 01234 ast = { { @@ -1583,7 +1646,7 @@ describe('api', function() hl('CallingParenthesis', '('), hl('Register', '@b'), }) - check_parsing('@a(@b, @c, @d, @e)', 0, { + check_parsing('@a(@b, @c, @d, @e)', { -- 012345678901234567 -- 0 1 ast = { @@ -1625,7 +1688,7 @@ describe('api', function() hl('Register', '@e', 1), hl('CallingParenthesis', ')'), }) - check_parsing('@a(@b(@c))', 0, { + check_parsing('@a(@b(@c))', { -- 01234567890123456789012345678901234567 -- 0 1 2 3 ast = { @@ -1652,7 +1715,7 @@ describe('api', function() hl('CallingParenthesis', ')'), hl('CallingParenthesis', ')'), }) - check_parsing('@a(@b(@c(@d(@e), @f(@g(@h), @i(@j)))))', 0, { + check_parsing('@a(@b(@c(@d(@e), @f(@g(@h), @i(@j)))))', { -- 01234567890123456789012345678901234567 -- 0 1 2 3 ast = { @@ -1742,14 +1805,14 @@ describe('api', function() }) end) it('works with variable names, including curly braces ones', function() - check_parsing('var', 0, { + check_parsing('var', { ast = { 'PlainIdentifier(scope=0,ident=var):0:0:var', }, }, { hl('IdentifierName', 'var'), }) - check_parsing('g:var', 0, { + check_parsing('g:var', { ast = { 'PlainIdentifier(scope=g,ident=var):0:0:g:var', }, @@ -1758,7 +1821,7 @@ describe('api', function() hl('IdentifierScopeDelimiter', ':'), hl('IdentifierName', 'var'), }) - check_parsing('g:', 0, { + check_parsing('g:', { ast = { 'PlainIdentifier(scope=g,ident=):0:0:g:', }, @@ -1766,7 +1829,7 @@ describe('api', function() hl('IdentifierScope', 'g'), hl('IdentifierScopeDelimiter', ':'), }) - check_parsing('{a}', 0, { + check_parsing('{a}', { -- 012 ast = { { @@ -1781,7 +1844,7 @@ describe('api', function() hl('IdentifierName', 'a'), hl('Curly', '}'), }) - check_parsing('{a:b}', 0, { + check_parsing('{a:b}', { -- 012 ast = { { @@ -1798,7 +1861,7 @@ describe('api', function() hl('IdentifierName', 'b'), hl('Curly', '}'), }) - check_parsing('{a:@b}', 0, { + check_parsing('{a:@b}', { -- 012345 ast = { { @@ -1825,7 +1888,7 @@ describe('api', function() hl('InvalidRegister', '@b'), hl('Curly', '}'), }) - check_parsing('{@a}', 0, { + check_parsing('{@a}', { ast = { { 'CurlyBracesIdentifier:0:0:{', @@ -1839,7 +1902,7 @@ describe('api', function() hl('Register', '@a'), hl('Curly', '}'), }) - check_parsing('{@a}{@b}', 0, { + check_parsing('{@a}{@b}', { -- 01234567 ast = { { @@ -1868,7 +1931,7 @@ describe('api', function() hl('Register', '@b'), hl('Curly', '}'), }) - check_parsing('g:{@a}', 0, { + check_parsing('g:{@a}', { -- 01234567 ast = { { @@ -1891,7 +1954,7 @@ describe('api', function() hl('Register', '@a'), hl('Curly', '}'), }) - check_parsing('{@a}_test', 0, { + check_parsing('{@a}_test', { -- 012345678 ast = { { @@ -1913,7 +1976,7 @@ describe('api', function() hl('Curly', '}'), hl('IdentifierName', '_test'), }) - check_parsing('g:{@a}_test', 0, { + check_parsing('g:{@a}_test', { -- 01234567890 ast = { { @@ -1943,7 +2006,7 @@ describe('api', function() hl('Curly', '}'), hl('IdentifierName', '_test'), }) - check_parsing('g:{@a}_test()', 0, { + check_parsing('g:{@a}_test()', { -- 0123456789012 ast = { { @@ -1980,7 +2043,7 @@ describe('api', function() hl('CallingParenthesis', '('), hl('CallingParenthesis', ')'), }) - check_parsing('{@a} ()', 0, { + check_parsing('{@a} ()', { -- 0123456789012 ast = { { @@ -2002,7 +2065,7 @@ describe('api', function() hl('CallingParenthesis', '(', 1), hl('CallingParenthesis', ')'), }) - check_parsing('g:{@a} ()', 0, { + check_parsing('g:{@a} ()', { -- 0123456789012 ast = { { @@ -2032,7 +2095,7 @@ describe('api', function() hl('CallingParenthesis', '(', 1), hl('CallingParenthesis', ')'), }) - check_parsing('{@a', 0, { + check_parsing('{@a', { -- 012 ast = { { @@ -2052,7 +2115,7 @@ describe('api', function() }) end) it('works with lambdas and dictionaries', function() - check_parsing('{}', 0, { + check_parsing('{}', { ast = { 'DictLiteral:0:0:{', }, @@ -2060,7 +2123,7 @@ describe('api', function() hl('Dict', '{'), hl('Dict', '}'), }) - check_parsing('{->@a}', 0, { + check_parsing('{->@a}', { ast = { { 'Lambda:0:0:{', @@ -2080,7 +2143,7 @@ describe('api', function() hl('Register', '@a'), hl('Lambda', '}'), }) - check_parsing('{->@a+@b}', 0, { + check_parsing('{->@a+@b}', { -- 012345678 ast = { { @@ -2109,7 +2172,7 @@ describe('api', function() hl('Register', '@b'), hl('Lambda', '}'), }) - check_parsing('{a->@a}', 0, { + check_parsing('{a->@a}', { -- 012345678 ast = { { @@ -2132,7 +2195,7 @@ describe('api', function() hl('Register', '@a'), hl('Lambda', '}'), }) - check_parsing('{a,b->@a}', 0, { + check_parsing('{a,b->@a}', { -- 012345678 ast = { { @@ -2163,7 +2226,7 @@ describe('api', function() hl('Register', '@a'), hl('Lambda', '}'), }) - check_parsing('{a,b,c->@a}', 0, { + check_parsing('{a,b,c->@a}', { -- 01234567890 ast = { { @@ -2202,7 +2265,7 @@ describe('api', function() hl('Register', '@a'), hl('Lambda', '}'), }) - check_parsing('{a,b,c,d->@a}', 0, { + check_parsing('{a,b,c,d->@a}', { -- 0123456789012 ast = { { @@ -2249,7 +2312,7 @@ describe('api', function() hl('Register', '@a'), hl('Lambda', '}'), }) - check_parsing('{a,b,c,d,->@a}', 0, { + check_parsing('{a,b,c,d,->@a}', { -- 01234567890123 ast = { { @@ -2302,7 +2365,7 @@ describe('api', function() hl('Register', '@a'), hl('Lambda', '}'), }) - check_parsing('{a,b->{c,d->{e,f->@a}}}', 0, { + check_parsing('{a,b->{c,d->{e,f->@a}}}', { -- 01234567890123456789012 -- 0 1 2 ast = { @@ -2380,7 +2443,7 @@ describe('api', function() hl('Lambda', '}'), hl('Lambda', '}'), }) - check_parsing('{a,b->c,d}', 0, { + check_parsing('{a,b->c,d}', { -- 0123456789 ast = { { @@ -2423,7 +2486,7 @@ describe('api', function() hl('IdentifierName', 'd'), hl('Lambda', '}'), }) - check_parsing('a,b,c,d', 0, { + check_parsing('a,b,c,d', { -- 0123456789 ast = { { @@ -2459,7 +2522,7 @@ describe('api', function() hl('InvalidComma', ','), hl('IdentifierName', 'd'), }) - check_parsing('a,b,c,d,', 0, { + check_parsing('a,b,c,d,', { -- 0123456789 ast = { { @@ -2501,7 +2564,7 @@ describe('api', function() hl('IdentifierName', 'd'), hl('InvalidComma', ','), }) - check_parsing(',', 0, { + check_parsing(',', { -- 0123456789 ast = { { @@ -2518,7 +2581,7 @@ describe('api', function() }, { hl('InvalidComma', ','), }) - check_parsing('{,a->@a}', 0, { + check_parsing('{,a->@a}', { -- 0123456789 ast = { { @@ -2552,7 +2615,7 @@ describe('api', function() hl('Register', '@a'), hl('Curly', '}'), }) - check_parsing('}', 0, { + check_parsing('}', { -- 0123456789 ast = { 'UnknownFigure:0:0:', @@ -2564,7 +2627,7 @@ describe('api', function() }, { hl('InvalidFigureBrace', '}'), }) - check_parsing('{->}', 0, { + check_parsing('{->}', { -- 0123456789 ast = { { @@ -2583,7 +2646,7 @@ describe('api', function() hl('Arrow', '->'), hl('InvalidLambda', '}'), }) - check_parsing('{a,b}', 0, { + check_parsing('{a,b}', { -- 0123456789 ast = { { @@ -2610,7 +2673,7 @@ describe('api', function() hl('IdentifierName', 'b'), hl('InvalidLambda', '}'), }) - check_parsing('{a,}', 0, { + check_parsing('{a,}', { -- 0123456789 ast = { { @@ -2635,7 +2698,7 @@ describe('api', function() hl('Comma', ','), hl('InvalidLambda', '}'), }) - check_parsing('{@a:@b}', 0, { + check_parsing('{@a:@b}', { -- 0123456789 ast = { { @@ -2658,7 +2721,7 @@ describe('api', function() hl('Register', '@b'), hl('Dict', '}'), }) - check_parsing('{@a:@b,@c:@d}', 0, { + check_parsing('{@a:@b,@c:@d}', { -- 0123456789012 -- 0 1 ast = { @@ -2698,7 +2761,7 @@ describe('api', function() hl('Register', '@d'), hl('Dict', '}'), }) - check_parsing('{@a:@b,@c:@d,@e:@f,}', 0, { + check_parsing('{@a:@b,@c:@d,@e:@f,}', { -- 01234567890123456789 -- 0 1 ast = { @@ -2760,7 +2823,7 @@ describe('api', function() hl('Comma', ','), hl('Dict', '}'), }) - check_parsing('{@a:@b,@c:@d,@e:@f,@g:}', 0, { + check_parsing('{@a:@b,@c:@d,@e:@f,@g:}', { -- 01234567890123456789012 -- 0 1 2 ast = { @@ -2834,7 +2897,7 @@ describe('api', function() hl('Colon', ':'), hl('InvalidDict', '}'), }) - check_parsing('{@a:@b,}', 0, { + check_parsing('{@a:@b,}', { -- 01234567890123 -- 0 1 ast = { @@ -2864,7 +2927,7 @@ describe('api', function() hl('Comma', ','), hl('Dict', '}'), }) - check_parsing('{({f -> g})(@h)(@i)}', 0, { + check_parsing('{({f -> g})(@h)(@i)}', { -- 01234567890123456789 -- 0 1 ast = { @@ -2920,7 +2983,7 @@ describe('api', function() hl('CallingParenthesis', ')'), hl('Curly', '}'), }) - check_parsing('a:{b()}c', 0, { + check_parsing('a:{b()}c', { -- 01234567 ast = { { @@ -2957,7 +3020,7 @@ describe('api', function() hl('Curly', '}'), hl('IdentifierName', 'c'), }) - check_parsing('a:{{b, c -> @d + @e + ({f -> g})(@h)}(@i)}j', 0, { + check_parsing('a:{{b, c -> @d + @e + ({f -> g})(@h)}(@i)}j', { -- 01234567890123456789012345678901234567890123456 -- 0 1 2 3 4 ast = { @@ -3067,7 +3130,7 @@ describe('api', function() hl('Curly', '}'), hl('IdentifierName', 'j'), }) - check_parsing('{@a + @b : @c + @d, @e + @f : @g + @i}', 0, { + check_parsing('{@a + @b : @c + @d, @e + @f : @g + @i}', { -- 01234567890123456789012345678901234567 -- 0 1 2 3 ast = { @@ -3139,7 +3202,7 @@ describe('api', function() hl('Register', '@i', 1), hl('Dict', '}'), }) - check_parsing('-> -> ->', 0, { + check_parsing('-> -> ->', { -- 01234567 ast = { { @@ -3170,7 +3233,7 @@ describe('api', function() hl('InvalidArrow', '->', 1), hl('InvalidArrow', '->', 1), }) - check_parsing('a -> b -> c -> d', 0, { + check_parsing('a -> b -> c -> d', { -- 0123456789012345 -- 0 1 ast = { @@ -3207,7 +3270,7 @@ describe('api', function() hl('InvalidArrow', '->', 1), hl('IdentifierName', 'd', 1), }) - check_parsing('{a -> b -> c}', 0, { + check_parsing('{a -> b -> c}', { -- 0123456789012 -- 0 1 ast = { @@ -3243,7 +3306,7 @@ describe('api', function() hl('IdentifierName', 'c', 1), hl('Lambda', '}'), }) - check_parsing('{a: -> b}', 0, { + check_parsing('{a: -> b}', { -- 012345678 ast = { { @@ -3272,7 +3335,7 @@ describe('api', function() hl('Curly', '}'), }) - check_parsing('{a:b -> b}', 0, { + check_parsing('{a:b -> b}', { -- 0123456789 ast = { { @@ -3302,7 +3365,7 @@ describe('api', function() hl('Curly', '}'), }) - check_parsing('{a#b -> b}', 0, { + check_parsing('{a#b -> b}', { -- 0123456789 ast = { { @@ -3329,7 +3392,7 @@ describe('api', function() hl('IdentifierName', 'b', 1), hl('Curly', '}'), }) - check_parsing('{a : b : c}', 0, { + check_parsing('{a : b : c}', { -- 01234567890 -- 0 1 ast = { @@ -3365,7 +3428,7 @@ describe('api', function() hl('IdentifierName', 'c', 1), hl('Dict', '}'), }) - check_parsing('{', 0, { + check_parsing('{', { -- 0 ast = { 'UnknownFigure:0:0:{', @@ -3377,7 +3440,7 @@ describe('api', function() }, { hl('FigureBrace', '{'), }) - check_parsing('{a', 0, { + check_parsing('{a', { -- 01 ast = { { @@ -3395,7 +3458,7 @@ describe('api', function() hl('FigureBrace', '{'), hl('IdentifierName', 'a'), }) - check_parsing('{a,b', 0, { + check_parsing('{a,b', { -- 0123 ast = { { @@ -3421,7 +3484,7 @@ describe('api', function() hl('Comma', ','), hl('IdentifierName', 'b'), }) - check_parsing('{a,b->', 0, { + check_parsing('{a,b->', { -- 012345 ast = { { @@ -3449,7 +3512,7 @@ describe('api', function() hl('IdentifierName', 'b'), hl('Arrow', '->'), }) - check_parsing('{a,b->c', 0, { + check_parsing('{a,b->c', { -- 0123456 ast = { { @@ -3483,7 +3546,7 @@ describe('api', function() hl('Arrow', '->'), hl('IdentifierName', 'c'), }) - check_parsing('{a : b', 0, { + check_parsing('{a : b', { -- 012345 ast = { { @@ -3509,7 +3572,7 @@ describe('api', function() hl('Colon', ':', 1), hl('IdentifierName', 'b', 1), }) - check_parsing('{a : b,', 0, { + check_parsing('{a : b,', { -- 0123456 ast = { { @@ -3543,7 +3606,7 @@ describe('api', function() }) end) it('works with ternary operator', function() - check_parsing('a ? b : c', 0, { + check_parsing('a ? b : c', { -- 012345678 ast = { { @@ -3567,7 +3630,7 @@ describe('api', function() hl('TernaryColon', ':', 1), hl('IdentifierName', 'c', 1), }) - check_parsing('@a?@b?@c:@d:@e', 0, { + check_parsing('@a?@b?@c:@d:@e', { -- 01234567890123 -- 0 1 ast = { @@ -3608,7 +3671,7 @@ describe('api', function() hl('TernaryColon', ':'), hl('Register', '@e'), }) - check_parsing('@a?@b:@c?@d:@e', 0, { + check_parsing('@a?@b:@c?@d:@e', { -- 01234567890123 -- 0 1 ast = { @@ -3649,7 +3712,7 @@ describe('api', function() hl('TernaryColon', ':'), hl('Register', '@e'), }) - check_parsing('@a?@b?@c?@d:@e?@f:@g:@h?@i:@j:@k', 0, { + check_parsing('@a?@b?@c?@d:@e?@f:@g:@h?@i:@j:@k', { -- 01234567890123456789012345678901 -- 0 1 2 3 ast = { @@ -3738,7 +3801,7 @@ describe('api', function() hl('TernaryColon', ':'), hl('Register', '@k'), }) - check_parsing('?', 0, { + check_parsing('?', { -- 0 ast = { { @@ -3757,7 +3820,7 @@ describe('api', function() hl('InvalidTernary', '?'), }) - check_parsing('?:', 0, { + check_parsing('?:', { -- 01 ast = { { @@ -3782,7 +3845,7 @@ describe('api', function() hl('InvalidTernaryColon', ':'), }) - check_parsing('?::', 0, { + check_parsing('?::', { -- 012 ast = { { @@ -3814,7 +3877,7 @@ describe('api', function() hl('InvalidColon', ':'), }) - check_parsing('a?b', 0, { + check_parsing('a?b', { -- 012 ast = { { @@ -3839,7 +3902,7 @@ describe('api', function() hl('Ternary', '?'), hl('IdentifierName', 'b'), }) - check_parsing('a?b:', 0, { + check_parsing('a?b:', { -- 0123 ast = { { @@ -3866,7 +3929,7 @@ describe('api', function() hl('IdentifierScopeDelimiter', ':'), }) - check_parsing('a?b::c', 0, { + check_parsing('a?b::c', { -- 012345 ast = { { @@ -3892,7 +3955,7 @@ describe('api', function() hl('IdentifierName', 'c'), }) - check_parsing('a?b :', 0, { + check_parsing('a?b :', { -- 01234 ast = { { @@ -3919,7 +3982,7 @@ describe('api', function() hl('TernaryColon', ':', 1), }) - check_parsing('(@a?@b:@c)?@d:@e', 0, { + check_parsing('(@a?@b:@c)?@d:@e', { -- 0123456789012345 -- 0 1 ast = { @@ -3968,7 +4031,7 @@ describe('api', function() hl('Register', '@e'), }) - check_parsing('(@a?@b:@c)?(@d?@e:@f):(@g?@h:@i)', 0, { + check_parsing('(@a?@b:@c)?(@d?@e:@f):(@g?@h:@i)', { -- 01234567890123456789012345678901 -- 0 1 2 3 ast = { @@ -4063,7 +4126,7 @@ describe('api', function() hl('NestingParenthesis', ')'), }) - check_parsing('(@a?@b:@c)?@d?@e:@f:@g?@h:@i', 0, { + check_parsing('(@a?@b:@c)?@d?@e:@f:@g?@h:@i', { -- 0123456789012345678901234567 -- 0 1 2 ast = { @@ -4143,7 +4206,7 @@ describe('api', function() hl('TernaryColon', ':'), hl('Register', '@i'), }) - check_parsing('a?b{cdef}g:h', 0, { + check_parsing('a?b{cdef}g:h', { -- 012345678901 -- 0 1 ast = { @@ -4189,7 +4252,7 @@ describe('api', function() hl('TernaryColon', ':'), hl('IdentifierName', 'h'), }) - check_parsing('a ? b : c : d', 0, { + check_parsing('a ? b : c : d', { -- 0123456789012 -- 0 1 ast = { @@ -4228,7 +4291,7 @@ describe('api', function() }) end) it('works with comparison operators', function() - check_parsing('a == b', 0, { + check_parsing('a == b', { -- 012345 ast = { { @@ -4245,7 +4308,7 @@ describe('api', function() hl('IdentifierName', 'b', 1), }) - check_parsing('a ==? b', 0, { + check_parsing('a ==? b', { -- 0123456 ast = { { @@ -4263,7 +4326,7 @@ describe('api', function() hl('IdentifierName', 'b', 1), }) - check_parsing('a ==# b', 0, { + check_parsing('a ==# b', { -- 0123456 ast = { { @@ -4281,7 +4344,7 @@ describe('api', function() hl('IdentifierName', 'b', 1), }) - check_parsing('a !=# b', 0, { + check_parsing('a !=# b', { -- 0123456 ast = { { @@ -4299,7 +4362,7 @@ describe('api', function() hl('IdentifierName', 'b', 1), }) - check_parsing('a <=# b', 0, { + check_parsing('a <=# b', { -- 0123456 ast = { { @@ -4317,7 +4380,7 @@ describe('api', function() hl('IdentifierName', 'b', 1), }) - check_parsing('a >=# b', 0, { + check_parsing('a >=# b', { -- 0123456 ast = { { @@ -4335,7 +4398,7 @@ describe('api', function() hl('IdentifierName', 'b', 1), }) - check_parsing('a ># b', 0, { + check_parsing('a ># b', { -- 012345 ast = { { @@ -4353,7 +4416,7 @@ describe('api', function() hl('IdentifierName', 'b', 1), }) - check_parsing('a <# b', 0, { + check_parsing('a <# b', { -- 012345 ast = { { @@ -4371,7 +4434,7 @@ describe('api', function() hl('IdentifierName', 'b', 1), }) - check_parsing('a is#b', 0, { + check_parsing('a is#b', { -- 012345 ast = { { @@ -4389,7 +4452,7 @@ describe('api', function() hl('IdentifierName', 'b'), }) - check_parsing('a is?b', 0, { + check_parsing('a is?b', { -- 012345 ast = { { @@ -4407,7 +4470,7 @@ describe('api', function() hl('IdentifierName', 'b'), }) - check_parsing('a isnot b', 0, { + check_parsing('a isnot b', { -- 012345678 ast = { { @@ -4424,7 +4487,7 @@ describe('api', function() hl('IdentifierName', 'b', 1), }) - check_parsing('a < b < c', 0, { + check_parsing('a < b < c', { -- 012345678 ast = { { @@ -4453,7 +4516,7 @@ describe('api', function() hl('IdentifierName', 'c', 1), }) - check_parsing('a < b <# c', 0, { + check_parsing('a < b <# c', { -- 012345678 ast = { { @@ -4483,7 +4546,7 @@ describe('api', function() hl('IdentifierName', 'c', 1), }) - check_parsing('a += b', 0, { + check_parsing('a += b', { -- 012345 ast = { { @@ -4510,7 +4573,7 @@ describe('api', function() hl('InvalidComparison', '='), hl('IdentifierName', 'b', 1), }) - check_parsing('a + b == c + d', 0, { + check_parsing('a + b == c + d', { -- 01234567890123 -- 0 1 ast = { @@ -4543,7 +4606,7 @@ describe('api', function() hl('BinaryPlus', '+', 1), hl('IdentifierName', 'd', 1), }) - check_parsing('+ a == + b', 0, { + check_parsing('+ a == + b', { -- 0123456789 ast = { { @@ -4573,7 +4636,7 @@ describe('api', function() }) end) it('works with concat/subscript', function() - check_parsing('.', 0, { + check_parsing('.', { -- 0 ast = { { @@ -4591,7 +4654,7 @@ describe('api', function() hl('InvalidConcatOrSubscript', '.'), }) - check_parsing('a.', 0, { + check_parsing('a.', { -- 01 ast = { { @@ -4610,7 +4673,7 @@ describe('api', function() hl('ConcatOrSubscript', '.'), }) - check_parsing('a.b', 0, { + check_parsing('a.b', { -- 012 ast = { { @@ -4627,7 +4690,7 @@ describe('api', function() hl('IdentifierKey', 'b'), }) - check_parsing('1.2', 0, { + check_parsing('1.2', { -- 012 ast = { 'Float(val=1.200000e+00):0:0:1.2', @@ -4636,7 +4699,7 @@ describe('api', function() hl('Float', '1.2'), }) - check_parsing('1.2 + 1.3e-5', 0, { + check_parsing('1.2 + 1.3e-5', { -- 012345678901 -- 0 1 ast = { @@ -4654,7 +4717,7 @@ describe('api', function() hl('Float', '1.3e-5', 1), }) - check_parsing('a . 1.2 + 1.3e-5', 0, { + check_parsing('a . 1.2 + 1.3e-5', { -- 0123456789012345 -- 0 1 ast = { @@ -4688,7 +4751,7 @@ describe('api', function() hl('Float', '1.3e-5', 1), }) - check_parsing('1.3e-5 + 1.2 . a', 0, { + check_parsing('1.3e-5 + 1.2 . a', { -- 0123456789012345 -- 0 1 ast = { @@ -4714,7 +4777,7 @@ describe('api', function() hl('IdentifierName', 'a', 1), }) - check_parsing('1.3e-5 + a . 1.2', 0, { + check_parsing('1.3e-5 + a . 1.2', { -- 0123456789012345 -- 0 1 ast = { @@ -4748,7 +4811,7 @@ describe('api', function() hl('IdentifierKey', '2'), }) - check_parsing('1.2.3', 0, { + check_parsing('1.2.3', { -- 01234 ast = { { @@ -4773,7 +4836,7 @@ describe('api', function() hl('IdentifierKey', '3'), }) - check_parsing('a.1.2', 0, { + check_parsing('a.1.2', { -- 01234 ast = { { @@ -4798,7 +4861,7 @@ describe('api', function() hl('IdentifierKey', '2'), }) - check_parsing('a . 1.2', 0, { + check_parsing('a . 1.2', { -- 0123456 ast = { { @@ -4823,7 +4886,7 @@ describe('api', function() hl('IdentifierKey', '2'), }) - check_parsing('+a . +b', 0, { + check_parsing('+a . +b', { -- 0123456 ast = { { @@ -4852,7 +4915,7 @@ describe('api', function() hl('IdentifierName', 'b'), }) - check_parsing('a. b', 0, { + check_parsing('a. b', { -- 0123 ast = { { @@ -4869,7 +4932,7 @@ describe('api', function() hl('IdentifierName', 'b', 1), }) - check_parsing('a. 1', 0, { + check_parsing('a. 1', { -- 0123 ast = { { @@ -4887,7 +4950,7 @@ describe('api', function() }) end) it('works with bracket subscripts', function() - check_parsing(':', 0, { + check_parsing(':', { -- 0 ast = { { @@ -4904,7 +4967,7 @@ describe('api', function() }, { hl('InvalidColon', ':'), }) - check_parsing('a[]', 0, { + check_parsing('a[]', { -- 012 ast = { { @@ -4923,7 +4986,7 @@ describe('api', function() hl('SubscriptBracket', '['), hl('InvalidSubscriptBracket', ']'), }) - check_parsing('a[b:]', 0, { + check_parsing('a[b:]', { -- 01234 ast = { { @@ -4942,7 +5005,7 @@ describe('api', function() hl('SubscriptBracket', ']'), }) - check_parsing('a[b:c]', 0, { + check_parsing('a[b:c]', { -- 012345 ast = { { @@ -4961,7 +5024,7 @@ describe('api', function() hl('IdentifierName', 'c'), hl('SubscriptBracket', ']'), }) - check_parsing('a[b : c]', 0, { + check_parsing('a[b : c]', { -- 01234567 ast = { { @@ -4987,7 +5050,7 @@ describe('api', function() hl('SubscriptBracket', ']'), }) - check_parsing('a[: b]', 0, { + check_parsing('a[: b]', { -- 012345 ast = { { @@ -5012,7 +5075,7 @@ describe('api', function() hl('SubscriptBracket', ']'), }) - check_parsing('a[b :]', 0, { + check_parsing('a[b :]', { -- 012345 ast = { { @@ -5035,7 +5098,7 @@ describe('api', function() hl('SubscriptColon', ':', 1), hl('SubscriptBracket', ']'), }) - check_parsing('a[b][c][d](e)(f)(g)', 0, { + check_parsing('a[b][c][d](e)(f)(g)', { -- 0123456789012345678 -- 0 1 ast = { @@ -5098,7 +5161,7 @@ describe('api', function() hl('IdentifierName', 'g'), hl('CallingParenthesis', ')'), }) - check_parsing('{a}{b}{c}[d][e][f]', 0, { + check_parsing('{a}{b}{c}[d][e][f]', { -- 012345678901234567 -- 0 1 ast = { @@ -5171,7 +5234,7 @@ describe('api', function() }) end) it('supports list literals', function() - check_parsing('[]', 0, { + check_parsing('[]', { -- 01 ast = { 'ListLiteral:0:0:[', @@ -5181,7 +5244,7 @@ describe('api', function() hl('List', ']'), }) - check_parsing('[a]', 0, { + check_parsing('[a]', { -- 012 ast = { { @@ -5197,7 +5260,7 @@ describe('api', function() hl('List', ']'), }) - check_parsing('[a, b]', 0, { + check_parsing('[a, b]', { -- 012345 ast = { { @@ -5221,7 +5284,7 @@ describe('api', function() hl('List', ']'), }) - check_parsing('[a, b, c]', 0, { + check_parsing('[a, b, c]', { -- 012345678 ast = { { @@ -5253,7 +5316,7 @@ describe('api', function() hl('List', ']'), }) - check_parsing('[a, b, c, ]', 0, { + check_parsing('[a, b, c, ]', { -- 01234567890 -- 0 1 ast = { @@ -5292,7 +5355,7 @@ describe('api', function() hl('List', ']', 1), }) - check_parsing('[a : b, c : d]', 0, { + check_parsing('[a : b, c : d]', { -- 01234567890123 -- 0 1 ast = { @@ -5337,7 +5400,7 @@ describe('api', function() hl('List', ']'), }) - check_parsing(']', 0, { + check_parsing(']', { -- 0 ast = { 'ListLiteral:0:0:', @@ -5350,7 +5413,7 @@ describe('api', function() hl('InvalidList', ']'), }) - check_parsing('a]', 0, { + check_parsing('a]', { -- 01 ast = { { @@ -5369,7 +5432,7 @@ describe('api', function() hl('InvalidList', ']'), }) - check_parsing('[] []', 0, { + check_parsing('[] []', { -- 01234 ast = { { @@ -5390,9 +5453,23 @@ describe('api', function() hl('InvalidSpacing', ' '), hl('List', '['), hl('List', ']'), + }, { + [1] = { + ast = { + err = REMOVE_THIS, + ast = { + 'ListLiteral:0:0:[', + }, + }, + hl_fs = { + [3] = REMOVE_THIS, + [4] = REMOVE_THIS, + [5] = REMOVE_THIS, + }, + }, }) - check_parsing('[][]', 0, { + check_parsing('[][]', { -- 0123 ast = { { @@ -5413,7 +5490,7 @@ describe('api', function() hl('InvalidSubscriptBracket', ']'), }) - check_parsing('[', 0, { + check_parsing('[', { -- 0 ast = { 'ListLiteral:0:0:[', @@ -5426,7 +5503,7 @@ describe('api', function() hl('List', '['), }) - check_parsing('[1', 0, { + check_parsing('[1', { -- 01 ast = { { @@ -5446,7 +5523,7 @@ describe('api', function() }) end) it('works with strings', function() - check_parsing('\'abc\'', 0, { + check_parsing('\'abc\'', { -- 01234 ast = { 'SingleQuotedString(val="abc"):0:0:\'abc\'', @@ -5456,7 +5533,7 @@ describe('api', function() hl('SingleQuotedBody', 'abc'), hl('SingleQuote', '\''), }) - check_parsing('"abc"', 0, { + check_parsing('"abc"', { -- 01234 ast = { 'DoubleQuotedString(val="abc"):0:0:"abc"', @@ -5466,7 +5543,7 @@ describe('api', function() hl('DoubleQuotedBody', 'abc'), hl('DoubleQuote', '"'), }) - check_parsing('\'\'', 0, { + check_parsing('\'\'', { -- 01 ast = { 'SingleQuotedString(val=""):0:0:\'\'', @@ -5475,7 +5552,7 @@ describe('api', function() hl('SingleQuote', '\''), hl('SingleQuote', '\''), }) - check_parsing('""', 0, { + check_parsing('""', { -- 01 ast = { 'DoubleQuotedString(val=""):0:0:""', @@ -5484,7 +5561,7 @@ describe('api', function() hl('DoubleQuote', '"'), hl('DoubleQuote', '"'), }) - check_parsing('"', 0, { + check_parsing('"', { -- 0 ast = { 'DoubleQuotedString(val=""):0:0:"', @@ -5496,7 +5573,7 @@ describe('api', function() }, { hl('InvalidDoubleQuote', '"'), }) - check_parsing('\'', 0, { + check_parsing('\'', { -- 0 ast = { 'SingleQuotedString(val=""):0:0:\'', @@ -5508,7 +5585,7 @@ describe('api', function() }, { hl('InvalidSingleQuote', '\''), }) - check_parsing('"a', 0, { + check_parsing('"a', { -- 01 ast = { 'DoubleQuotedString(val="a"):0:0:"a', @@ -5521,7 +5598,7 @@ describe('api', function() hl('InvalidDoubleQuote', '"'), hl('InvalidDoubleQuotedBody', 'a'), }) - check_parsing('\'a', 0, { + check_parsing('\'a', { -- 01 ast = { 'SingleQuotedString(val="a"):0:0:\'a', @@ -5534,7 +5611,7 @@ describe('api', function() hl('InvalidSingleQuote', '\''), hl('InvalidSingleQuotedBody', 'a'), }) - check_parsing('\'abc\'\'def\'', 0, { + check_parsing('\'abc\'\'def\'', { -- 0123456789 ast = { 'SingleQuotedString(val="abc\'def"):0:0:\'abc\'\'def\'', @@ -5546,7 +5623,7 @@ describe('api', function() hl('SingleQuotedBody', 'def'), hl('SingleQuote', '\''), }) - check_parsing('\'abc\'\'', 0, { + check_parsing('\'abc\'\'', { -- 012345 ast = { 'SingleQuotedString(val="abc\'"):0:0:\'abc\'\'', @@ -5560,7 +5637,7 @@ describe('api', function() hl('InvalidSingleQuotedBody', 'abc'), hl('InvalidSingleQuotedQuote', '\'\''), }) - check_parsing('\'\'\'\'\'\'\'\'', 0, { + check_parsing('\'\'\'\'\'\'\'\'', { -- 01234567 ast = { 'SingleQuotedString(val="\'\'\'"):0:0:\'\'\'\'\'\'\'\'', @@ -5572,7 +5649,7 @@ describe('api', function() hl('SingleQuotedQuote', '\'\''), hl('SingleQuote', '\''), }) - check_parsing('\'\'\'a\'\'\'\'bc\'', 0, { + check_parsing('\'\'\'a\'\'\'\'bc\'', { -- 01234567890 -- 0 1 ast = { @@ -5587,7 +5664,7 @@ describe('api', function() hl('SingleQuotedBody', 'bc'), hl('SingleQuote', '\''), }) - check_parsing('"\\"\\"\\"\\""', 0, { + check_parsing('"\\"\\"\\"\\""', { -- 0123456789 ast = { 'DoubleQuotedString(val="\\"\\"\\"\\""):0:0:"\\"\\"\\"\\""', @@ -5600,7 +5677,7 @@ describe('api', function() hl('DoubleQuotedEscape', '\\"'), hl('DoubleQuote', '"'), }) - check_parsing('"abc\\"def\\"ghi\\"jkl\\"mno"', 0, { + check_parsing('"abc\\"def\\"ghi\\"jkl\\"mno"', { -- 0123456789012345678901234 -- 0 1 2 ast = { @@ -5619,7 +5696,7 @@ describe('api', function() hl('DoubleQuotedBody', 'mno'), hl('DoubleQuote', '"'), }) - check_parsing('"\\b\\e\\f\\r\\t\\\\"', 0, { + check_parsing('"\\b\\e\\f\\r\\t\\\\"', { -- 0123456789012345 -- 0 1 ast = { @@ -5635,7 +5712,7 @@ describe('api', function() hl('DoubleQuotedEscape', '\\\\'), hl('DoubleQuote', '"'), }) - check_parsing('"\\n\n"', 0, { + check_parsing('"\\n\n"', { -- 01234 ast = { 'DoubleQuotedString(val="\\\n\\\n"):0:0:"\\n\n"', @@ -5646,7 +5723,7 @@ describe('api', function() hl('DoubleQuotedBody', '\n'), hl('DoubleQuote', '"'), }) - check_parsing('"\\x00"', 0, { + check_parsing('"\\x00"', { -- 012345 ast = { 'DoubleQuotedString(val="\\0"):0:0:"\\x00"', @@ -5656,7 +5733,7 @@ describe('api', function() hl('DoubleQuotedEscape', '\\x00'), hl('DoubleQuote', '"'), }) - check_parsing('"\\xFF"', 0, { + check_parsing('"\\xFF"', { -- 012345 ast = { 'DoubleQuotedString(val="\255"):0:0:"\\xFF"', @@ -5666,7 +5743,7 @@ describe('api', function() hl('DoubleQuotedEscape', '\\xFF'), hl('DoubleQuote', '"'), }) - check_parsing('"\\xF"', 0, { + check_parsing('"\\xF"', { -- 012345 ast = { 'DoubleQuotedString(val="\\15"):0:0:"\\xF"', @@ -5676,7 +5753,7 @@ describe('api', function() hl('DoubleQuotedEscape', '\\xF'), hl('DoubleQuote', '"'), }) - check_parsing('"\\u00AB"', 0, { + check_parsing('"\\u00AB"', { -- 01234567 ast = { 'DoubleQuotedString(val="«"):0:0:"\\u00AB"', @@ -5686,7 +5763,7 @@ describe('api', function() hl('DoubleQuotedEscape', '\\u00AB'), hl('DoubleQuote', '"'), }) - check_parsing('"\\U000000AB"', 0, { + check_parsing('"\\U000000AB"', { -- 01234567 ast = { 'DoubleQuotedString(val="«"):0:0:"\\U000000AB"', @@ -5696,7 +5773,7 @@ describe('api', function() hl('DoubleQuotedEscape', '\\U000000AB'), hl('DoubleQuote', '"'), }) - check_parsing('"\\x"', 0, { + check_parsing('"\\x"', { -- 0123 ast = { 'DoubleQuotedString(val="x"):0:0:"\\x"', @@ -5707,7 +5784,7 @@ describe('api', function() hl('DoubleQuote', '"'), }) - check_parsing('"\\x', 0, { + check_parsing('"\\x', { -- 012 ast = { 'DoubleQuotedString(val="x"):0:0:"\\x', @@ -5721,7 +5798,7 @@ describe('api', function() hl('InvalidDoubleQuotedUnknownEscape', '\\x'), }) - check_parsing('"\\xF', 0, { + check_parsing('"\\xF', { -- 0123 ast = { 'DoubleQuotedString(val="\\15"):0:0:"\\xF', @@ -5735,7 +5812,7 @@ describe('api', function() hl('InvalidDoubleQuotedEscape', '\\xF'), }) - check_parsing('"\\u"', 0, { + check_parsing('"\\u"', { -- 0123 ast = { 'DoubleQuotedString(val="u"):0:0:"\\u"', @@ -5746,7 +5823,7 @@ describe('api', function() hl('DoubleQuote', '"'), }) - check_parsing('"\\u', 0, { + check_parsing('"\\u', { -- 012 ast = { 'DoubleQuotedString(val="u"):0:0:"\\u', @@ -5760,7 +5837,7 @@ describe('api', function() hl('InvalidDoubleQuotedUnknownEscape', '\\u'), }) - check_parsing('"\\U', 0, { + check_parsing('"\\U', { -- 012 ast = { 'DoubleQuotedString(val="U"):0:0:"\\U', @@ -5774,7 +5851,7 @@ describe('api', function() hl('InvalidDoubleQuotedUnknownEscape', '\\U'), }) - check_parsing('"\\U"', 0, { + check_parsing('"\\U"', { -- 0123 ast = { 'DoubleQuotedString(val="U"):0:0:"\\U"', @@ -5785,7 +5862,7 @@ describe('api', function() hl('DoubleQuote', '"'), }) - check_parsing('"\\xFX"', 0, { + check_parsing('"\\xFX"', { -- 012345 ast = { 'DoubleQuotedString(val="\\15X"):0:0:"\\xFX"', @@ -5797,7 +5874,7 @@ describe('api', function() hl('DoubleQuote', '"'), }) - check_parsing('"\\XFX"', 0, { + check_parsing('"\\XFX"', { -- 012345 ast = { 'DoubleQuotedString(val="\\15X"):0:0:"\\XFX"', @@ -5809,7 +5886,7 @@ describe('api', function() hl('DoubleQuote', '"'), }) - check_parsing('"\\xX"', 0, { + check_parsing('"\\xX"', { -- 01234 ast = { 'DoubleQuotedString(val="xX"):0:0:"\\xX"', @@ -5821,7 +5898,7 @@ describe('api', function() hl('DoubleQuote', '"'), }) - check_parsing('"\\XX"', 0, { + check_parsing('"\\XX"', { -- 01234 ast = { 'DoubleQuotedString(val="XX"):0:0:"\\XX"', @@ -5833,7 +5910,7 @@ describe('api', function() hl('DoubleQuote', '"'), }) - check_parsing('"\\uX"', 0, { + check_parsing('"\\uX"', { -- 01234 ast = { 'DoubleQuotedString(val="uX"):0:0:"\\uX"', @@ -5845,7 +5922,7 @@ describe('api', function() hl('DoubleQuote', '"'), }) - check_parsing('"\\UX"', 0, { + check_parsing('"\\UX"', { -- 01234 ast = { 'DoubleQuotedString(val="UX"):0:0:"\\UX"', @@ -5857,7 +5934,7 @@ describe('api', function() hl('DoubleQuote', '"'), }) - check_parsing('"\\x0X"', 0, { + check_parsing('"\\x0X"', { -- 012345 ast = { 'DoubleQuotedString(val="\\0X"):0:0:"\\x0X"', @@ -5869,7 +5946,7 @@ describe('api', function() hl('DoubleQuote', '"'), }) - check_parsing('"\\X0X"', 0, { + check_parsing('"\\X0X"', { -- 012345 ast = { 'DoubleQuotedString(val="\\0X"):0:0:"\\X0X"', @@ -5881,7 +5958,7 @@ describe('api', function() hl('DoubleQuote', '"'), }) - check_parsing('"\\u0X"', 0, { + check_parsing('"\\u0X"', { -- 012345 ast = { 'DoubleQuotedString(val="\\0X"):0:0:"\\u0X"', @@ -5893,7 +5970,7 @@ describe('api', function() hl('DoubleQuote', '"'), }) - check_parsing('"\\U0X"', 0, { + check_parsing('"\\U0X"', { -- 012345 ast = { 'DoubleQuotedString(val="\\0X"):0:0:"\\U0X"', @@ -5905,7 +5982,7 @@ describe('api', function() hl('DoubleQuote', '"'), }) - check_parsing('"\\x00X"', 0, { + check_parsing('"\\x00X"', { -- 0123456 ast = { 'DoubleQuotedString(val="\\0X"):0:0:"\\x00X"', @@ -5917,7 +5994,7 @@ describe('api', function() hl('DoubleQuote', '"'), }) - check_parsing('"\\X00X"', 0, { + check_parsing('"\\X00X"', { -- 0123456 ast = { 'DoubleQuotedString(val="\\0X"):0:0:"\\X00X"', @@ -5929,7 +6006,7 @@ describe('api', function() hl('DoubleQuote', '"'), }) - check_parsing('"\\u00X"', 0, { + check_parsing('"\\u00X"', { -- 0123456 ast = { 'DoubleQuotedString(val="\\0X"):0:0:"\\u00X"', @@ -5941,7 +6018,7 @@ describe('api', function() hl('DoubleQuote', '"'), }) - check_parsing('"\\U00X"', 0, { + check_parsing('"\\U00X"', { -- 0123456 ast = { 'DoubleQuotedString(val="\\0X"):0:0:"\\U00X"', @@ -5953,7 +6030,7 @@ describe('api', function() hl('DoubleQuote', '"'), }) - check_parsing('"\\u000X"', 0, { + check_parsing('"\\u000X"', { -- 01234567 ast = { 'DoubleQuotedString(val="\\0X"):0:0:"\\u000X"', @@ -5965,7 +6042,7 @@ describe('api', function() hl('DoubleQuote', '"'), }) - check_parsing('"\\U000X"', 0, { + check_parsing('"\\U000X"', { -- 01234567 ast = { 'DoubleQuotedString(val="\\0X"):0:0:"\\U000X"', @@ -5977,7 +6054,7 @@ describe('api', function() hl('DoubleQuote', '"'), }) - check_parsing('"\\u0000X"', 0, { + check_parsing('"\\u0000X"', { -- 012345678 ast = { 'DoubleQuotedString(val="\\0X"):0:0:"\\u0000X"', @@ -5989,7 +6066,7 @@ describe('api', function() hl('DoubleQuote', '"'), }) - check_parsing('"\\U0000X"', 0, { + check_parsing('"\\U0000X"', { -- 012345678 ast = { 'DoubleQuotedString(val="\\0X"):0:0:"\\U0000X"', @@ -6001,7 +6078,7 @@ describe('api', function() hl('DoubleQuote', '"'), }) - check_parsing('"\\U00000X"', 0, { + check_parsing('"\\U00000X"', { -- 0123456789 ast = { 'DoubleQuotedString(val="\\0X"):0:0:"\\U00000X"', @@ -6013,7 +6090,7 @@ describe('api', function() hl('DoubleQuote', '"'), }) - check_parsing('"\\U000000X"', 0, { + check_parsing('"\\U000000X"', { -- 01234567890 -- 0 1 ast = { @@ -6026,7 +6103,7 @@ describe('api', function() hl('DoubleQuote', '"'), }) - check_parsing('"\\U0000000X"', 0, { + check_parsing('"\\U0000000X"', { -- 012345678901 -- 0 1 ast = { @@ -6039,7 +6116,7 @@ describe('api', function() hl('DoubleQuote', '"'), }) - check_parsing('"\\U00000000X"', 0, { + check_parsing('"\\U00000000X"', { -- 0123456789012 -- 0 1 ast = { @@ -6052,7 +6129,7 @@ describe('api', function() hl('DoubleQuote', '"'), }) - check_parsing('"\\x000X"', 0, { + check_parsing('"\\x000X"', { -- 01234567 ast = { 'DoubleQuotedString(val="\\0000X"):0:0:"\\x000X"', @@ -6064,7 +6141,7 @@ describe('api', function() hl('DoubleQuote', '"'), }) - check_parsing('"\\X000X"', 0, { + check_parsing('"\\X000X"', { -- 01234567 ast = { 'DoubleQuotedString(val="\\0000X"):0:0:"\\X000X"', @@ -6076,7 +6153,7 @@ describe('api', function() hl('DoubleQuote', '"'), }) - check_parsing('"\\u00000X"', 0, { + check_parsing('"\\u00000X"', { -- 0123456789 ast = { 'DoubleQuotedString(val="\\0000X"):0:0:"\\u00000X"', @@ -6088,7 +6165,7 @@ describe('api', function() hl('DoubleQuote', '"'), }) - check_parsing('"\\U000000000X"', 0, { + check_parsing('"\\U000000000X"', { -- 01234567890123 -- 0 1 ast = { @@ -6101,7 +6178,7 @@ describe('api', function() hl('DoubleQuote', '"'), }) - check_parsing('"\\0"', 0, { + check_parsing('"\\0"', { -- 0123 ast = { 'DoubleQuotedString(val="\\0"):0:0:"\\0"', @@ -6112,7 +6189,7 @@ describe('api', function() hl('DoubleQuote', '"'), }) - check_parsing('"\\00"', 0, { + check_parsing('"\\00"', { -- 01234 ast = { 'DoubleQuotedString(val="\\0"):0:0:"\\00"', @@ -6123,7 +6200,7 @@ describe('api', function() hl('DoubleQuote', '"'), }) - check_parsing('"\\000"', 0, { + check_parsing('"\\000"', { -- 012345 ast = { 'DoubleQuotedString(val="\\0"):0:0:"\\000"', @@ -6134,7 +6211,7 @@ describe('api', function() hl('DoubleQuote', '"'), }) - check_parsing('"\\0000"', 0, { + check_parsing('"\\0000"', { -- 0123456 ast = { 'DoubleQuotedString(val="\\0000"):0:0:"\\0000"', @@ -6146,7 +6223,7 @@ describe('api', function() hl('DoubleQuote', '"'), }) - check_parsing('"\\8"', 0, { + check_parsing('"\\8"', { -- 0123 ast = { 'DoubleQuotedString(val="8"):0:0:"\\8"', @@ -6157,7 +6234,7 @@ describe('api', function() hl('DoubleQuote', '"'), }) - check_parsing('"\\08"', 0, { + check_parsing('"\\08"', { -- 01234 ast = { 'DoubleQuotedString(val="\\0008"):0:0:"\\08"', @@ -6169,7 +6246,7 @@ describe('api', function() hl('DoubleQuote', '"'), }) - check_parsing('"\\008"', 0, { + check_parsing('"\\008"', { -- 012345 ast = { 'DoubleQuotedString(val="\\0008"):0:0:"\\008"', @@ -6181,7 +6258,7 @@ describe('api', function() hl('DoubleQuote', '"'), }) - check_parsing('"\\0008"', 0, { + check_parsing('"\\0008"', { -- 0123456 ast = { 'DoubleQuotedString(val="\\0008"):0:0:"\\0008"', @@ -6193,7 +6270,7 @@ describe('api', function() hl('DoubleQuote', '"'), }) - check_parsing('"\\777"', 0, { + check_parsing('"\\777"', { -- 012345 ast = { 'DoubleQuotedString(val="\255"):0:0:"\\777"', @@ -6204,7 +6281,7 @@ describe('api', function() hl('DoubleQuote', '"'), }) - check_parsing('"\\050"', 0, { + check_parsing('"\\050"', { -- 012345 ast = { 'DoubleQuotedString(val="\40"):0:0:"\\050"', @@ -6215,7 +6292,7 @@ describe('api', function() hl('DoubleQuote', '"'), }) - check_parsing('"\\"', 0, { + check_parsing('"\\"', { -- 012345 ast = { 'DoubleQuotedString(val="\\21"):0:0:"\\"', @@ -6226,7 +6303,7 @@ describe('api', function() hl('DoubleQuote', '"'), }) - check_parsing('"\\<', 0, { + check_parsing('"\\<', { -- 012 ast = { 'DoubleQuotedString(val="<"):0:0:"\\<', @@ -6240,7 +6317,7 @@ describe('api', function() hl('InvalidDoubleQuotedUnknownEscape', '\\<'), }) - check_parsing('"\\<"', 0, { + check_parsing('"\\<"', { -- 0123 ast = { 'DoubleQuotedString(val="<"):0:0:"\\<"', @@ -6251,7 +6328,7 @@ describe('api', function() hl('DoubleQuote', '"'), }) - check_parsing('"\\@a}', 0, { + check_parsing('{->@a}', { ast = { { 'Lambda(\\di):0:0:{', @@ -1512,7 +1567,7 @@ describe('Expressions parser', function() hl('Register', '@a'), hl('Lambda', '}'), }) - check_parsing('{->@a+@b}', 0, { + check_parsing('{->@a+@b}', { -- 012345678 ast = { { @@ -1541,7 +1596,7 @@ describe('Expressions parser', function() hl('Register', '@b'), hl('Lambda', '}'), }) - check_parsing('{a->@a}', 0, { + check_parsing('{a->@a}', { -- 012345678 ast = { { @@ -1564,7 +1619,7 @@ describe('Expressions parser', function() hl('Register', '@a'), hl('Lambda', '}'), }) - check_parsing('{a,b->@a}', 0, { + check_parsing('{a,b->@a}', { -- 012345678 ast = { { @@ -1595,7 +1650,7 @@ describe('Expressions parser', function() hl('Register', '@a'), hl('Lambda', '}'), }) - check_parsing('{a,b,c->@a}', 0, { + check_parsing('{a,b,c->@a}', { -- 01234567890 ast = { { @@ -1634,7 +1689,7 @@ describe('Expressions parser', function() hl('Register', '@a'), hl('Lambda', '}'), }) - check_parsing('{a,b,c,d->@a}', 0, { + check_parsing('{a,b,c,d->@a}', { -- 0123456789012 ast = { { @@ -1681,7 +1736,7 @@ describe('Expressions parser', function() hl('Register', '@a'), hl('Lambda', '}'), }) - check_parsing('{a,b,c,d,->@a}', 0, { + check_parsing('{a,b,c,d,->@a}', { -- 01234567890123 ast = { { @@ -1734,7 +1789,7 @@ describe('Expressions parser', function() hl('Register', '@a'), hl('Lambda', '}'), }) - check_parsing('{a,b->{c,d->{e,f->@a}}}', 0, { + check_parsing('{a,b->{c,d->{e,f->@a}}}', { -- 01234567890123456789012 -- 0 1 2 ast = { @@ -1812,7 +1867,7 @@ describe('Expressions parser', function() hl('Lambda', '}'), hl('Lambda', '}'), }) - check_parsing('{a,b->c,d}', 0, { + check_parsing('{a,b->c,d}', { -- 0123456789 ast = { { @@ -1855,7 +1910,7 @@ describe('Expressions parser', function() hl('IdentifierName', 'd'), hl('Lambda', '}'), }) - check_parsing('a,b,c,d', 0, { + check_parsing('a,b,c,d', { -- 0123456789 ast = { { @@ -1891,7 +1946,7 @@ describe('Expressions parser', function() hl('InvalidComma', ','), hl('IdentifierName', 'd'), }) - check_parsing('a,b,c,d,', 0, { + check_parsing('a,b,c,d,', { -- 0123456789 ast = { { @@ -1933,7 +1988,7 @@ describe('Expressions parser', function() hl('IdentifierName', 'd'), hl('InvalidComma', ','), }) - check_parsing(',', 0, { + check_parsing(',', { -- 0123456789 ast = { { @@ -1950,7 +2005,7 @@ describe('Expressions parser', function() }, { hl('InvalidComma', ','), }) - check_parsing('{,a->@a}', 0, { + check_parsing('{,a->@a}', { -- 0123456789 ast = { { @@ -1984,7 +2039,7 @@ describe('Expressions parser', function() hl('Register', '@a'), hl('Curly', '}'), }) - check_parsing('}', 0, { + check_parsing('}', { -- 0123456789 ast = { 'UnknownFigure(---):0:0:', @@ -1996,7 +2051,7 @@ describe('Expressions parser', function() }, { hl('InvalidFigureBrace', '}'), }) - check_parsing('{->}', 0, { + check_parsing('{->}', { -- 0123456789 ast = { { @@ -2015,7 +2070,7 @@ describe('Expressions parser', function() hl('Arrow', '->'), hl('InvalidLambda', '}'), }) - check_parsing('{a,b}', 0, { + check_parsing('{a,b}', { -- 0123456789 ast = { { @@ -2042,7 +2097,7 @@ describe('Expressions parser', function() hl('IdentifierName', 'b'), hl('InvalidLambda', '}'), }) - check_parsing('{a,}', 0, { + check_parsing('{a,}', { -- 0123456789 ast = { { @@ -2067,7 +2122,7 @@ describe('Expressions parser', function() hl('Comma', ','), hl('InvalidLambda', '}'), }) - check_parsing('{@a:@b}', 0, { + check_parsing('{@a:@b}', { -- 0123456789 ast = { { @@ -2090,7 +2145,7 @@ describe('Expressions parser', function() hl('Register', '@b'), hl('Dict', '}'), }) - check_parsing('{@a:@b,@c:@d}', 0, { + check_parsing('{@a:@b,@c:@d}', { -- 0123456789012 -- 0 1 ast = { @@ -2130,7 +2185,7 @@ describe('Expressions parser', function() hl('Register', '@d'), hl('Dict', '}'), }) - check_parsing('{@a:@b,@c:@d,@e:@f,}', 0, { + check_parsing('{@a:@b,@c:@d,@e:@f,}', { -- 01234567890123456789 -- 0 1 ast = { @@ -2192,7 +2247,7 @@ describe('Expressions parser', function() hl('Comma', ','), hl('Dict', '}'), }) - check_parsing('{@a:@b,@c:@d,@e:@f,@g:}', 0, { + check_parsing('{@a:@b,@c:@d,@e:@f,@g:}', { -- 01234567890123456789012 -- 0 1 2 ast = { @@ -2266,7 +2321,7 @@ describe('Expressions parser', function() hl('Colon', ':'), hl('InvalidDict', '}'), }) - check_parsing('{@a:@b,}', 0, { + check_parsing('{@a:@b,}', { -- 01234567890123 -- 0 1 ast = { @@ -2296,7 +2351,7 @@ describe('Expressions parser', function() hl('Comma', ','), hl('Dict', '}'), }) - check_parsing('{({f -> g})(@h)(@i)}', 0, { + check_parsing('{({f -> g})(@h)(@i)}', { -- 01234567890123456789 -- 0 1 ast = { @@ -2352,7 +2407,7 @@ describe('Expressions parser', function() hl('CallingParenthesis', ')'), hl('Curly', '}'), }) - check_parsing('a:{b()}c', 0, { + check_parsing('a:{b()}c', { -- 01234567 ast = { { @@ -2389,7 +2444,7 @@ describe('Expressions parser', function() hl('Curly', '}'), hl('IdentifierName', 'c'), }) - check_parsing('a:{{b, c -> @d + @e + ({f -> g})(@h)}(@i)}j', 0, { + check_parsing('a:{{b, c -> @d + @e + ({f -> g})(@h)}(@i)}j', { -- 01234567890123456789012345678901234567890123456 -- 0 1 2 3 4 ast = { @@ -2499,7 +2554,7 @@ describe('Expressions parser', function() hl('Curly', '}'), hl('IdentifierName', 'j'), }) - check_parsing('{@a + @b : @c + @d, @e + @f : @g + @i}', 0, { + check_parsing('{@a + @b : @c + @d, @e + @f : @g + @i}', { -- 01234567890123456789012345678901234567 -- 0 1 2 3 ast = { @@ -2571,7 +2626,7 @@ describe('Expressions parser', function() hl('Register', '@i', 1), hl('Dict', '}'), }) - check_parsing('-> -> ->', 0, { + check_parsing('-> -> ->', { -- 01234567 ast = { { @@ -2602,7 +2657,7 @@ describe('Expressions parser', function() hl('InvalidArrow', '->', 1), hl('InvalidArrow', '->', 1), }) - check_parsing('a -> b -> c -> d', 0, { + check_parsing('a -> b -> c -> d', { -- 0123456789012345 -- 0 1 ast = { @@ -2639,7 +2694,7 @@ describe('Expressions parser', function() hl('InvalidArrow', '->', 1), hl('IdentifierName', 'd', 1), }) - check_parsing('{a -> b -> c}', 0, { + check_parsing('{a -> b -> c}', { -- 0123456789012 -- 0 1 ast = { @@ -2675,7 +2730,7 @@ describe('Expressions parser', function() hl('IdentifierName', 'c', 1), hl('Lambda', '}'), }) - check_parsing('{a: -> b}', 0, { + check_parsing('{a: -> b}', { -- 012345678 ast = { { @@ -2704,7 +2759,7 @@ describe('Expressions parser', function() hl('Curly', '}'), }) - check_parsing('{a:b -> b}', 0, { + check_parsing('{a:b -> b}', { -- 0123456789 ast = { { @@ -2734,7 +2789,7 @@ describe('Expressions parser', function() hl('Curly', '}'), }) - check_parsing('{a#b -> b}', 0, { + check_parsing('{a#b -> b}', { -- 0123456789 ast = { { @@ -2761,7 +2816,7 @@ describe('Expressions parser', function() hl('IdentifierName', 'b', 1), hl('Curly', '}'), }) - check_parsing('{a : b : c}', 0, { + check_parsing('{a : b : c}', { -- 01234567890 -- 0 1 ast = { @@ -2797,7 +2852,7 @@ describe('Expressions parser', function() hl('IdentifierName', 'c', 1), hl('Dict', '}'), }) - check_parsing('{', 0, { + check_parsing('{', { -- 0 ast = { 'UnknownFigure(\\di):0:0:{', @@ -2809,7 +2864,7 @@ describe('Expressions parser', function() }, { hl('FigureBrace', '{'), }) - check_parsing('{a', 0, { + check_parsing('{a', { -- 01 ast = { { @@ -2827,7 +2882,7 @@ describe('Expressions parser', function() hl('FigureBrace', '{'), hl('IdentifierName', 'a'), }) - check_parsing('{a,b', 0, { + check_parsing('{a,b', { -- 0123 ast = { { @@ -2853,7 +2908,7 @@ describe('Expressions parser', function() hl('Comma', ','), hl('IdentifierName', 'b'), }) - check_parsing('{a,b->', 0, { + check_parsing('{a,b->', { -- 012345 ast = { { @@ -2881,7 +2936,7 @@ describe('Expressions parser', function() hl('IdentifierName', 'b'), hl('Arrow', '->'), }) - check_parsing('{a,b->c', 0, { + check_parsing('{a,b->c', { -- 0123456 ast = { { @@ -2915,7 +2970,7 @@ describe('Expressions parser', function() hl('Arrow', '->'), hl('IdentifierName', 'c'), }) - check_parsing('{a : b', 0, { + check_parsing('{a : b', { -- 012345 ast = { { @@ -2941,7 +2996,7 @@ describe('Expressions parser', function() hl('Colon', ':', 1), hl('IdentifierName', 'b', 1), }) - check_parsing('{a : b,', 0, { + check_parsing('{a : b,', { -- 0123456 ast = { { @@ -2975,7 +3030,7 @@ describe('Expressions parser', function() }) end) itp('works with ternary operator', function() - check_parsing('a ? b : c', 0, { + check_parsing('a ? b : c', { -- 012345678 ast = { { @@ -2999,7 +3054,7 @@ describe('Expressions parser', function() hl('TernaryColon', ':', 1), hl('IdentifierName', 'c', 1), }) - check_parsing('@a?@b?@c:@d:@e', 0, { + check_parsing('@a?@b?@c:@d:@e', { -- 01234567890123 -- 0 1 ast = { @@ -3040,7 +3095,7 @@ describe('Expressions parser', function() hl('TernaryColon', ':'), hl('Register', '@e'), }) - check_parsing('@a?@b:@c?@d:@e', 0, { + check_parsing('@a?@b:@c?@d:@e', { -- 01234567890123 -- 0 1 ast = { @@ -3081,7 +3136,7 @@ describe('Expressions parser', function() hl('TernaryColon', ':'), hl('Register', '@e'), }) - check_parsing('@a?@b?@c?@d:@e?@f:@g:@h?@i:@j:@k', 0, { + check_parsing('@a?@b?@c?@d:@e?@f:@g:@h?@i:@j:@k', { -- 01234567890123456789012345678901 -- 0 1 2 3 ast = { @@ -3170,7 +3225,7 @@ describe('Expressions parser', function() hl('TernaryColon', ':'), hl('Register', '@k'), }) - check_parsing('?', 0, { + check_parsing('?', { -- 0 ast = { { @@ -3189,7 +3244,7 @@ describe('Expressions parser', function() hl('InvalidTernary', '?'), }) - check_parsing('?:', 0, { + check_parsing('?:', { -- 01 ast = { { @@ -3214,7 +3269,7 @@ describe('Expressions parser', function() hl('InvalidTernaryColon', ':'), }) - check_parsing('?::', 0, { + check_parsing('?::', { -- 012 ast = { { @@ -3246,7 +3301,7 @@ describe('Expressions parser', function() hl('InvalidColon', ':'), }) - check_parsing('a?b', 0, { + check_parsing('a?b', { -- 012 ast = { { @@ -3271,7 +3326,7 @@ describe('Expressions parser', function() hl('Ternary', '?'), hl('IdentifierName', 'b'), }) - check_parsing('a?b:', 0, { + check_parsing('a?b:', { -- 0123 ast = { { @@ -3298,7 +3353,7 @@ describe('Expressions parser', function() hl('IdentifierScopeDelimiter', ':'), }) - check_parsing('a?b::c', 0, { + check_parsing('a?b::c', { -- 012345 ast = { { @@ -3324,7 +3379,7 @@ describe('Expressions parser', function() hl('IdentifierName', 'c'), }) - check_parsing('a?b :', 0, { + check_parsing('a?b :', { -- 01234 ast = { { @@ -3351,7 +3406,7 @@ describe('Expressions parser', function() hl('TernaryColon', ':', 1), }) - check_parsing('(@a?@b:@c)?@d:@e', 0, { + check_parsing('(@a?@b:@c)?@d:@e', { -- 0123456789012345 -- 0 1 ast = { @@ -3400,7 +3455,7 @@ describe('Expressions parser', function() hl('Register', '@e'), }) - check_parsing('(@a?@b:@c)?(@d?@e:@f):(@g?@h:@i)', 0, { + check_parsing('(@a?@b:@c)?(@d?@e:@f):(@g?@h:@i)', { -- 01234567890123456789012345678901 -- 0 1 2 3 ast = { @@ -3495,7 +3550,7 @@ describe('Expressions parser', function() hl('NestingParenthesis', ')'), }) - check_parsing('(@a?@b:@c)?@d?@e:@f:@g?@h:@i', 0, { + check_parsing('(@a?@b:@c)?@d?@e:@f:@g?@h:@i', { -- 0123456789012345678901234567 -- 0 1 2 ast = { @@ -3575,7 +3630,7 @@ describe('Expressions parser', function() hl('TernaryColon', ':'), hl('Register', '@i'), }) - check_parsing('a?b{cdef}g:h', 0, { + check_parsing('a?b{cdef}g:h', { -- 012345678901 -- 0 1 ast = { @@ -3621,7 +3676,7 @@ describe('Expressions parser', function() hl('TernaryColon', ':'), hl('IdentifierName', 'h'), }) - check_parsing('a ? b : c : d', 0, { + check_parsing('a ? b : c : d', { -- 0123456789012 -- 0 1 ast = { @@ -3660,7 +3715,7 @@ describe('Expressions parser', function() }) end) itp('works with comparison operators', function() - check_parsing('a == b', 0, { + check_parsing('a == b', { -- 012345 ast = { { @@ -3677,7 +3732,7 @@ describe('Expressions parser', function() hl('IdentifierName', 'b', 1), }) - check_parsing('a ==? b', 0, { + check_parsing('a ==? b', { -- 0123456 ast = { { @@ -3695,7 +3750,7 @@ describe('Expressions parser', function() hl('IdentifierName', 'b', 1), }) - check_parsing('a ==# b', 0, { + check_parsing('a ==# b', { -- 0123456 ast = { { @@ -3713,7 +3768,7 @@ describe('Expressions parser', function() hl('IdentifierName', 'b', 1), }) - check_parsing('a !=# b', 0, { + check_parsing('a !=# b', { -- 0123456 ast = { { @@ -3731,7 +3786,7 @@ describe('Expressions parser', function() hl('IdentifierName', 'b', 1), }) - check_parsing('a <=# b', 0, { + check_parsing('a <=# b', { -- 0123456 ast = { { @@ -3749,7 +3804,7 @@ describe('Expressions parser', function() hl('IdentifierName', 'b', 1), }) - check_parsing('a >=# b', 0, { + check_parsing('a >=# b', { -- 0123456 ast = { { @@ -3767,7 +3822,7 @@ describe('Expressions parser', function() hl('IdentifierName', 'b', 1), }) - check_parsing('a ># b', 0, { + check_parsing('a ># b', { -- 012345 ast = { { @@ -3785,7 +3840,7 @@ describe('Expressions parser', function() hl('IdentifierName', 'b', 1), }) - check_parsing('a <# b', 0, { + check_parsing('a <# b', { -- 012345 ast = { { @@ -3803,7 +3858,7 @@ describe('Expressions parser', function() hl('IdentifierName', 'b', 1), }) - check_parsing('a is#b', 0, { + check_parsing('a is#b', { -- 012345 ast = { { @@ -3821,7 +3876,7 @@ describe('Expressions parser', function() hl('IdentifierName', 'b'), }) - check_parsing('a is?b', 0, { + check_parsing('a is?b', { -- 012345 ast = { { @@ -3839,7 +3894,7 @@ describe('Expressions parser', function() hl('IdentifierName', 'b'), }) - check_parsing('a isnot b', 0, { + check_parsing('a isnot b', { -- 012345678 ast = { { @@ -3856,7 +3911,7 @@ describe('Expressions parser', function() hl('IdentifierName', 'b', 1), }) - check_parsing('a < b < c', 0, { + check_parsing('a < b < c', { -- 012345678 ast = { { @@ -3885,7 +3940,7 @@ describe('Expressions parser', function() hl('IdentifierName', 'c', 1), }) - check_parsing('a < b <# c', 0, { + check_parsing('a < b <# c', { -- 012345678 ast = { { @@ -3915,7 +3970,7 @@ describe('Expressions parser', function() hl('IdentifierName', 'c', 1), }) - check_parsing('a += b', 0, { + check_parsing('a += b', { -- 012345 ast = { { @@ -3942,7 +3997,7 @@ describe('Expressions parser', function() hl('InvalidComparison', '='), hl('IdentifierName', 'b', 1), }) - check_parsing('a + b == c + d', 0, { + check_parsing('a + b == c + d', { -- 01234567890123 -- 0 1 ast = { @@ -3975,7 +4030,7 @@ describe('Expressions parser', function() hl('BinaryPlus', '+', 1), hl('IdentifierName', 'd', 1), }) - check_parsing('+ a == + b', 0, { + check_parsing('+ a == + b', { -- 0123456789 ast = { { @@ -4005,7 +4060,7 @@ describe('Expressions parser', function() }) end) itp('works with concat/subscript', function() - check_parsing('.', 0, { + check_parsing('.', { -- 0 ast = { { @@ -4023,7 +4078,7 @@ describe('Expressions parser', function() hl('InvalidConcatOrSubscript', '.'), }) - check_parsing('a.', 0, { + check_parsing('a.', { -- 01 ast = { { @@ -4042,7 +4097,7 @@ describe('Expressions parser', function() hl('ConcatOrSubscript', '.'), }) - check_parsing('a.b', 0, { + check_parsing('a.b', { -- 012 ast = { { @@ -4059,7 +4114,7 @@ describe('Expressions parser', function() hl('IdentifierKey', 'b'), }) - check_parsing('1.2', 0, { + check_parsing('1.2', { -- 012 ast = { 'Float(val=1.200000e+00):0:0:1.2', @@ -4068,7 +4123,7 @@ describe('Expressions parser', function() hl('Float', '1.2'), }) - check_parsing('1.2 + 1.3e-5', 0, { + check_parsing('1.2 + 1.3e-5', { -- 012345678901 -- 0 1 ast = { @@ -4086,7 +4141,7 @@ describe('Expressions parser', function() hl('Float', '1.3e-5', 1), }) - check_parsing('a . 1.2 + 1.3e-5', 0, { + check_parsing('a . 1.2 + 1.3e-5', { -- 0123456789012345 -- 0 1 ast = { @@ -4120,7 +4175,7 @@ describe('Expressions parser', function() hl('Float', '1.3e-5', 1), }) - check_parsing('1.3e-5 + 1.2 . a', 0, { + check_parsing('1.3e-5 + 1.2 . a', { -- 0123456789012345 -- 0 1 ast = { @@ -4146,7 +4201,7 @@ describe('Expressions parser', function() hl('IdentifierName', 'a', 1), }) - check_parsing('1.3e-5 + a . 1.2', 0, { + check_parsing('1.3e-5 + a . 1.2', { -- 0123456789012345 -- 0 1 ast = { @@ -4180,7 +4235,7 @@ describe('Expressions parser', function() hl('IdentifierKey', '2'), }) - check_parsing('1.2.3', 0, { + check_parsing('1.2.3', { -- 01234 ast = { { @@ -4205,7 +4260,7 @@ describe('Expressions parser', function() hl('IdentifierKey', '3'), }) - check_parsing('a.1.2', 0, { + check_parsing('a.1.2', { -- 01234 ast = { { @@ -4230,7 +4285,7 @@ describe('Expressions parser', function() hl('IdentifierKey', '2'), }) - check_parsing('a . 1.2', 0, { + check_parsing('a . 1.2', { -- 0123456 ast = { { @@ -4255,7 +4310,7 @@ describe('Expressions parser', function() hl('IdentifierKey', '2'), }) - check_parsing('+a . +b', 0, { + check_parsing('+a . +b', { -- 0123456 ast = { { @@ -4284,7 +4339,7 @@ describe('Expressions parser', function() hl('IdentifierName', 'b'), }) - check_parsing('a. b', 0, { + check_parsing('a. b', { -- 0123 ast = { { @@ -4301,7 +4356,7 @@ describe('Expressions parser', function() hl('IdentifierName', 'b', 1), }) - check_parsing('a. 1', 0, { + check_parsing('a. 1', { -- 0123 ast = { { @@ -4319,7 +4374,7 @@ describe('Expressions parser', function() }) end) itp('works with bracket subscripts', function() - check_parsing(':', 0, { + check_parsing(':', { -- 0 ast = { { @@ -4336,7 +4391,7 @@ describe('Expressions parser', function() }, { hl('InvalidColon', ':'), }) - check_parsing('a[]', 0, { + check_parsing('a[]', { -- 012 ast = { { @@ -4355,7 +4410,7 @@ describe('Expressions parser', function() hl('SubscriptBracket', '['), hl('InvalidSubscriptBracket', ']'), }) - check_parsing('a[b:]', 0, { + check_parsing('a[b:]', { -- 01234 ast = { { @@ -4374,7 +4429,7 @@ describe('Expressions parser', function() hl('SubscriptBracket', ']'), }) - check_parsing('a[b:c]', 0, { + check_parsing('a[b:c]', { -- 012345 ast = { { @@ -4393,7 +4448,7 @@ describe('Expressions parser', function() hl('IdentifierName', 'c'), hl('SubscriptBracket', ']'), }) - check_parsing('a[b : c]', 0, { + check_parsing('a[b : c]', { -- 01234567 ast = { { @@ -4419,7 +4474,7 @@ describe('Expressions parser', function() hl('SubscriptBracket', ']'), }) - check_parsing('a[: b]', 0, { + check_parsing('a[: b]', { -- 012345 ast = { { @@ -4444,7 +4499,7 @@ describe('Expressions parser', function() hl('SubscriptBracket', ']'), }) - check_parsing('a[b :]', 0, { + check_parsing('a[b :]', { -- 012345 ast = { { @@ -4467,7 +4522,7 @@ describe('Expressions parser', function() hl('SubscriptColon', ':', 1), hl('SubscriptBracket', ']'), }) - check_parsing('a[b][c][d](e)(f)(g)', 0, { + check_parsing('a[b][c][d](e)(f)(g)', { -- 0123456789012345678 -- 0 1 ast = { @@ -4530,7 +4585,7 @@ describe('Expressions parser', function() hl('IdentifierName', 'g'), hl('CallingParenthesis', ')'), }) - check_parsing('{a}{b}{c}[d][e][f]', 0, { + check_parsing('{a}{b}{c}[d][e][f]', { -- 012345678901234567 -- 0 1 ast = { @@ -4603,7 +4658,7 @@ describe('Expressions parser', function() }) end) itp('supports list literals', function() - check_parsing('[]', 0, { + check_parsing('[]', { -- 01 ast = { 'ListLiteral:0:0:[', @@ -4613,7 +4668,7 @@ describe('Expressions parser', function() hl('List', ']'), }) - check_parsing('[a]', 0, { + check_parsing('[a]', { -- 012 ast = { { @@ -4629,7 +4684,7 @@ describe('Expressions parser', function() hl('List', ']'), }) - check_parsing('[a, b]', 0, { + check_parsing('[a, b]', { -- 012345 ast = { { @@ -4653,7 +4708,7 @@ describe('Expressions parser', function() hl('List', ']'), }) - check_parsing('[a, b, c]', 0, { + check_parsing('[a, b, c]', { -- 012345678 ast = { { @@ -4685,7 +4740,7 @@ describe('Expressions parser', function() hl('List', ']'), }) - check_parsing('[a, b, c, ]', 0, { + check_parsing('[a, b, c, ]', { -- 01234567890 -- 0 1 ast = { @@ -4724,7 +4779,7 @@ describe('Expressions parser', function() hl('List', ']', 1), }) - check_parsing('[a : b, c : d]', 0, { + check_parsing('[a : b, c : d]', { -- 01234567890123 -- 0 1 ast = { @@ -4769,7 +4824,7 @@ describe('Expressions parser', function() hl('List', ']'), }) - check_parsing(']', 0, { + check_parsing(']', { -- 0 ast = { 'ListLiteral:0:0:', @@ -4782,7 +4837,7 @@ describe('Expressions parser', function() hl('InvalidList', ']'), }) - check_parsing('a]', 0, { + check_parsing('a]', { -- 01 ast = { { @@ -4801,7 +4856,7 @@ describe('Expressions parser', function() hl('InvalidList', ']'), }) - check_parsing('[] []', 0, { + check_parsing('[] []', { -- 01234 ast = { { @@ -4822,9 +4877,23 @@ describe('Expressions parser', function() hl('InvalidSpacing', ' '), hl('List', '['), hl('List', ']'), + }, { + [1] = { + ast = { + err = REMOVE_THIS, + ast = { + 'ListLiteral:0:0:[', + }, + }, + hl_fs = { + [3] = REMOVE_THIS, + [4] = REMOVE_THIS, + [5] = REMOVE_THIS, + }, + }, }) - check_parsing('[][]', 0, { + check_parsing('[][]', { -- 0123 ast = { { @@ -4845,7 +4914,7 @@ describe('Expressions parser', function() hl('InvalidSubscriptBracket', ']'), }) - check_parsing('[', 0, { + check_parsing('[', { -- 0 ast = { 'ListLiteral:0:0:[', @@ -4858,7 +4927,7 @@ describe('Expressions parser', function() hl('List', '['), }) - check_parsing('[1', 0, { + check_parsing('[1', { -- 01 ast = { { @@ -4878,7 +4947,7 @@ describe('Expressions parser', function() }) end) itp('works with strings', function() - check_parsing('\'abc\'', 0, { + check_parsing('\'abc\'', { -- 01234 ast = { 'SingleQuotedString(val="abc"):0:0:\'abc\'', @@ -4888,7 +4957,7 @@ describe('Expressions parser', function() hl('SingleQuotedBody', 'abc'), hl('SingleQuote', '\''), }) - check_parsing('"abc"', 0, { + check_parsing('"abc"', { -- 01234 ast = { 'DoubleQuotedString(val="abc"):0:0:"abc"', @@ -4898,7 +4967,7 @@ describe('Expressions parser', function() hl('DoubleQuotedBody', 'abc'), hl('DoubleQuote', '"'), }) - check_parsing('\'\'', 0, { + check_parsing('\'\'', { -- 01 ast = { 'SingleQuotedString(val=NULL):0:0:\'\'', @@ -4907,7 +4976,7 @@ describe('Expressions parser', function() hl('SingleQuote', '\''), hl('SingleQuote', '\''), }) - check_parsing('""', 0, { + check_parsing('""', { -- 01 ast = { 'DoubleQuotedString(val=NULL):0:0:""', @@ -4916,7 +4985,7 @@ describe('Expressions parser', function() hl('DoubleQuote', '"'), hl('DoubleQuote', '"'), }) - check_parsing('"', 0, { + check_parsing('"', { -- 0 ast = { 'DoubleQuotedString(val=NULL):0:0:"', @@ -4928,7 +4997,7 @@ describe('Expressions parser', function() }, { hl('InvalidDoubleQuote', '"'), }) - check_parsing('\'', 0, { + check_parsing('\'', { -- 0 ast = { 'SingleQuotedString(val=NULL):0:0:\'', @@ -4940,7 +5009,7 @@ describe('Expressions parser', function() }, { hl('InvalidSingleQuote', '\''), }) - check_parsing('"a', 0, { + check_parsing('"a', { -- 01 ast = { 'DoubleQuotedString(val="a"):0:0:"a', @@ -4953,7 +5022,7 @@ describe('Expressions parser', function() hl('InvalidDoubleQuote', '"'), hl('InvalidDoubleQuotedBody', 'a'), }) - check_parsing('\'a', 0, { + check_parsing('\'a', { -- 01 ast = { 'SingleQuotedString(val="a"):0:0:\'a', @@ -4966,7 +5035,7 @@ describe('Expressions parser', function() hl('InvalidSingleQuote', '\''), hl('InvalidSingleQuotedBody', 'a'), }) - check_parsing('\'abc\'\'def\'', 0, { + check_parsing('\'abc\'\'def\'', { -- 0123456789 ast = { 'SingleQuotedString(val="abc\'def"):0:0:\'abc\'\'def\'', @@ -4978,7 +5047,7 @@ describe('Expressions parser', function() hl('SingleQuotedBody', 'def'), hl('SingleQuote', '\''), }) - check_parsing('\'abc\'\'', 0, { + check_parsing('\'abc\'\'', { -- 012345 ast = { 'SingleQuotedString(val="abc\'"):0:0:\'abc\'\'', @@ -4992,7 +5061,7 @@ describe('Expressions parser', function() hl('InvalidSingleQuotedBody', 'abc'), hl('InvalidSingleQuotedQuote', '\'\''), }) - check_parsing('\'\'\'\'\'\'\'\'', 0, { + check_parsing('\'\'\'\'\'\'\'\'', { -- 01234567 ast = { 'SingleQuotedString(val="\'\'\'"):0:0:\'\'\'\'\'\'\'\'', @@ -5004,7 +5073,7 @@ describe('Expressions parser', function() hl('SingleQuotedQuote', '\'\''), hl('SingleQuote', '\''), }) - check_parsing('\'\'\'a\'\'\'\'bc\'', 0, { + check_parsing('\'\'\'a\'\'\'\'bc\'', { -- 01234567890 -- 0 1 ast = { @@ -5019,7 +5088,7 @@ describe('Expressions parser', function() hl('SingleQuotedBody', 'bc'), hl('SingleQuote', '\''), }) - check_parsing('"\\"\\"\\"\\""', 0, { + check_parsing('"\\"\\"\\"\\""', { -- 0123456789 ast = { 'DoubleQuotedString(val="\\"\\"\\"\\""):0:0:"\\"\\"\\"\\""', @@ -5032,7 +5101,7 @@ describe('Expressions parser', function() hl('DoubleQuotedEscape', '\\"'), hl('DoubleQuote', '"'), }) - check_parsing('"abc\\"def\\"ghi\\"jkl\\"mno"', 0, { + check_parsing('"abc\\"def\\"ghi\\"jkl\\"mno"', { -- 0123456789012345678901234 -- 0 1 2 ast = { @@ -5051,7 +5120,7 @@ describe('Expressions parser', function() hl('DoubleQuotedBody', 'mno'), hl('DoubleQuote', '"'), }) - check_parsing('"\\b\\e\\f\\r\\t\\\\"', 0, { + check_parsing('"\\b\\e\\f\\r\\t\\\\"', { -- 0123456789012345 -- 0 1 ast = { @@ -5067,7 +5136,7 @@ describe('Expressions parser', function() hl('DoubleQuotedEscape', '\\\\'), hl('DoubleQuote', '"'), }) - check_parsing('"\\n\n"', 0, { + check_parsing('"\\n\n"', { -- 01234 ast = { 'DoubleQuotedString(val="\\\n\\\n"):0:0:"\\n\n"', @@ -5078,7 +5147,7 @@ describe('Expressions parser', function() hl('DoubleQuotedBody', '\n'), hl('DoubleQuote', '"'), }) - check_parsing('"\\x00"', 0, { + check_parsing('"\\x00"', { -- 012345 ast = { 'DoubleQuotedString(val="\\0"):0:0:"\\x00"', @@ -5088,7 +5157,7 @@ describe('Expressions parser', function() hl('DoubleQuotedEscape', '\\x00'), hl('DoubleQuote', '"'), }) - check_parsing('"\\xFF"', 0, { + check_parsing('"\\xFF"', { -- 012345 ast = { 'DoubleQuotedString(val="\255"):0:0:"\\xFF"', @@ -5098,7 +5167,7 @@ describe('Expressions parser', function() hl('DoubleQuotedEscape', '\\xFF'), hl('DoubleQuote', '"'), }) - check_parsing('"\\xF"', 0, { + check_parsing('"\\xF"', { -- 012345 ast = { 'DoubleQuotedString(val="\\15"):0:0:"\\xF"', @@ -5108,7 +5177,7 @@ describe('Expressions parser', function() hl('DoubleQuotedEscape', '\\xF'), hl('DoubleQuote', '"'), }) - check_parsing('"\\u00AB"', 0, { + check_parsing('"\\u00AB"', { -- 01234567 ast = { 'DoubleQuotedString(val="«"):0:0:"\\u00AB"', @@ -5118,7 +5187,7 @@ describe('Expressions parser', function() hl('DoubleQuotedEscape', '\\u00AB'), hl('DoubleQuote', '"'), }) - check_parsing('"\\U000000AB"', 0, { + check_parsing('"\\U000000AB"', { -- 01234567 ast = { 'DoubleQuotedString(val="«"):0:0:"\\U000000AB"', @@ -5128,7 +5197,7 @@ describe('Expressions parser', function() hl('DoubleQuotedEscape', '\\U000000AB'), hl('DoubleQuote', '"'), }) - check_parsing('"\\x"', 0, { + check_parsing('"\\x"', { -- 0123 ast = { 'DoubleQuotedString(val="x"):0:0:"\\x"', @@ -5139,7 +5208,7 @@ describe('Expressions parser', function() hl('DoubleQuote', '"'), }) - check_parsing('"\\x', 0, { + check_parsing('"\\x', { -- 012 ast = { 'DoubleQuotedString(val="x"):0:0:"\\x', @@ -5153,7 +5222,7 @@ describe('Expressions parser', function() hl('InvalidDoubleQuotedUnknownEscape', '\\x'), }) - check_parsing('"\\xF', 0, { + check_parsing('"\\xF', { -- 0123 ast = { 'DoubleQuotedString(val="\\15"):0:0:"\\xF', @@ -5167,7 +5236,7 @@ describe('Expressions parser', function() hl('InvalidDoubleQuotedEscape', '\\xF'), }) - check_parsing('"\\u"', 0, { + check_parsing('"\\u"', { -- 0123 ast = { 'DoubleQuotedString(val="u"):0:0:"\\u"', @@ -5178,7 +5247,7 @@ describe('Expressions parser', function() hl('DoubleQuote', '"'), }) - check_parsing('"\\u', 0, { + check_parsing('"\\u', { -- 012 ast = { 'DoubleQuotedString(val="u"):0:0:"\\u', @@ -5192,7 +5261,7 @@ describe('Expressions parser', function() hl('InvalidDoubleQuotedUnknownEscape', '\\u'), }) - check_parsing('"\\U', 0, { + check_parsing('"\\U', { -- 012 ast = { 'DoubleQuotedString(val="U"):0:0:"\\U', @@ -5206,7 +5275,7 @@ describe('Expressions parser', function() hl('InvalidDoubleQuotedUnknownEscape', '\\U'), }) - check_parsing('"\\U"', 0, { + check_parsing('"\\U"', { -- 0123 ast = { 'DoubleQuotedString(val="U"):0:0:"\\U"', @@ -5217,7 +5286,7 @@ describe('Expressions parser', function() hl('DoubleQuote', '"'), }) - check_parsing('"\\xFX"', 0, { + check_parsing('"\\xFX"', { -- 012345 ast = { 'DoubleQuotedString(val="\\15X"):0:0:"\\xFX"', @@ -5229,7 +5298,7 @@ describe('Expressions parser', function() hl('DoubleQuote', '"'), }) - check_parsing('"\\XFX"', 0, { + check_parsing('"\\XFX"', { -- 012345 ast = { 'DoubleQuotedString(val="\\15X"):0:0:"\\XFX"', @@ -5241,7 +5310,7 @@ describe('Expressions parser', function() hl('DoubleQuote', '"'), }) - check_parsing('"\\xX"', 0, { + check_parsing('"\\xX"', { -- 01234 ast = { 'DoubleQuotedString(val="xX"):0:0:"\\xX"', @@ -5253,7 +5322,7 @@ describe('Expressions parser', function() hl('DoubleQuote', '"'), }) - check_parsing('"\\XX"', 0, { + check_parsing('"\\XX"', { -- 01234 ast = { 'DoubleQuotedString(val="XX"):0:0:"\\XX"', @@ -5265,7 +5334,7 @@ describe('Expressions parser', function() hl('DoubleQuote', '"'), }) - check_parsing('"\\uX"', 0, { + check_parsing('"\\uX"', { -- 01234 ast = { 'DoubleQuotedString(val="uX"):0:0:"\\uX"', @@ -5277,7 +5346,7 @@ describe('Expressions parser', function() hl('DoubleQuote', '"'), }) - check_parsing('"\\UX"', 0, { + check_parsing('"\\UX"', { -- 01234 ast = { 'DoubleQuotedString(val="UX"):0:0:"\\UX"', @@ -5289,7 +5358,7 @@ describe('Expressions parser', function() hl('DoubleQuote', '"'), }) - check_parsing('"\\x0X"', 0, { + check_parsing('"\\x0X"', { -- 012345 ast = { 'DoubleQuotedString(val="\\0X"):0:0:"\\x0X"', @@ -5301,7 +5370,7 @@ describe('Expressions parser', function() hl('DoubleQuote', '"'), }) - check_parsing('"\\X0X"', 0, { + check_parsing('"\\X0X"', { -- 012345 ast = { 'DoubleQuotedString(val="\\0X"):0:0:"\\X0X"', @@ -5313,7 +5382,7 @@ describe('Expressions parser', function() hl('DoubleQuote', '"'), }) - check_parsing('"\\u0X"', 0, { + check_parsing('"\\u0X"', { -- 012345 ast = { 'DoubleQuotedString(val="\\0X"):0:0:"\\u0X"', @@ -5325,7 +5394,7 @@ describe('Expressions parser', function() hl('DoubleQuote', '"'), }) - check_parsing('"\\U0X"', 0, { + check_parsing('"\\U0X"', { -- 012345 ast = { 'DoubleQuotedString(val="\\0X"):0:0:"\\U0X"', @@ -5337,7 +5406,7 @@ describe('Expressions parser', function() hl('DoubleQuote', '"'), }) - check_parsing('"\\x00X"', 0, { + check_parsing('"\\x00X"', { -- 0123456 ast = { 'DoubleQuotedString(val="\\0X"):0:0:"\\x00X"', @@ -5349,7 +5418,7 @@ describe('Expressions parser', function() hl('DoubleQuote', '"'), }) - check_parsing('"\\X00X"', 0, { + check_parsing('"\\X00X"', { -- 0123456 ast = { 'DoubleQuotedString(val="\\0X"):0:0:"\\X00X"', @@ -5361,7 +5430,7 @@ describe('Expressions parser', function() hl('DoubleQuote', '"'), }) - check_parsing('"\\u00X"', 0, { + check_parsing('"\\u00X"', { -- 0123456 ast = { 'DoubleQuotedString(val="\\0X"):0:0:"\\u00X"', @@ -5373,7 +5442,7 @@ describe('Expressions parser', function() hl('DoubleQuote', '"'), }) - check_parsing('"\\U00X"', 0, { + check_parsing('"\\U00X"', { -- 0123456 ast = { 'DoubleQuotedString(val="\\0X"):0:0:"\\U00X"', @@ -5385,7 +5454,7 @@ describe('Expressions parser', function() hl('DoubleQuote', '"'), }) - check_parsing('"\\u000X"', 0, { + check_parsing('"\\u000X"', { -- 01234567 ast = { 'DoubleQuotedString(val="\\0X"):0:0:"\\u000X"', @@ -5397,7 +5466,7 @@ describe('Expressions parser', function() hl('DoubleQuote', '"'), }) - check_parsing('"\\U000X"', 0, { + check_parsing('"\\U000X"', { -- 01234567 ast = { 'DoubleQuotedString(val="\\0X"):0:0:"\\U000X"', @@ -5409,7 +5478,7 @@ describe('Expressions parser', function() hl('DoubleQuote', '"'), }) - check_parsing('"\\u0000X"', 0, { + check_parsing('"\\u0000X"', { -- 012345678 ast = { 'DoubleQuotedString(val="\\0X"):0:0:"\\u0000X"', @@ -5421,7 +5490,7 @@ describe('Expressions parser', function() hl('DoubleQuote', '"'), }) - check_parsing('"\\U0000X"', 0, { + check_parsing('"\\U0000X"', { -- 012345678 ast = { 'DoubleQuotedString(val="\\0X"):0:0:"\\U0000X"', @@ -5433,7 +5502,7 @@ describe('Expressions parser', function() hl('DoubleQuote', '"'), }) - check_parsing('"\\U00000X"', 0, { + check_parsing('"\\U00000X"', { -- 0123456789 ast = { 'DoubleQuotedString(val="\\0X"):0:0:"\\U00000X"', @@ -5445,7 +5514,7 @@ describe('Expressions parser', function() hl('DoubleQuote', '"'), }) - check_parsing('"\\U000000X"', 0, { + check_parsing('"\\U000000X"', { -- 01234567890 -- 0 1 ast = { @@ -5458,7 +5527,7 @@ describe('Expressions parser', function() hl('DoubleQuote', '"'), }) - check_parsing('"\\U0000000X"', 0, { + check_parsing('"\\U0000000X"', { -- 012345678901 -- 0 1 ast = { @@ -5471,7 +5540,7 @@ describe('Expressions parser', function() hl('DoubleQuote', '"'), }) - check_parsing('"\\U00000000X"', 0, { + check_parsing('"\\U00000000X"', { -- 0123456789012 -- 0 1 ast = { @@ -5484,7 +5553,7 @@ describe('Expressions parser', function() hl('DoubleQuote', '"'), }) - check_parsing('"\\x000X"', 0, { + check_parsing('"\\x000X"', { -- 01234567 ast = { 'DoubleQuotedString(val="\\0000X"):0:0:"\\x000X"', @@ -5496,7 +5565,7 @@ describe('Expressions parser', function() hl('DoubleQuote', '"'), }) - check_parsing('"\\X000X"', 0, { + check_parsing('"\\X000X"', { -- 01234567 ast = { 'DoubleQuotedString(val="\\0000X"):0:0:"\\X000X"', @@ -5508,7 +5577,7 @@ describe('Expressions parser', function() hl('DoubleQuote', '"'), }) - check_parsing('"\\u00000X"', 0, { + check_parsing('"\\u00000X"', { -- 0123456789 ast = { 'DoubleQuotedString(val="\\0000X"):0:0:"\\u00000X"', @@ -5520,7 +5589,7 @@ describe('Expressions parser', function() hl('DoubleQuote', '"'), }) - check_parsing('"\\U000000000X"', 0, { + check_parsing('"\\U000000000X"', { -- 01234567890123 -- 0 1 ast = { @@ -5533,7 +5602,7 @@ describe('Expressions parser', function() hl('DoubleQuote', '"'), }) - check_parsing('"\\0"', 0, { + check_parsing('"\\0"', { -- 0123 ast = { 'DoubleQuotedString(val="\\0"):0:0:"\\0"', @@ -5544,7 +5613,7 @@ describe('Expressions parser', function() hl('DoubleQuote', '"'), }) - check_parsing('"\\00"', 0, { + check_parsing('"\\00"', { -- 01234 ast = { 'DoubleQuotedString(val="\\0"):0:0:"\\00"', @@ -5555,7 +5624,7 @@ describe('Expressions parser', function() hl('DoubleQuote', '"'), }) - check_parsing('"\\000"', 0, { + check_parsing('"\\000"', { -- 012345 ast = { 'DoubleQuotedString(val="\\0"):0:0:"\\000"', @@ -5566,7 +5635,7 @@ describe('Expressions parser', function() hl('DoubleQuote', '"'), }) - check_parsing('"\\0000"', 0, { + check_parsing('"\\0000"', { -- 0123456 ast = { 'DoubleQuotedString(val="\\0000"):0:0:"\\0000"', @@ -5578,7 +5647,7 @@ describe('Expressions parser', function() hl('DoubleQuote', '"'), }) - check_parsing('"\\8"', 0, { + check_parsing('"\\8"', { -- 0123 ast = { 'DoubleQuotedString(val="8"):0:0:"\\8"', @@ -5589,7 +5658,7 @@ describe('Expressions parser', function() hl('DoubleQuote', '"'), }) - check_parsing('"\\08"', 0, { + check_parsing('"\\08"', { -- 01234 ast = { 'DoubleQuotedString(val="\\0008"):0:0:"\\08"', @@ -5601,7 +5670,7 @@ describe('Expressions parser', function() hl('DoubleQuote', '"'), }) - check_parsing('"\\008"', 0, { + check_parsing('"\\008"', { -- 012345 ast = { 'DoubleQuotedString(val="\\0008"):0:0:"\\008"', @@ -5613,7 +5682,7 @@ describe('Expressions parser', function() hl('DoubleQuote', '"'), }) - check_parsing('"\\0008"', 0, { + check_parsing('"\\0008"', { -- 0123456 ast = { 'DoubleQuotedString(val="\\0008"):0:0:"\\0008"', @@ -5625,7 +5694,7 @@ describe('Expressions parser', function() hl('DoubleQuote', '"'), }) - check_parsing('"\\777"', 0, { + check_parsing('"\\777"', { -- 012345 ast = { 'DoubleQuotedString(val="\255"):0:0:"\\777"', @@ -5636,7 +5705,7 @@ describe('Expressions parser', function() hl('DoubleQuote', '"'), }) - check_parsing('"\\050"', 0, { + check_parsing('"\\050"', { -- 012345 ast = { 'DoubleQuotedString(val="\40"):0:0:"\\050"', @@ -5647,7 +5716,7 @@ describe('Expressions parser', function() hl('DoubleQuote', '"'), }) - check_parsing('"\\"', 0, { + check_parsing('"\\"', { -- 012345 ast = { 'DoubleQuotedString(val="\\21"):0:0:"\\"', @@ -5658,7 +5727,7 @@ describe('Expressions parser', function() hl('DoubleQuote', '"'), }) - check_parsing('"\\<', 0, { + check_parsing('"\\<', { -- 012 ast = { 'DoubleQuotedString(val="<"):0:0:"\\<', @@ -5672,7 +5741,7 @@ describe('Expressions parser', function() hl('InvalidDoubleQuotedUnknownEscape', '\\<'), }) - check_parsing('"\\<"', 0, { + check_parsing('"\\<"', { -- 0123 ast = { 'DoubleQuotedString(val="<"):0:0:"\\<"', @@ -5683,7 +5752,7 @@ describe('Expressions parser', function() hl('DoubleQuote', '"'), }) - check_parsing('"\\ Date: Mon, 6 Nov 2017 01:15:18 +0300 Subject: api/vim: Add “len” dictionary key MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This allows determining where parsing ended which may be needed for e.g. parsing `:echo` with that API function. --- src/nvim/api/vim.c | 15 +++++++++++++-- test/functional/api/vim_spec.lua | 25 +++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index b12c595cb5..8de37e2cf3 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -922,6 +922,12 @@ typedef kvec_withinit_t(ExprASTConvStackItem, 16) ExprASTConvStack; /// Must contain exactly one "%.*s". /// "arg": String, error message argument. /// +/// "len": Amount of bytes successfully parsed. With flags equal to "" +/// that should be equal to the length of expr string. +/// +/// @note: “Sucessfully parsed” here means “participated in AST +/// creation”, not “till the first error”. +/// /// "ast": actual AST, either nil or a dictionary with the following /// keys: /// @@ -1000,9 +1006,8 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, &pstate, parser_simple_get_line, &plines_p, colors_p); ExprAST east = viml_pexpr_parse(&pstate, pflags); - // FIXME add parse_length key const size_t ret_size = ( - 1 // "ast" + 2 // "ast", "len" + (size_t)(east.err.msg != NULL) // "error" + (size_t)highlight // "highlight" ); @@ -1015,6 +1020,12 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, .key = STATIC_CSTR_TO_STRING("ast"), .value = NIL, }; + ret.items[ret.size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("len"), + .value = INTEGER_OBJ((Integer)(pstate.pos.line == 1 + ? plines[0].size + : pstate.pos.col)), + }; if (east.err.msg != NULL) { Dictionary err_dict = { .items = xmalloc(2 * sizeof(err_dict.items[0])), diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index b904bd2a8f..714b1988fb 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -799,6 +799,9 @@ describe('api', function() if east_api.ast then east_api.ast = {simplify_east_api_node(line, east_api.ast)} end + if east_api.len == #line then + east_api.len = nil + end return east_api end local function simplify_east_hl(line, east_hl) @@ -997,6 +1000,7 @@ describe('api', function() }, { [1] = { ast = { + len = 2, err = REMOVE_THIS, ast = { 'Register(name=a):0:0:@a' @@ -1028,6 +1032,7 @@ describe('api', function() }, { [1] = { ast = { + len = 6, err = REMOVE_THIS, ast = { 'Register(name=a):0:0: @a' @@ -1236,6 +1241,7 @@ describe('api', function() }, { [1] = { ast = { + len = 3, err = REMOVE_THIS, ast = { 'Register(name=a):0:0:@a', @@ -5456,6 +5462,7 @@ describe('api', function() }, { [1] = { ast = { + len = 3, err = REMOVE_THIS, ast = { 'ListLiteral:0:0:[', @@ -7135,6 +7142,7 @@ describe('api', function() }, { [1] = { ast = { + len = 4, err = REMOVE_THIS, ast = { 'Option(scope=0,ident=xxx):0:0:&xxx', @@ -7520,6 +7528,7 @@ describe('api', function() }, { [1] = { ast = { + len = 1, err = REMOVE_THIS, ast = { 'Integer(val=1):0:0:1', @@ -7652,6 +7661,7 @@ describe('api', function() end) it('respects highlight argument', function() eq({ + len = 1, ast = { ivalue = 1, len = 1, @@ -7660,6 +7670,7 @@ describe('api', function() }, }, meths.parse_expression('1', '', false)) eq({ + len = 1, ast = { ivalue = 1, len = 1, @@ -7674,6 +7685,7 @@ describe('api', function() it('works (KLEE tests)', function() check_parsing('\0002&A:\000', { ast = {}, + len = 0, err = { arg = '\0002&A:\0', msg = 'E15: Expected value, got EOC: %.*s', @@ -7682,6 +7694,7 @@ describe('api', function() }, { [2] = { ast = { + len = REMOVE_THIS, ast = { { 'Colon:0:4::', @@ -7711,6 +7724,7 @@ describe('api', function() }, [3] = { ast = { + len = 2, ast = { 'Integer(val=2):0:1:2', }, @@ -7754,6 +7768,7 @@ describe('api', function() check_parsing('|"\\U\\', { -- 01234 ast = {}, + len = 0, err = { arg = '|"\\U\\', msg = 'E15: Expected value, got EOC: %.*s', @@ -7762,6 +7777,7 @@ describe('api', function() }, { [2] = { ast = { + len = REMOVE_THIS, ast = { { 'Or:0:0:|', @@ -7786,6 +7802,7 @@ describe('api', function() check_parsing('|"\\e"', { -- 01234 ast = {}, + len = 0, err = { arg = '|"\\e"', msg = 'E15: Expected value, got EOC: %.*s', @@ -7794,6 +7811,7 @@ describe('api', function() }, { [2] = { ast = { + len = REMOVE_THIS, ast = { { 'Or:0:0:|', @@ -7818,6 +7836,7 @@ describe('api', function() check_parsing('|\029', { -- 01 ast = {}, + len = 0, err = { arg = '|\029', msg = 'E15: Expected value, got EOC: %.*s', @@ -7826,6 +7845,7 @@ describe('api', function() }, { [2] = { ast = { + len = REMOVE_THIS, ast = { { 'Or:0:0:|', @@ -7892,6 +7912,7 @@ describe('api', function() }, { [1] = { ast = { + len = 1, ast = { 'UnknownFigure:0:0:', }, @@ -7917,6 +7938,7 @@ describe('api', function() }, }, }, + len = 2, err = { arg = ':?\000\000\000\000\000\000\000', msg = 'E15: Colon outside of dictionary or ternary operator: %.*s', @@ -7926,6 +7948,9 @@ describe('api', function() hl('InvalidTernary', '?'), }, { [2] = { + ast = { + len = REMOVE_THIS, + }, hl_fs = { [3] = hl('InvalidSpacing', '\0'), [4] = hl('InvalidSpacing', '\0'), -- cgit From 05f775b5f248d922c9539432235738cc53e7edd7 Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 6 Nov 2017 01:57:22 +0300 Subject: viml/parser/expressions: Briefly document some differences --- src/nvim/viml/parser/expressions.c | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index b10952a8ac..998edb1ed4 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -3,6 +3,44 @@ /// VimL expression parser +// Planned incompatibilities (to be included into vim_diff.txt when this parser +// will be an actual part of VimL evaluation process): +// +// 1. Expressions are first fully parsed and only then executed. This means +// that while ":echo [system('touch abc')" will create file "abc" in Vim and +// only then raise syntax error regarding missing comma in list in Neovim +// trying to execute that will immediately raise syntax error regarding +// missing list end without actually executing anything. +// 2. Expressions are first fully parsed, without considering any runtime +// information. This means things like that "d.a" does not change its +// meaning depending on type of "d" (or whether Vim is currently executing or +// skipping). For compatibility reasons the dot thus may either be “concat +// or subscript” operator or just “concat” operator. +// 3. Expressions parser is aware whether it is called for :echo or =. +// This means that while "=1 | 2" is equivalent to "=1" +// because "| 2" part is left to be treated as a command separator and then +// ignored in Neovim it is an error. +// 4. Expressions parser has generally better error reporting. But for +// compatibility reasons most errors have error code E15 while error messages +// are significantly different from Vim’s E15. Also some error codes were +// retired because of being harder to emulate or because of them being +// a result of differences in parsing process: e.g. with ":echo {a, b}" Vim +// will attempt to parse expression as lambda, fail, check whether it is +// a curly-braces-name, fail again, and evaluate that as a dictionary, giving +// error regarding undefined variable "a" (or about missing colon). Neovim +// will not try to evaluate anything here: comma right after an argument name +// means that expression may not be anything, but lambda, so the resulting +// error message will never be about missing variable or colon: it will be +// about missing arrow (or a continuation of argument list). +// 5. Failing to parse expression always gives exactly one error message: no +// more stack of error messages like > +// +// :echo [1, +// E697: Missing end of List ']': +// E15: Invalid expression: [1, +// +// < , just exactly one E697 message. + #include #include #include -- cgit From c85f485aa78352f31421d209176b2ed57b91b86e Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 6 Nov 2017 19:06:24 +0300 Subject: charset: Move vim_str2nr flags from vim.h to charset.h --- src/nvim/charset.c | 2 +- src/nvim/charset.h | 15 +++++++++++++++ src/nvim/vim.h | 7 ------- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/nvim/charset.c b/src/nvim/charset.c index 5aebf90194..980b4ed426 100644 --- a/src/nvim/charset.c +++ b/src/nvim/charset.c @@ -1615,7 +1615,7 @@ bool vim_isblankline(char_u *lbuf) /// hexadecimal, '0' = octal, 'b' or 'B' is binary. When using /// STR2NR_FORCE is always zero. /// @param len Returns the detected length of number. -/// @param what Recognizes what number passed. +/// @param what Recognizes what number passed, @see ChStr2NrFlags. /// @param nptr Returns the signed result. /// @param unptr Returns the unsigned result. /// @param maxlen Max length of string to check. diff --git a/src/nvim/charset.h b/src/nvim/charset.h index c69582c4c6..7198c8d405 100644 --- a/src/nvim/charset.h +++ b/src/nvim/charset.h @@ -15,6 +15,21 @@ ?((int)(uint8_t)(c)) \ :((int)(c))) +/// Flags for vim_str2nr() +typedef enum { + STR2NR_DEC = 0, + STR2NR_BIN = (1 << 0), ///< Allow binary numbers. + STR2NR_OCT = (1 << 1), ///< Allow octal numbers. + STR2NR_HEX = (1 << 2), ///< Allow hexadecimal numbers. + /// Force one of the above variants. + /// + /// STR2NR_FORCE|STR2NR_DEC is actually not different from supplying zero + /// as flags, but still present for completeness. + STR2NR_FORCE = (1 << 3), + /// Recognize all formats vim_str2nr() can recognize. + STR2NR_ALL = STR2NR_BIN | STR2NR_OCT | STR2NR_HEX, +} ChStr2NrFlags; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "charset.h.generated.h" #endif diff --git a/src/nvim/vim.h b/src/nvim/vim.h index 62ffc7433e..096c57450f 100644 --- a/src/nvim/vim.h +++ b/src/nvim/vim.h @@ -28,13 +28,6 @@ /// length of a buffer to store a number in ASCII (64 bits binary + NUL) enum { NUMBUFLEN = 65 }; -// flags for vim_str2nr() -#define STR2NR_BIN 1 -#define STR2NR_OCT 2 -#define STR2NR_HEX 4 -#define STR2NR_ALL (STR2NR_BIN + STR2NR_OCT + STR2NR_HEX) -#define STR2NR_FORCE 8 // only when ONE of the above is used - #define MAX_TYPENR 65535 #define ROOT_UID 0 -- cgit From 42959d0e8f9e779ba4983016b24967bf02abb59f Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 6 Nov 2017 20:15:05 +0300 Subject: unittests: Add tests for vim_str2nr --- test/helpers.lua | 8 + test/unit/charset/vim_str2nr_spec.lua | 294 +++++++++++++++++++++++++++++++++- 2 files changed, 301 insertions(+), 1 deletion(-) diff --git a/test/helpers.lua b/test/helpers.lua index 20fe23821f..16b9818f12 100644 --- a/test/helpers.lua +++ b/test/helpers.lua @@ -310,6 +310,13 @@ local function mergedicts_copy(d1, d2) return ret end +local function updated(d, d2) + for k, v in pairs(d2) do + d[k] = v + end + return d +end + local function concat_tables(...) local ret = {} for i = 1, select('#', ...) do @@ -460,4 +467,5 @@ return { format_luav = format_luav, format_string = format_string, intchar2lua = intchar2lua, + updated = updated, } diff --git a/test/unit/charset/vim_str2nr_spec.lua b/test/unit/charset/vim_str2nr_spec.lua index cfbc77bc2a..9309dc380c 100644 --- a/test/unit/charset/vim_str2nr_spec.lua +++ b/test/unit/charset/vim_str2nr_spec.lua @@ -1 +1,293 @@ --- FIXME +local helpers = require("test.unit.helpers")(after_each) +local global_helpers = require('test.helpers') + +local itp = helpers.gen_itp(it) + +local cimport = helpers.cimport +local ffi = helpers.ffi +local eq = helpers.eq + +local shallowcopy = global_helpers.shallowcopy +local updated = global_helpers.updated + +local lib = cimport('./src/nvim/charset.h') + +local ARGTYPES = { + num = ffi.typeof('varnumber_T[1]'), + unum = ffi.typeof('uvarnumber_T[1]'), + pre = ffi.typeof('int[1]'), + len = ffi.typeof('int[1]'), +} + +local icnt = -42 +local ucnt = 4242 + +local function arginit(arg) + if arg == 'unum' then + ucnt = ucnt + 1 + return ARGTYPES[arg]({ucnt}) + else + icnt = icnt - 1 + return ARGTYPES[arg]({icnt}) + end +end + +local function test_vim_str2nr(s, what, exp, maxlen) + local comb = {[''] = {}} + for k, _ in pairs(exp) do + for ck, cv in pairs(comb) do + comb[ck .. ',' .. k] = updated(shallowcopy(cv), { [k] = arginit(k) }) + end + end + maxlen = maxlen or #s + for _, cv in pairs(comb) do + lib.vim_str2nr(s, cv.pre, cv.len, what, cv.num, cv.unum, maxlen) + for cck, ccv in pairs(cv) do + if exp[cck] ~= tonumber(ccv[0]) then + error(('Failed check (%s = %d) in test (s=%s, w=%u, m=%d): %d'):format( + cck, exp[cck], s, tonumber(what), maxlen, tonumber(ccv[0]) + )) + end + end + end +end + +describe('vim_str2nr()', function() + itp('works fine when it has nothing to do', function() + test_vim_str2nr('', 0, {len = 0, num = 0, unum = 0, pre = 0}, 0) + test_vim_str2nr('', lib.STR2NR_ALL, {len = 0, num = 0, unum = 0, pre = 0}, 0) + test_vim_str2nr('', lib.STR2NR_BIN, {len = 0, num = 0, unum = 0, pre = 0}, 0) + test_vim_str2nr('', lib.STR2NR_OCT, {len = 0, num = 0, unum = 0, pre = 0}, 0) + test_vim_str2nr('', lib.STR2NR_HEX, {len = 0, num = 0, unum = 0, pre = 0}, 0) + test_vim_str2nr('', lib.STR2NR_FORCE + lib.STR2NR_DEC, {len = 0, num = 0, unum = 0, pre = 0}, 0) + test_vim_str2nr('', lib.STR2NR_FORCE + lib.STR2NR_BIN, {len = 0, num = 0, unum = 0, pre = 0}, 0) + test_vim_str2nr('', lib.STR2NR_FORCE + lib.STR2NR_OCT, {len = 0, num = 0, unum = 0, pre = 0}, 0) + test_vim_str2nr('', lib.STR2NR_FORCE + lib.STR2NR_HEX, {len = 0, num = 0, unum = 0, pre = 0}, 0) + end) + itp('works with decimal numbers', function() + for _, flags in ipairs({ + 0, + lib.STR2NR_BIN, + lib.STR2NR_OCT, + lib.STR2NR_HEX, + lib.STR2NR_BIN + lib.STR2NR_OCT, + lib.STR2NR_BIN + lib.STR2NR_HEX, + lib.STR2NR_OCT + lib.STR2NR_HEX, + lib.STR2NR_ALL, + lib.STR2NR_FORCE + lib.STR2NR_DEC, + }) do + -- Check that all digits are recognized + test_vim_str2nr( '12345', flags, {len = 5, num = 12345, unum = 12345, pre = 0}, 0) + test_vim_str2nr( '67890', flags, {len = 5, num = 67890, unum = 67890, pre = 0}, 0) + test_vim_str2nr( '12345A', flags, {len = 5, num = 12345, unum = 12345, pre = 0}, 0) + test_vim_str2nr( '67890A', flags, {len = 5, num = 67890, unum = 67890, pre = 0}, 0) + + test_vim_str2nr( '42', flags, {len = 2, num = 42, unum = 42, pre = 0}, 0) + test_vim_str2nr( '42', flags, {len = 1, num = 4, unum = 4, pre = 0}, 1) + test_vim_str2nr( '42', flags, {len = 2, num = 42, unum = 42, pre = 0}, 2) + test_vim_str2nr( '42', flags, {len = 2, num = 42, unum = 42, pre = 0}, 3) -- includes NUL byte in maxlen + + test_vim_str2nr( '42x', flags, {len = 2, num = 42, unum = 42, pre = 0}, 0) + test_vim_str2nr( '42x', flags, {len = 2, num = 42, unum = 42, pre = 0}, 3) + + test_vim_str2nr('-42', flags, {len = 3, num = -42, unum = 42, pre = 0}, 3) + test_vim_str2nr('-42', flags, {len = 1, num = 0, unum = 0, pre = 0}, 1) + + test_vim_str2nr('-42x', flags, {len = 3, num = -42, unum = 42, pre = 0}, 0) + test_vim_str2nr('-42x', flags, {len = 3, num = -42, unum = 42, pre = 0}, 4) + end + end) + itp('works with binary numbers', function() + for _, flags in ipairs({ + lib.STR2NR_BIN, + lib.STR2NR_BIN + lib.STR2NR_OCT, + lib.STR2NR_BIN + lib.STR2NR_HEX, + lib.STR2NR_ALL, + lib.STR2NR_FORCE + lib.STR2NR_BIN, + }) do + local bin + local BIN + if flags > lib.STR2NR_FORCE then + bin = 0 + BIN = 0 + else + bin = ('b'):byte() + BIN = ('B'):byte() + end + + test_vim_str2nr( '0b101', flags, {len = 5, num = 5, unum = 5, pre = bin}, 0) + test_vim_str2nr( '0b101', flags, {len = 1, num = 0, unum = 0, pre = 0 }, 1) + test_vim_str2nr( '0b101', flags, {len = 1, num = 0, unum = 0, pre = 0 }, 2) + test_vim_str2nr( '0b101', flags, {len = 3, num = 1, unum = 1, pre = bin}, 3) + test_vim_str2nr( '0b101', flags, {len = 4, num = 2, unum = 2, pre = bin}, 4) + test_vim_str2nr( '0b101', flags, {len = 5, num = 5, unum = 5, pre = bin}, 5) + test_vim_str2nr( '0b101', flags, {len = 5, num = 5, unum = 5, pre = bin}, 6) + + test_vim_str2nr( '0b1012', flags, {len = 5, num = 5, unum = 5, pre = bin}, 0) + test_vim_str2nr( '0b1012', flags, {len = 5, num = 5, unum = 5, pre = bin}, 6) + + test_vim_str2nr('-0b101', flags, {len = 6, num = -5, unum = 5, pre = bin}, 0) + test_vim_str2nr('-0b101', flags, {len = 1, num = 0, unum = 0, pre = 0 }, 1) + test_vim_str2nr('-0b101', flags, {len = 2, num = 0, unum = 0, pre = 0 }, 2) + test_vim_str2nr('-0b101', flags, {len = 2, num = 0, unum = 0, pre = 0 }, 3) + test_vim_str2nr('-0b101', flags, {len = 4, num = -1, unum = 1, pre = bin}, 4) + test_vim_str2nr('-0b101', flags, {len = 5, num = -2, unum = 2, pre = bin}, 5) + test_vim_str2nr('-0b101', flags, {len = 6, num = -5, unum = 5, pre = bin}, 6) + test_vim_str2nr('-0b101', flags, {len = 6, num = -5, unum = 5, pre = bin}, 7) + + test_vim_str2nr('-0b1012', flags, {len = 6, num = -5, unum = 5, pre = bin}, 0) + test_vim_str2nr('-0b1012', flags, {len = 6, num = -5, unum = 5, pre = bin}, 7) + + test_vim_str2nr( '0B101', flags, {len = 5, num = 5, unum = 5, pre = BIN}, 0) + test_vim_str2nr( '0B101', flags, {len = 1, num = 0, unum = 0, pre = 0 }, 1) + test_vim_str2nr( '0B101', flags, {len = 1, num = 0, unum = 0, pre = 0 }, 2) + test_vim_str2nr( '0B101', flags, {len = 3, num = 1, unum = 1, pre = BIN}, 3) + test_vim_str2nr( '0B101', flags, {len = 4, num = 2, unum = 2, pre = BIN}, 4) + test_vim_str2nr( '0B101', flags, {len = 5, num = 5, unum = 5, pre = BIN}, 5) + test_vim_str2nr( '0B101', flags, {len = 5, num = 5, unum = 5, pre = BIN}, 6) + + test_vim_str2nr( '0B1012', flags, {len = 5, num = 5, unum = 5, pre = BIN}, 0) + test_vim_str2nr( '0B1012', flags, {len = 5, num = 5, unum = 5, pre = BIN}, 6) + + test_vim_str2nr('-0B101', flags, {len = 6, num = -5, unum = 5, pre = BIN}, 0) + test_vim_str2nr('-0B101', flags, {len = 1, num = 0, unum = 0, pre = 0 }, 1) + test_vim_str2nr('-0B101', flags, {len = 2, num = 0, unum = 0, pre = 0 }, 2) + test_vim_str2nr('-0B101', flags, {len = 2, num = 0, unum = 0, pre = 0 }, 3) + test_vim_str2nr('-0B101', flags, {len = 4, num = -1, unum = 1, pre = BIN}, 4) + test_vim_str2nr('-0B101', flags, {len = 5, num = -2, unum = 2, pre = BIN}, 5) + test_vim_str2nr('-0B101', flags, {len = 6, num = -5, unum = 5, pre = BIN}, 6) + test_vim_str2nr('-0B101', flags, {len = 6, num = -5, unum = 5, pre = BIN}, 7) + + test_vim_str2nr('-0B1012', flags, {len = 6, num = -5, unum = 5, pre = BIN}, 0) + test_vim_str2nr('-0B1012', flags, {len = 6, num = -5, unum = 5, pre = BIN}, 7) + + if flags > lib.STR2NR_FORCE then + test_vim_str2nr('-101', flags, {len = 4, num = -5, unum = 5, pre = 0}, 0) + end + end + end) + itp('works with octal numbers', function() + for _, flags in ipairs({ + lib.STR2NR_OCT, + lib.STR2NR_OCT + lib.STR2NR_BIN, + lib.STR2NR_OCT + lib.STR2NR_HEX, + lib.STR2NR_ALL, + lib.STR2NR_FORCE + lib.STR2NR_OCT, + }) do + local oct + if flags > lib.STR2NR_FORCE then + oct = 0 + else + oct = ('0'):byte() + end + + -- Check that all digits are recognized + test_vim_str2nr( '012345670', flags, {len = 9, num = 2739128, unum = 2739128, pre = oct}, 0) + + test_vim_str2nr( '054', flags, {len = 3, num = 44, unum = 44, pre = oct}, 0) + test_vim_str2nr( '054', flags, {len = 1, num = 0, unum = 0, pre = 0 }, 1) + test_vim_str2nr( '054', flags, {len = 2, num = 5, unum = 5, pre = oct}, 2) + test_vim_str2nr( '054', flags, {len = 3, num = 44, unum = 44, pre = oct}, 3) + test_vim_str2nr( '0548', flags, {len = 3, num = 44, unum = 44, pre = oct}, 3) + test_vim_str2nr( '054', flags, {len = 3, num = 44, unum = 44, pre = oct}, 4) + + test_vim_str2nr( '054x', flags, {len = 3, num = 44, unum = 44, pre = oct}, 4) + test_vim_str2nr( '054x', flags, {len = 3, num = 44, unum = 44, pre = oct}, 0) + + test_vim_str2nr('-054', flags, {len = 4, num = -44, unum = 44, pre = oct}, 0) + test_vim_str2nr('-054', flags, {len = 1, num = 0, unum = 0, pre = 0 }, 1) + test_vim_str2nr('-054', flags, {len = 2, num = 0, unum = 0, pre = 0 }, 2) + test_vim_str2nr('-054', flags, {len = 3, num = -5, unum = 5, pre = oct}, 3) + test_vim_str2nr('-054', flags, {len = 4, num = -44, unum = 44, pre = oct}, 4) + test_vim_str2nr('-0548', flags, {len = 4, num = -44, unum = 44, pre = oct}, 4) + test_vim_str2nr('-054', flags, {len = 4, num = -44, unum = 44, pre = oct}, 5) + + test_vim_str2nr('-054x', flags, {len = 4, num = -44, unum = 44, pre = oct}, 5) + test_vim_str2nr('-054x', flags, {len = 4, num = -44, unum = 44, pre = oct}, 0) + + if flags > lib.STR2NR_FORCE then + test_vim_str2nr('-54', flags, {len = 3, num = -44, unum = 44, pre = 0}, 0) + test_vim_str2nr('-0548', flags, {len = 4, num = -44, unum = 44, pre = 0}, 5) + test_vim_str2nr('-0548', flags, {len = 4, num = -44, unum = 44, pre = 0}, 0) + else + test_vim_str2nr('-0548', flags, {len = 5, num = -548, unum = 548, pre = 0}, 5) + test_vim_str2nr('-0548', flags, {len = 5, num = -548, unum = 548, pre = 0}, 0) + end + end + end) + itp('works with hexadecimal numbers', function() + for _, flags in ipairs({ + lib.STR2NR_HEX, + lib.STR2NR_HEX + lib.STR2NR_BIN, + lib.STR2NR_HEX + lib.STR2NR_OCT, + lib.STR2NR_ALL, + lib.STR2NR_FORCE + lib.STR2NR_HEX, + }) do + local hex + local HEX + if flags > lib.STR2NR_FORCE then + hex = 0 + HEX = 0 + else + hex = ('x'):byte() + HEX = ('X'):byte() + end + + -- Check that all digits are recognized + test_vim_str2nr('0x12345', flags, {len = 7, num = 74565, unum = 74565, pre = hex}, 0) + test_vim_str2nr('0x67890', flags, {len = 7, num = 424080, unum = 424080, pre = hex}, 0) + test_vim_str2nr('0xABCDEF', flags, {len = 8, num = 11259375, unum = 11259375, pre = hex}, 0) + test_vim_str2nr('0xabcdef', flags, {len = 8, num = 11259375, unum = 11259375, pre = hex}, 0) + + test_vim_str2nr( '0x101', flags, {len = 5, num = 257, unum =257, pre = hex}, 0) + test_vim_str2nr( '0x101', flags, {len = 1, num = 0, unum = 0, pre = 0 }, 1) + test_vim_str2nr( '0x101', flags, {len = 1, num = 0, unum = 0, pre = 0 }, 2) + test_vim_str2nr( '0x101', flags, {len = 3, num = 1, unum = 1, pre = hex}, 3) + test_vim_str2nr( '0x101', flags, {len = 4, num = 16, unum = 16, pre = hex}, 4) + test_vim_str2nr( '0x101', flags, {len = 5, num = 257, unum =257, pre = hex}, 5) + test_vim_str2nr( '0x101', flags, {len = 5, num = 257, unum =257, pre = hex}, 6) + + test_vim_str2nr( '0x101G', flags, {len = 5, num = 257, unum =257, pre = hex}, 0) + test_vim_str2nr( '0x101G', flags, {len = 5, num = 257, unum =257, pre = hex}, 6) + + test_vim_str2nr('-0x101', flags, {len = 6, num =-257, unum =257, pre = hex}, 0) + test_vim_str2nr('-0x101', flags, {len = 1, num = 0, unum = 0, pre = 0 }, 1) + test_vim_str2nr('-0x101', flags, {len = 2, num = 0, unum = 0, pre = 0 }, 2) + test_vim_str2nr('-0x101', flags, {len = 2, num = 0, unum = 0, pre = 0 }, 3) + test_vim_str2nr('-0x101', flags, {len = 4, num = -1, unum = 1, pre = hex}, 4) + test_vim_str2nr('-0x101', flags, {len = 5, num = -16, unum = 16, pre = hex}, 5) + test_vim_str2nr('-0x101', flags, {len = 6, num =-257, unum =257, pre = hex}, 6) + test_vim_str2nr('-0x101', flags, {len = 6, num =-257, unum =257, pre = hex}, 7) + + test_vim_str2nr('-0x101G', flags, {len = 6, num =-257, unum =257, pre = hex}, 0) + test_vim_str2nr('-0x101G', flags, {len = 6, num =-257, unum =257, pre = hex}, 7) + + test_vim_str2nr( '0X101', flags, {len = 5, num = 257, unum =257, pre = HEX}, 0) + test_vim_str2nr( '0X101', flags, {len = 1, num = 0, unum = 0, pre = 0 }, 1) + test_vim_str2nr( '0X101', flags, {len = 1, num = 0, unum = 0, pre = 0 }, 2) + test_vim_str2nr( '0X101', flags, {len = 3, num = 1, unum = 1, pre = HEX}, 3) + test_vim_str2nr( '0X101', flags, {len = 4, num = 16, unum = 16, pre = HEX}, 4) + test_vim_str2nr( '0X101', flags, {len = 5, num = 257, unum =257, pre = HEX}, 5) + test_vim_str2nr( '0X101', flags, {len = 5, num = 257, unum =257, pre = HEX}, 6) + + test_vim_str2nr( '0X101G', flags, {len = 5, num = 257, unum =257, pre = HEX}, 0) + test_vim_str2nr( '0X101G', flags, {len = 5, num = 257, unum =257, pre = HEX}, 6) + + test_vim_str2nr('-0X101', flags, {len = 6, num =-257, unum =257, pre = HEX}, 0) + test_vim_str2nr('-0X101', flags, {len = 1, num = 0, unum = 0, pre = 0 }, 1) + test_vim_str2nr('-0X101', flags, {len = 2, num = 0, unum = 0, pre = 0 }, 2) + test_vim_str2nr('-0X101', flags, {len = 2, num = 0, unum = 0, pre = 0 }, 3) + test_vim_str2nr('-0X101', flags, {len = 4, num = -1, unum = 1, pre = HEX}, 4) + test_vim_str2nr('-0X101', flags, {len = 5, num = -16, unum = 16, pre = HEX}, 5) + test_vim_str2nr('-0X101', flags, {len = 6, num =-257, unum =257, pre = HEX}, 6) + test_vim_str2nr('-0X101', flags, {len = 6, num =-257, unum =257, pre = HEX}, 7) + + test_vim_str2nr('-0X101G', flags, {len = 6, num =-257, unum =257, pre = HEX}, 0) + test_vim_str2nr('-0X101G', flags, {len = 6, num =-257, unum =257, pre = HEX}, 7) + + if flags > lib.STR2NR_FORCE then + test_vim_str2nr('-101', flags, {len = 4, num = -257, unum = 257, pre = 0}, 0) + end + end + end) +end) -- cgit From f2660bee6aca35be3d0ddb1d225784476c13cd27 Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 6 Nov 2017 20:20:31 +0300 Subject: *: Fix some typos found by oni-link --- src/nvim/ex_getln.c | 1 - src/nvim/generators/gen_declarations.lua | 6 +++--- src/nvim/keymap.c | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index f64efe08a1..3b751530e8 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -2501,7 +2501,6 @@ static bool color_cmdline(CmdlineInfo *colored_ccline) can_free_cb = true; } else if (colored_ccline->cmdfirstc == '=') { color_expr_cmdline(colored_ccline, ccline_colors); - can_free_cb = false; } if (!tl_ret || !dgc_ret) { goto color_cmdline_error; diff --git a/src/nvim/generators/gen_declarations.lua b/src/nvim/generators/gen_declarations.lua index 0c73376ba0..065c043557 100755 --- a/src/nvim/generators/gen_declarations.lua +++ b/src/nvim/generators/gen_declarations.lua @@ -169,10 +169,10 @@ Usage: gendeclarations.lua definitions.c static.h non-static.h definitions.i -Generates declarations for a C file defitions.c, putting declarations for +Generates declarations for a C file definitions.c, putting declarations for static functions into static.h and declarations for non-static functions into non-static.h. File `definitions.i' should contain an already preprocessed -version of defintions.c and it is the only one which is actually parsed, +version of definitions.c and it is the only one which is actually parsed, definitions.c is needed only to determine functions from which file out of all functions found in definitions.i are needed. @@ -181,7 +181,7 @@ Additionally uses the following environment variables: NVIM_GEN_DECLARATIONS_LINE_NUMBERS: If set to 1 then all generated declarations receive a comment with file name and line number after the declaration. This may be useful for - debugging gen_declarations script, but not much beyound that with + debugging gen_declarations script, but not much beyond that with configured development environment (i.e. with ctags/cscope/finding definitions with clang/etc). diff --git a/src/nvim/keymap.c b/src/nvim/keymap.c index 0c8e47b02e..aca21c20a5 100644 --- a/src/nvim/keymap.c +++ b/src/nvim/keymap.c @@ -714,7 +714,7 @@ int find_special_key_in_table(int c) /// with "t_" the next two characters are interpreted as /// a termcap name. /// -/// @return Key code or 0 if ton found. +/// @return Key code or 0 if not found. int get_special_key_code(const char_u *name) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { -- cgit From 4aebd00a9eeeb2f56ff53dd4e383825e997ee7be Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 6 Nov 2017 20:28:37 +0300 Subject: *: Fix linter errors --- src/nvim/api/vim.c | 20 ++++++++++---------- src/nvim/viml/parser/expressions.c | 2 +- test/helpers.lua | 6 +++--- test/unit/charset/vim_str2nr_spec.lua | 1 - test/unit/viml/helpers.lua | 1 - 5 files changed, 14 insertions(+), 16 deletions(-) diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 0596c3ebd8..bac19ef363 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -930,7 +930,7 @@ typedef kvec_withinit_t(ExprASTConvStackItem, 16) ExprASTConvStack; /// @note: “Sucessfully parsed” here means “participated in AST /// creation”, not “till the first error”. /// -/// "ast": actual AST, either nil or a dictionary with the following +/// "ast": actual AST, either nil or a dictionary with the following /// keys: /// /// "type": node type, one of the value names from ExprASTNodeType @@ -1009,10 +1009,10 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, ExprAST east = viml_pexpr_parse(&pstate, pflags); const size_t ret_size = ( - 2 // "ast", "len" - + (size_t)(east.err.msg != NULL) // "error" - + (size_t)highlight // "highlight" - ); + 2 // "ast", "len" + + (size_t)(east.err.msg != NULL) // "error" + + (size_t)highlight // "highlight" + + 0); Dictionary ret = { .items = xmalloc(ret_size * sizeof(ret.items[0])), .size = 0, @@ -1242,12 +1242,12 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, ret_node->items[ret_node->size++] = (KeyValuePair) { .key = STATIC_CSTR_TO_STRING("cmp_type"), .value = STRING_OBJ(cstr_to_string( - eltkn_cmp_type_tab[node->data.cmp.type])), + eltkn_cmp_type_tab[node->data.cmp.type])), }; ret_node->items[ret_node->size++] = (KeyValuePair) { .key = STATIC_CSTR_TO_STRING("ccs_strategy"), .value = STRING_OBJ(cstr_to_string( - ccs_tab[node->data.cmp.ccs])), + ccs_tab[node->data.cmp.ccs])), }; ret_node->items[ret_node->size++] = (KeyValuePair) { .key = STATIC_CSTR_TO_STRING("invert"), @@ -1266,9 +1266,9 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, ret_node->items[ret_node->size++] = (KeyValuePair) { .key = STATIC_CSTR_TO_STRING("ivalue"), .value = INTEGER_OBJ((Integer)( - node->data.num.value > API_INTEGER_MAX - ? API_INTEGER_MAX - : (Integer)node->data.num.value)), + node->data.num.value > API_INTEGER_MAX + ? API_INTEGER_MAX + : (Integer)node->data.num.value)), }; break; } diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index 998edb1ed4..71eda2cdc1 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -22,7 +22,7 @@ // ignored in Neovim it is an error. // 4. Expressions parser has generally better error reporting. But for // compatibility reasons most errors have error code E15 while error messages -// are significantly different from Vim’s E15. Also some error codes were +// are significantly different from Vim’s E15. Also some error codes were // retired because of being harder to emulate or because of them being // a result of differences in parsing process: e.g. with ":echo {a, b}" Vim // will attempt to parse expression as lambda, fail, check whether it is diff --git a/test/helpers.lua b/test/helpers.lua index 16b9818f12..d24fae745b 100644 --- a/test/helpers.lua +++ b/test/helpers.lua @@ -301,10 +301,10 @@ local function mergedicts_copy(d1, d2) for k, v in pairs(d2) do if d2[k] == REMOVE_THIS then ret[k] = nil - elseif type(d1[k]) == 'table' and type(d2[k]) == 'table' then - ret[k] = mergedicts_copy(d1[k], d2[k]) + elseif type(d1[k]) == 'table' and type(v) == 'table' then + ret[k] = mergedicts_copy(d1[k], v) else - ret[k] = d2[k] + ret[k] = v end end return ret diff --git a/test/unit/charset/vim_str2nr_spec.lua b/test/unit/charset/vim_str2nr_spec.lua index 9309dc380c..22504649f6 100644 --- a/test/unit/charset/vim_str2nr_spec.lua +++ b/test/unit/charset/vim_str2nr_spec.lua @@ -5,7 +5,6 @@ local itp = helpers.gen_itp(it) local cimport = helpers.cimport local ffi = helpers.ffi -local eq = helpers.eq local shallowcopy = global_helpers.shallowcopy local updated = global_helpers.updated diff --git a/test/unit/viml/helpers.lua b/test/unit/viml/helpers.lua index aeb886a66f..9d2d7b61c7 100644 --- a/test/unit/viml/helpers.lua +++ b/test/unit/viml/helpers.lua @@ -112,7 +112,6 @@ return { pline2lua = pline2lua, pstate_str = pstate_str, new_pstate = new_pstate, - intchar2lua = intchar2lua, conv_cmp_type = conv_cmp_type, pstate_set_str = pstate_set_str, } -- cgit From bbb21e5dd3891727c272ffd3aa4ce2a4841a1f0b Mon Sep 17 00:00:00 2001 From: ZyX Date: Sat, 11 Nov 2017 23:50:37 +0300 Subject: unittests: Add a way to show some custom messages only when crashed --- test/unit/helpers.lua | 17 ++++++++++++++++- test/unit/viml/expressions/parser_spec.lua | 2 ++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/test/unit/helpers.lua b/test/unit/helpers.lua index 96aa505739..2c148630dd 100644 --- a/test/unit/helpers.lua +++ b/test/unit/helpers.lua @@ -529,9 +529,13 @@ local hook_numlen = 5 local hook_msglen = 1 + 1 + 1 + (1 + hook_fnamelen) + (1 + hook_sfnamelen) + (1 + hook_numlen) + 1 local tracehelp = dedent([[ + Trace: either in the format described below or custom debug output starting + with `>`. Latter lines still have the same width in byte. + ┌ Trace type: _r_eturn from function , function _c_all, _l_ine executed, │ _t_ail return, _C_ount (should not actually appear), - │ _s_aved from previous run for reference. + │ _s_aved from previous run for reference, _>_ for custom debug + │ output. │┏ Function type: _L_ua function, _C_ function, _m_ain part of chunk, │┃ function that did _t_ail call. │┃┌ Function name type: _g_lobal, _l_ocal, _m_ethod, _f_ield, _u_pvalue, @@ -629,7 +633,17 @@ end local trace_end_msg = ('E%s\n'):format((' '):rep(hook_msglen - 2)) +local _debug_log + +local debug_log = only_separate(function(...) + return _debug_log(...) +end) + local function itp_child(wr, func) + _debug_log = function(s) + s = s:sub(1, hook_msglen - 2) + sc.write(wr, '>' .. s .. (' '):rep(hook_msglen - 2 - #s) .. '\n') + end init() collectgarbage('stop') child_sethook(wr) @@ -845,6 +859,7 @@ local module = { make_enum_conv_tab = make_enum_conv_tab, ptr2addr = ptr2addr, ptr2key = ptr2key, + debug_log = debug_log, } return function() return module diff --git a/test/unit/viml/expressions/parser_spec.lua b/test/unit/viml/expressions/parser_spec.lua index e5d0f2b84c..cfc9fe95ac 100644 --- a/test/unit/viml/expressions/parser_spec.lua +++ b/test/unit/viml/expressions/parser_spec.lua @@ -8,6 +8,7 @@ local child_call_once = helpers.child_call_once local alloc_log_new = helpers.alloc_log_new local kvi_destroy = helpers.kvi_destroy local conv_enum = helpers.conv_enum +local debug_log = helpers.debug_log local ptr2key = helpers.ptr2key local cimport = helpers.cimport local ffi = helpers.ffi @@ -233,6 +234,7 @@ describe('Expressions parser', function() local function check_parsing(str, exp_ast, exp_highlighting_fs, nz_flags_exps) nz_flags_exps = nz_flags_exps or {} for _, flags in ipairs({0, 1, 2, 3}) do + debug_log(('Running test case (%s, %u)'):format(str, flags)) local err, msg = pcall(function() if os.getenv('NVIM_TEST_PARSER_SPEC_PRINT_TEST_CASE') == '1' then print(str, flags) -- cgit From 1aa6276c29d562a6287519e6755a613eabca5c31 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 12 Nov 2017 00:03:45 +0300 Subject: viml/parser/expressions: Replace lambda-specific WantedNode entries This way code will be easier to adapt to handling (partially) non-expressions like :let lvalue part or :function definitions, and that would be needed in the future both for proper completion support and for the Ex commands parser. --- src/nvim/viml/parser/expressions.c | 101 +++++++++++++++++++------------------ 1 file changed, 52 insertions(+), 49 deletions(-) diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index 71eda2cdc1..e23c58bfd1 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -73,12 +73,21 @@ typedef enum { /// For unrestricted expressions as well, implies that top item in AST stack /// points to NULL. kENodeValue, - /// Argument: only allows simple argument names. - kENodeArgument, - /// Argument separator: only allows commas. - kENodeArgumentSeparator, } ExprASTWantedNode; +/// Parse type: what is being parsed currently +typedef enum { + /// Parsing regular VimL expression + kEPTExpr = 0, + /// Parsing lambda arguments + /// + /// Just like parsing function arguments, but it is valid to be ended with an + /// arrow only. + kEPTLambdaArguments, +} ExprASTParseType; + +typedef kvec_withinit_t(ExprASTParseType, 4) ExprASTParseTypeStack; + /// Operator priority level typedef enum { kEOpLvlInvalid = 0, @@ -1238,9 +1247,7 @@ static bool viml_pexpr_handle_bop(const ParserState *const pstate, ret = false; } } - *want_node_p = (*want_node_p == kENodeArgumentSeparator - ? kENodeArgument - : kENodeValue); + *want_node_p = kENodeValue; return ret; } @@ -1790,8 +1797,6 @@ static void parse_quoted_string(ParserState *const pstate, static const int want_node_to_lexer_flags[] = { [kENodeValue] = kELFlagIsNotCmp, [kENodeOperator] = kELFlagForbidScope, - [kENodeArgument] = kELFlagIsNotCmp, - [kENodeArgumentSeparator] = kELFlagForbidScope, }; /// Number of characters to highlight as NumberPrefix depending on the base @@ -1827,12 +1832,13 @@ ExprAST viml_pexpr_parse(ParserState *const pstate, const int flags) // (list start) last stack item contains NULL. Otherwise last stack item is // supposed to contain last “finished” value: e.g. "1" or "+(1, 1)" (node // representing "1+1"). - // - // Both kENodeValue and kENodeArgument stand for “value” nodes. ExprASTStack ast_stack; kvi_init(ast_stack); kvi_push(ast_stack, &ast.root); ExprASTWantedNode want_node = kENodeValue; + ExprASTParseTypeStack pt_stack; + kvi_init(pt_stack); + kvi_push(pt_stack, kEPTExpr); LexExprToken prev_token = { .type = kExprLexMissing }; bool highlighted_prev_spacing = false; // Lambda node, valid when parsing lambda arguments only. @@ -1884,8 +1890,7 @@ viml_pexpr_parse_process_token: assert(kv_size(ast_stack) >= 1); ExprASTNode *cur_node = NULL; #ifndef NDEBUG - const bool want_value = ( - want_node == kENodeValue || want_node == kENodeArgument); + const bool want_value = (want_node == kENodeValue); assert(want_value == (*top_node_p == NULL)); assert(kv_A(ast_stack, 0) == &ast.root); // Check that stack item i + 1 points to stack items’ i *last* child. @@ -1933,14 +1938,15 @@ viml_pexpr_parse_process_token: // circumstances, and in any case runtime and not parse time errors. (*kv_Z(ast_stack, 1))->type = kExprNodeConcat; } - if ((want_node == kENodeArgumentSeparator - && tok_type != kExprLexComma - && tok_type != kExprLexArrow) - || (want_node == kENodeArgument - && !(cur_token.type == kExprLexPlainIdentifier - && cur_token.data.var.scope == kExprVarScopeMissing - && !cur_token.data.var.autoload) - && tok_type != kExprLexArrow)) { + if (kv_last(pt_stack) == kEPTLambdaArguments + && ((want_node == kENodeOperator + && tok_type != kExprLexComma + && tok_type != kExprLexArrow) + || (want_node == kENodeValue + && !(cur_token.type == kExprLexPlainIdentifier + && cur_token.data.var.scope == kExprVarScopeMissing + && !cur_token.data.var.autoload) + && tok_type != kExprLexArrow))) { lambda_node->data.fig.type_guesses.allow_lambda = false; if (lambda_node->children != NULL && lambda_node->children->type == kExprNodeComma) { @@ -1956,16 +1962,11 @@ viml_pexpr_parse_process_token: // Else it may appear that possibly-lambda node is actually a dictionary // or curly-braces-name identifier. lambda_node = NULL; - if (want_node == kENodeArgumentSeparator) { - want_node = kENodeOperator; - } else { - want_node = kENodeValue; - } + kv_drop(pt_stack, 1); } } - assert(lambda_node == NULL - || want_node == kENodeArgumentSeparator - || want_node == kENodeArgument); + const ExprASTParseType cur_pt = kv_last(pt_stack); + assert(lambda_node == NULL || cur_pt == kEPTLambdaArguments); switch (tok_type) { case kExprLexMissing: case kExprLexSpacing: @@ -2129,7 +2130,7 @@ viml_pexpr_parse_process_token: break; } case kExprLexComma: { - assert(want_node != kENodeArgument); + assert(!(want_node == kENodeValue && cur_pt == kEPTLambdaArguments)); if (want_node == kENodeValue) { // Value level: comma appearing here is not valid. // Note: in Vim string(,x) will give E116, this is not the case here. @@ -2138,13 +2139,11 @@ viml_pexpr_parse_process_token: NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeMissing); cur_node->len = 0; *top_node_p = cur_node; - want_node = (want_node == kENodeArgument - ? kENodeArgumentSeparator - : kENodeOperator); + want_node = kENodeOperator; } - if (want_node == kENodeArgumentSeparator) { - assert(lambda_node->data.fig.type_guesses.allow_lambda); + if (cur_pt == kEPTLambdaArguments) { assert(lambda_node != NULL); + assert(lambda_node->data.fig.type_guesses.allow_lambda); SELECT_FIGURE_BRACE_TYPE(lambda_node, Lambda, Lambda); } if (kv_size(ast_stack) < 2) { @@ -2156,7 +2155,8 @@ viml_pexpr_parse_process_token: const ExprASTNodeType eastnode_type = (*eastnode_p)->type; const ExprOpLvl eastnode_lvl = node_lvl(**eastnode_p); if (eastnode_type == kExprNodeLambda) { - assert(want_node == kENodeArgumentSeparator); + assert(cur_pt == kEPTLambdaArguments + && want_node == kENodeOperator); break; } else if (eastnode_type == kExprNodeDictLiteral || eastnode_type == kExprNodeListLiteral @@ -2410,9 +2410,6 @@ viml_pexpr_parse_bracket_closing_error: case kExprNodeUnknownFigure: { if (new_top_node->children == NULL) { // No children of curly braces node indicates empty dictionary. - - // Should actually be kENodeArgument, but that was changed - // earlier. assert(want_node == kENodeValue); assert(new_top_node->data.fig.type_guesses.allow_dict); SELECT_FIGURE_BRACE_TYPE(new_top_node, DictLiteral, Dict); @@ -2475,7 +2472,7 @@ viml_pexpr_parse_figure_brace_closing_error: } *top_node_p = cur_node; kvi_push(ast_stack, &cur_node->children); - want_node = kENodeArgument; + kvi_push(pt_stack, kEPTLambdaArguments); lambda_node = cur_node; } else { ADD_IDENT( @@ -2495,9 +2492,12 @@ viml_pexpr_parse_figure_brace_closing_error: break; } case kExprLexArrow: { - if (want_node == kENodeArgumentSeparator - || want_node == kENodeArgument) { - if (want_node == kENodeArgument) { + if (cur_pt == kEPTLambdaArguments) { + kv_drop(pt_stack, 1); + assert(kv_size(pt_stack)); + if (want_node == kENodeValue) { + // Wanting value means trailing comma and NULL at the top of the + // stack. kv_drop(ast_stack, 1); } assert(kv_size(ast_stack) >= 1); @@ -2509,7 +2509,7 @@ viml_pexpr_parse_figure_brace_closing_error: SELECT_FIGURE_BRACE_TYPE(lambda_node, Lambda, Lambda); NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeArrow); if (lambda_node->children == NULL) { - assert(want_node == kENodeArgument); + assert(want_node == kENodeValue); lambda_node->children = cur_node; kvi_push(ast_stack, &lambda_node->children); } else { @@ -2535,10 +2535,8 @@ viml_pexpr_parse_figure_brace_closing_error: const ExprVarScope scope = (cur_token.type == kExprLexInvalid ? kExprVarScopeMissing : cur_token.data.var.scope); - if (want_node == kENodeValue || want_node == kENodeArgument) { - want_node = (want_node == kENodeArgument - ? kENodeArgumentSeparator - : kENodeOperator); + if (want_node == kENodeValue) { + want_node = kENodeOperator; NEW_NODE_WITH_CUR_POS(cur_node, (node_is_key ? kExprNodePlainKey @@ -2755,7 +2753,12 @@ viml_pexpr_parse_cycle_end: viml_parser_advance(pstate, cur_token.len); } while (true); viml_pexpr_parse_end: - if (want_node == kENodeValue) { + assert(kv_size(pt_stack)); + assert(kv_size(ast_stack)); + if (want_node == kENodeValue + // Blacklist some parse type entries as their presence means better error + // message in the other branch. + && kv_last(pt_stack) != kEPTLambdaArguments) { east_set_error(pstate, &ast.err, _("E15: Expected value, got EOC: %.*s"), pstate->pos); } else if (kv_size(ast_stack) != 1) { -- cgit From c7495ebcc0918ffd682083408895451318e41d1f Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 12 Nov 2017 02:18:43 +0300 Subject: viml/parser/expressions: Add support for parsing assignments --- src/nvim/api/vim.c | 39 ++++- src/nvim/syntax.c | 20 +++ src/nvim/viml/parser/expressions.c | 239 +++++++++++++++++++++++---- src/nvim/viml/parser/expressions.h | 40 ++++- test/symbolic/klee/viml_expressions_parser.c | 3 +- test/unit/viml/expressions/lexer_spec.lua | 10 +- test/unit/viml/expressions/parser_spec.lua | 23 ++- test/unit/viml/helpers.lua | 13 ++ 8 files changed, 327 insertions(+), 60 deletions(-) diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index bac19ef363..ce554d351c 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -900,14 +900,21 @@ typedef kvec_withinit_t(ExprASTConvStackItem, 16) ExprASTConvStack; /// Parse a VimL expression /// /// @param[in] expr Expression to parse. Is always treated as a single line. -/// @param[in] flags Flags: "m" if multiple expressions in a row are allowed -/// (only the first one will be parsed), "E" if EOC tokens -/// are not allowed (determines whether they will stop -/// parsing process or be recognized as an operator/space, -/// though also yielding an error). -/// -/// Use only "m" to parse like for "=", only "E" to -/// parse like for ":echo", empty string for ":let". +/// @param[in] flags Flags: +/// +/// - "m" if multiple expressions in a row are allowed (only +/// the first one will be parsed), +/// - "E" if EOC tokens are not allowed (determines whether +/// they will stop parsing process or be recognized as an +/// operator/space, though also yielding an error). +/// - "l" when needing to start parsing with lvalues for +/// ":let" or ":for". +/// +/// Common flag sets: +/// - "m" to parse like for ":echo". +/// - "E" to parse like for "=". +/// - empty string for ":call". +/// - "lm" to parse for ":let". /// @param[in] highlight If true, return value will also include "highlight" /// key containing array of 4-tuples (arrays) (Integer, /// Integer, Integer, String), where first three numbers @@ -964,6 +971,9 @@ typedef kvec_withinit_t(ExprASTConvStackItem, 16) ExprASTConvStack; /// value names from ExprCaseCompareStrategy, /// stringified without "kCCStrategy" prefix. Only /// present for "Comparison" nodes. +/// "augmentation": String, augmentation type for "Assignment" nodes. +/// Is either an empty string, "Add", "Subtract" or +/// "Concat" for "=", "+=", "-=" or ".=" respectively. /// "invert": Boolean, true if result of comparison needs to be /// inverted. Only present for "Comparison" nodes. /// "ivalue": Integer, integer value for "Integer" nodes. @@ -979,6 +989,7 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, switch (flags.data[i]) { case 'm': { pflags |= kExprFlagsMulti; break; } case 'E': { pflags |= kExprFlagsDisallowEOC; break; } + case 'l': { pflags |= kExprFlagsParseLet; break; } case NUL: { api_set_error(err, kErrorTypeValidation, "Invalid flag: '\\0' (%u)", (unsigned)flags.data[i]); @@ -1114,6 +1125,7 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, + (node->type == kExprNodeFloat) // "fvalue" + (node->type == kExprNodeDoubleQuotedString || node->type == kExprNodeSingleQuotedString) // "svalue" + + (node->type == kExprNodeAssignment) // "augmentation" + 0); Dictionary ret_node = { .items = xmalloc(ret_node_items_size * sizeof(ret_node.items[0])), @@ -1272,6 +1284,17 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, }; break; } + case kExprNodeAssignment: { + const ExprAssignmentType asgn_type = node->data.ass.type; + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("augmentation"), + .value = STRING_OBJ( + asgn_type == kExprAsgnPlain + ? (String)STRING_INIT + : cstr_to_string(expr_asgn_type_tab[asgn_type])), + }; + break; + } case kExprNodeMissing: case kExprNodeOpMissing: case kExprNodeTernary: diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index e0bf74567d..9f98b26905 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -6021,11 +6021,21 @@ static const char *highlight_init_dark[] = { }; static const char *highlight_init_cmdline[] = { + // XXX When modifying a list modify it in both valid and invalid halfs. + // TODO(ZyX-I): merge valid and invalid groups via a macros. + // NVimInternalError should appear only when highlighter has a bug. "NVimInternalError ctermfg=Red ctermbg=Red guifg=Red guibg=Red", // Highlight groups (links) used by parser: + "default link NVimAssignment Operator", + "default link NVimPlainAssignment NVimAssignment", + "default link NVimAugmentedAssignment NVimAssignment", + "default link NVimAssignmentWithAddition NVimAugmentedAssignment", + "default link NVimAssignmentWithSubtraction NVimAugmentedAssignment", + "default link NVimAssignmentWithConcatenation NVimAugmentedAssignment", + "default link NVimOperator Operator", "default link NVimUnaryOperator NVimOperator", @@ -6113,6 +6123,16 @@ static const char *highlight_init_cmdline[] = { "default link NVimInvalid Error", + "default link NVimInvalidAssignment NVimInvalid", + "default link NVimInvalidPlainAssignment NVimInvalidAssignment", + "default link NVimInvalidAugmentedAssignment NVimInvalidAssignment", + "default link NVimInvalidAssignmentWithAddition " + "NVimInvalidAugmentedAssignment", + "default link NVimInvalidAssignmentWithSubtraction " + "NVimInvalidAugmentedAssignment", + "default link NVimInvalidAssignmentWithConcatenation " + "NVimInvalidAugmentedAssignment", + "default link NVimInvalidOperator NVimInvalid", "default link NVimInvalidUnaryOperator NVimInvalidOperator", diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index e23c58bfd1..13f7131744 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -84,6 +84,10 @@ typedef enum { /// Just like parsing function arguments, but it is valid to be ended with an /// arrow only. kEPTLambdaArguments, + /// Assignment: parsing for :let + kEPTAssignment, + /// Single assignment: used when lists are not allowed (i.e. when nesting) + kEPTSingleAssignment, } ExprASTParseType; typedef kvec_withinit_t(ExprASTParseType, 4) ExprASTParseTypeStack; @@ -93,6 +97,7 @@ typedef enum { kEOpLvlInvalid = 0, kEOpLvlComplexIdentifier, kEOpLvlParens, + kEOpLvlAssignment, kEOpLvlArrow, kEOpLvlComma, kEOpLvlColon, @@ -217,8 +222,6 @@ LexExprToken viml_pexpr_next_token(ParserState *const pstate, const int flags) } CHAR(kExprLexQuestion, '?') CHAR(kExprLexColon, ':') - CHAR(kExprLexDot, '.') - CHAR(kExprLexPlus, '+') CHAR(kExprLexComma, ',') #undef CHAR @@ -532,12 +535,8 @@ LexExprToken viml_pexpr_next_token(ParserState *const pstate, const int flags) case '!': case '=': { if (pline.size == 1) { -viml_pexpr_next_token_invalid_comparison: - ret.type = (schar == '!' ? kExprLexNot : kExprLexInvalid); - if (ret.type == kExprLexInvalid) { - ret.data.err.msg = _("E15: Expected == or =~: %.*s"); - ret.data.err.type = kExprLexComparison; - } + ret.type = (schar == '!' ? kExprLexNot : kExprLexAssignment); + ret.data.ass.type = kExprAsgnPlain; break; } ret.type = kExprLexComparison; @@ -548,8 +547,11 @@ viml_pexpr_next_token_invalid_comparison: } else if (pline.data[1] == '~') { ret.data.cmp.type = kExprCmpMatches; ret.len++; + } else if (schar == '!') { + ret.type = kExprLexNot; } else { - goto viml_pexpr_next_token_invalid_comparison; + ret.type = kExprLexAssignment; + ret.data.ass.type = kExprAsgnPlain; } GET_CCS(ret, pline); break; @@ -571,17 +573,37 @@ viml_pexpr_next_token_invalid_comparison: break; } - // Minus sign or arrow from lambdas. + // Minus sign, arrow from lambdas or augmented assignment. case '-': { if (pline.size > 1 && pline.data[1] == '>') { ret.len++; ret.type = kExprLexArrow; + } else if (pline.size > 1 && pline.data[1] == '=') { + ret.len++; + ret.type = kExprLexAssignment; + ret.data.ass.type = kExprAsgnSubtract; } else { ret.type = kExprLexMinus; } break; } + // Sign or augmented assignment. +#define CHAR_OR_ASSIGN(ch, ch_type, ass_type) \ + case ch: { \ + if (pline.size > 1 && pline.data[1] == '=') { \ + ret.len++; \ + ret.type = kExprLexAssignment; \ + ret.data.ass.type = ass_type; \ + } else { \ + ret.type = ch_type; \ + } \ + break; \ + } + CHAR_OR_ASSIGN('+', kExprLexPlus, kExprAsgnAdd) + CHAR_OR_ASSIGN('.', kExprLexDot, kExprAsgnConcat) +#undef CHAR_OR_ASSIGN + // Expression end because Ex command ended. case NUL: case NL: { @@ -661,6 +683,7 @@ static const char *const eltkn_type_tab[] = { [kExprLexParenthesis] = "Parenthesis", [kExprLexComma] = "Comma", [kExprLexArrow] = "Arrow", + [kExprLexAssignment] = "Assignment", }; const char *const eltkn_cmp_type_tab[] = { @@ -671,6 +694,13 @@ const char *const eltkn_cmp_type_tab[] = { [kExprCmpIdentical] = "Identical", }; +const char *const expr_asgn_type_tab[] = { + [kExprAsgnPlain] = "Plain", + [kExprAsgnAdd] = "Add", + [kExprAsgnSubtract] = "Subtract", + [kExprAsgnConcat] = "Concat", +}; + const char *const ccs_tab[] = { [kCCStrategyUseOption] = "UseOption", [kCCStrategyMatchCase] = "MatchCase", @@ -732,6 +762,8 @@ const char *viml_pexpr_repr_token(const ParserState *const pstate, (int)token.data.cmp.inv) TKNARGS(kExprLexMultiplication, "(type=%s)", eltkn_mul_type_tab[token.data.mul.type]) + TKNARGS(kExprLexAssignment, "(type=%s)", + expr_asgn_type_tab[token.data.ass.type]) TKNARGS(kExprLexRegister, "(name=%s)", intchar2str(token.data.reg.name)) case kExprLexDoubleQuotedString: TKNARGS(kExprLexSingleQuotedString, "(closed=%i)", @@ -811,6 +843,7 @@ const char *const east_node_type_tab[] = { [kExprNodeMod] = "Mod", [kExprNodeOption] = "Option", [kExprNodeEnvironment] = "Environment", + [kExprNodeAssignment] = "Assignment", }; /// Represent `int` character as a string @@ -933,6 +966,7 @@ const uint8_t node_maxchildren[] = { [kExprNodeMod] = 2, [kExprNodeOption] = 0, [kExprNodeEnvironment] = 0, + [kExprNodeAssignment] = 2, }; /// Free memory occupied by AST @@ -993,6 +1027,7 @@ void viml_pexpr_free_ast(ExprAST ast) case kExprNodeLambda: case kExprNodeDictLiteral: case kExprNodeCurlyBracesIdentifier: + case kExprNodeAssignment: case kExprNodeComma: case kExprNodeColon: case kExprNodeArrow: @@ -1111,6 +1146,8 @@ static struct { [kExprNodeCurlyBracesIdentifier] = { kEOpLvlComplexIdentifier, kEOpAssLeft }, + [kExprNodeAssignment] = { kEOpLvlAssignment, kEOpAssLeft }, + [kExprNodeComplexIdentifier] = { kEOpLvlValue, kEOpAssLeft }, [kExprNodePlainIdentifier] = { kEOpLvlValue, kEOpAssNo }, @@ -1478,6 +1515,17 @@ static inline void east_set_error(const ParserState *const pstate, } \ } while (0) +/// Determine whether given parse type is an assignment +/// +/// @param[in] pt Checked parse type. +/// +/// @return true if parsing an assignment, false otherwise. +static inline bool pt_is_assignment(const ExprASTParseType pt) + FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_CONST FUNC_ATTR_WARN_UNUSED_RESULT +{ + return (pt == kEPTAssignment || pt == kEPTSingleAssignment); +} + /// Structure used to define “string shifts” necessary to map string /// highlighting to actual strings. typedef struct { @@ -1839,6 +1887,9 @@ ExprAST viml_pexpr_parse(ParserState *const pstate, const int flags) ExprASTParseTypeStack pt_stack; kvi_init(pt_stack); kvi_push(pt_stack, kEPTExpr); + if (flags & kExprFlagsParseLet) { + kvi_push(pt_stack, kEPTAssignment); + } LexExprToken prev_token = { .type = kExprLexMissing }; bool highlighted_prev_spacing = false; // Lambda node, valid when parsing lambda arguments only. @@ -1938,33 +1989,83 @@ viml_pexpr_parse_process_token: // circumstances, and in any case runtime and not parse time errors. (*kv_Z(ast_stack, 1))->type = kExprNodeConcat; } - if (kv_last(pt_stack) == kEPTLambdaArguments - && ((want_node == kENodeOperator + // Pop some stack pt_stack items in case of misplaced nodes. + const bool is_single_assignment = kv_last(pt_stack) == kEPTSingleAssignment; + switch (kv_last(pt_stack)) { + case kEPTExpr: { + break; + } + case kEPTLambdaArguments: { + if ((want_node == kENodeOperator && tok_type != kExprLexComma && tok_type != kExprLexArrow) || (want_node == kENodeValue && !(cur_token.type == kExprLexPlainIdentifier && cur_token.data.var.scope == kExprVarScopeMissing && !cur_token.data.var.autoload) - && tok_type != kExprLexArrow))) { - lambda_node->data.fig.type_guesses.allow_lambda = false; - if (lambda_node->children != NULL - && lambda_node->children->type == kExprNodeComma) { - // If lambda has comma child this means that parser has already seen at - // least "{arg1,", so node cannot possibly be anything, but lambda. - - // Vim may give E121 or E720 in this case, but it does not look right to - // have either because both are results of reevaluation possibly-lambda - // node as a dictionary and here this is not going to happen. - ERROR_FROM_TOKEN_AND_MSG( - cur_token, _("E15: Expected lambda arguments list or arrow: %.*s")); - } else { - // Else it may appear that possibly-lambda node is actually a dictionary - // or curly-braces-name identifier. - lambda_node = NULL; - kv_drop(pt_stack, 1); + && tok_type != kExprLexArrow)) { + lambda_node->data.fig.type_guesses.allow_lambda = false; + if (lambda_node->children != NULL + && lambda_node->children->type == kExprNodeComma) { + // If lambda has comma child this means that parser has already seen at + // least "{arg1,", so node cannot possibly be anything, but lambda. + + // Vim may give E121 or E720 in this case, but it does not look right to + // have either because both are results of reevaluation possibly-lambda + // node as a dictionary and here this is not going to happen. + ERROR_FROM_TOKEN_AND_MSG( + cur_token, _("E15: Expected lambda arguments list or arrow: %.*s")); + } else { + // Else it may appear that possibly-lambda node is actually a dictionary + // or curly-braces-name identifier. + lambda_node = NULL; + kv_drop(pt_stack, 1); + } + } + break; + } + case kEPTSingleAssignment: { + if (tok_type == kExprLexBracket && !cur_token.data.brc.closing) { + ERROR_FROM_TOKEN_AND_MSG( + cur_token, + _("E475: Nested lists not allowed when assigning: %.*s")); + kv_drop(pt_stack, 2); + assert(kv_size(pt_stack)); + assert(kv_last(pt_stack) == kEPTExpr); + break; + } + FALLTHROUGH; + } + case kEPTAssignment: { + if (want_node == kENodeValue + && tok_type != kExprLexBracket + && tok_type != kExprLexPlainIdentifier + && (tok_type != kExprLexFigureBrace || cur_token.data.brc.closing) + && !(node_is_key && tok_type == kExprLexNumber) + && tok_type != kExprLexEnv + && tok_type != kExprLexOption + && tok_type != kExprLexRegister) { + ERROR_FROM_TOKEN_AND_MSG( + cur_token, + _("E15: Expected value part of assignment lvalue: %.*s")); + kv_drop(pt_stack, 1); + } else if (want_node == kENodeOperator + && tok_type != kExprLexBracket + && (tok_type != kExprLexFigureBrace + || cur_token.data.brc.closing) + && tok_type != kExprLexDot + && (tok_type != kExprLexComma || !is_single_assignment) + && tok_type != kExprLexAssignment) { + ERROR_FROM_TOKEN_AND_MSG( + cur_token, + _("E15: Expected assignment operator or subscript: %.*s")); + kv_drop(pt_stack, 1); + } + assert(kv_size(pt_stack)); + break; } } + assert(kv_size(pt_stack)); const ExprASTParseType cur_pt = kv_last(pt_stack); assert(lambda_node == NULL || cur_pt == kEPTLambdaArguments); switch (tok_type) { @@ -2339,21 +2440,41 @@ viml_pexpr_parse_bracket_closing_error: } kvi_push(ast_stack, new_top_node_p); want_node = kENodeOperator; + if (cur_pt == kEPTSingleAssignment) { + kv_drop(pt_stack, 1); + } else if (cur_pt == kEPTAssignment) { + assert(ast.err.msg); + } else if (cur_pt == kEPTExpr + && kv_size(pt_stack) > 1 + && pt_is_assignment(kv_Z(pt_stack, 1))) { + kv_drop(pt_stack, 1); + } } else { if (want_node == kENodeValue) { - // Value means list literal. + // Value means list literal or list assignment. HL_CUR_TOKEN(List); NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeListLiteral); *top_node_p = cur_node; kvi_push(ast_stack, &cur_node->children); want_node = kENodeValue; + if (cur_pt == kEPTAssignment) { + // Additional assignment parse type allows to easily forbid nested + // lists. + kvi_push(pt_stack, kEPTSingleAssignment); + } } else { + // Operator means subscript, also in assignment. But in assignment + // subscript may be pretty much any expression, so need to push + // kEPTExpr. if (prev_token.type == kExprLexSpacing) { OP_MISSING; } NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeSubscript); ADD_OP_NODE(cur_node); HL_CUR_TOKEN(SubscriptBracket); + if (pt_is_assignment(cur_pt)) { + kvi_push(pt_stack, kEPTExpr); + } } } break; @@ -2458,15 +2579,31 @@ viml_pexpr_parse_figure_brace_closing_error: } kvi_push(ast_stack, new_top_node_p); want_node = kENodeOperator; + if (cur_pt == kEPTExpr + && kv_size(pt_stack) > 1 + && pt_is_assignment(kv_Z(pt_stack, 1))) { + kv_drop(pt_stack, 1); + } } else { if (want_node == kENodeValue) { HL_CUR_TOKEN(FigureBrace); // Value: may be any of lambda, dictionary literal and curly braces // name. - NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeUnknownFigure); - cur_node->data.fig.type_guesses.allow_lambda = true; - cur_node->data.fig.type_guesses.allow_dict = true; - cur_node->data.fig.type_guesses.allow_ident = true; + + // Though if we are in an assignment this may only be a curly braces + // name. + if (pt_is_assignment(cur_pt)) { + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeCurlyBracesIdentifier); + cur_node->data.fig.type_guesses.allow_lambda = false; + cur_node->data.fig.type_guesses.allow_dict = false; + cur_node->data.fig.type_guesses.allow_ident = true; + kvi_push(pt_stack, kEPTExpr); + } else { + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeUnknownFigure); + cur_node->data.fig.type_guesses.allow_lambda = true; + cur_node->data.fig.type_guesses.allow_dict = true; + cur_node->data.fig.type_guesses.allow_ident = true; + } if (pstate->colors) { cur_node->data.fig.opening_hl_idx = kv_size(*pstate->colors) - 1; } @@ -2484,6 +2621,9 @@ viml_pexpr_parse_figure_brace_closing_error: cur_node->data.fig.type_guesses.allow_dict = false; cur_node->data.fig.type_guesses.allow_ident = true; kvi_push(ast_stack, &cur_node->children); + if (pt_is_assignment(cur_pt)) { + kvi_push(pt_stack, kEPTExpr); + } want_node = kENodeValue; } while (0), Curly); @@ -2746,6 +2886,36 @@ viml_pexpr_parse_no_paren_closing_error: {} want_node = kENodeOperator; break; } + case kExprLexAssignment: { + if (cur_pt == kEPTAssignment) { + kv_drop(pt_stack, 1); + } else if (cur_pt == kEPTSingleAssignment) { + kv_drop(pt_stack, 2); + ERROR_FROM_TOKEN_AND_MSG( + cur_token, + _("E475: Expected closing bracket to end list assignment " + "lvalue: %.*s")); + } else { + ERROR_FROM_TOKEN_AND_MSG( + cur_token, _("E15: Misplaced assignment: %.*s")); + } + assert(kv_size(pt_stack)); + assert(kv_last(pt_stack) == kEPTExpr); + ADD_VALUE_IF_MISSING(_("E15: Unexpected assignment: %.*s")); + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeAssignment); + cur_node->data.ass.type = cur_token.data.ass.type; + switch (cur_token.data.ass.type) { +#define HL_ASGN(asgn, hl) \ + case kExprAsgn##asgn: { HL_CUR_TOKEN(hl); break; } + HL_ASGN(Plain, PlainAssignment) + HL_ASGN(Add, AssignmentWithAddition) + HL_ASGN(Subtract, AssignmentWithSubtraction) + HL_ASGN(Concat, AssignmentWithConcatenation) +#undef HL_ASGN + } + ADD_OP_NODE(cur_node); + break; + } } viml_pexpr_parse_cycle_end: prev_token = cur_token; @@ -2862,6 +3032,7 @@ viml_pexpr_parse_end: // FIXME: Investigate whether above are OK to be present in the stack. break; } + case kExprNodeAssignment: case kExprNodeMod: case kExprNodeDivision: case kExprNodeMultiplication: diff --git a/src/nvim/viml/parser/expressions.h b/src/nvim/viml/parser/expressions.h index 648f8cbc1f..23e172da75 100644 --- a/src/nvim/viml/parser/expressions.h +++ b/src/nvim/viml/parser/expressions.h @@ -51,6 +51,9 @@ typedef enum { kExprLexParenthesis, ///< Parenthesis, either opening or closing. kExprLexComma, ///< Comma. kExprLexArrow, ///< Arrow, like from lambda expressions. + kExprLexAssignment, ///< Assignment: `=` or `{op}=`. + // XXX When modifying this enum you need to also modify eltkn_type_tab in + // expressions.c and tests and, possibly, viml_pexpr_repr_token. } LexExprTokenType; typedef enum { @@ -68,6 +71,14 @@ typedef enum { kExprOptScopeLocal = 'l', } ExprOptScope; +/// All possible assignment types: `=` and `{op}=`. +typedef enum { + kExprAsgnPlain = 0, ///< Plain assignment: `=`. + kExprAsgnAdd, ///< Assignment augmented with addition: `+=`. + kExprAsgnSubtract, ///< Assignment augmented with subtraction: `-=`. + kExprAsgnConcat, ///< Assignment augmented with concatenation: `.=`. +} ExprAssignmentType; + #define EXPR_OPT_SCOPE_LIST \ ((char[]){ kExprOptScopeGlobal, kExprOptScopeLocal }) @@ -147,6 +158,10 @@ typedef struct { uint8_t base; ///< Base: 2, 8, 10 or 16. bool is_float; ///< True if number is a floating-point. } num; ///< For kExprLexNumber + + struct { + ExprAssignmentType type; + } ass; ///< For kExprLexAssignment } data; ///< Additional data, if needed. } LexExprToken; @@ -170,8 +185,8 @@ typedef enum { /// “EOC” is something like "|". It is fine with emitting EOC at the end of /// string still, with or without this flag set. kELFlagForbidEOC = (1 << 4), - // WARNING: whenever you add a new flag, alter klee_assume() statement in - // viml_expressions_lexer.c. + // XXX Whenever you add a new flag, alter klee_assume() statement in + // viml_expressions_lexer.c. } LexExprFlags; /// Expression AST node type @@ -233,6 +248,10 @@ typedef enum { kExprNodeMod, kExprNodeOption, kExprNodeEnvironment, + kExprNodeAssignment, + // XXX When modifying this list also modify east_node_type_tab both in parser + // and in tests, and you most likely will also have to alter list of + // highlight groups stored in highlight_init_cmdline variable. } ExprASTNodeType; typedef struct expr_ast_node ExprASTNode; @@ -301,6 +320,9 @@ struct expr_ast_node { const char *ident; ///< Environment variable name start. size_t ident_len; ///< Environment variable name length. } env; ///< For kExprNodeEnvironment. + struct { + ExprAssignmentType type; + } ass; ///< For kExprNodeAssignment } data; }; @@ -314,8 +336,15 @@ enum { /// When parsing expressions input by user bar is assumed to be a binary /// operator and other two are spacings. kExprFlagsDisallowEOC = (1 << 1), - // WARNING: whenever you add a new flag, alter klee_assume() statement in - // viml_expressions_parser.c. + /// Parse :let argument + /// + /// That mean that top level node must be an assignment and first nodes + /// belong to lvalues. + kExprFlagsParseLet = (1 << 2), + // XXX whenever you add a new flag, alter klee_assume() statement in + // viml_expressions_parser.c, nvim_parse_expression() flags parsing + // alongside with its documentation and flag sets in check_parsing() + // function in expressions parser functional and unit tests. } ExprParserFlags; /// AST error definition @@ -350,6 +379,9 @@ extern const char *const eltkn_cmp_type_tab[]; /// Array mapping ExprCaseCompareStrategy values to their stringified versions extern const char *const ccs_tab[]; +/// Array mapping ExprAssignmentType values to their stringified versions +extern const char *const expr_asgn_type_tab[]; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "viml/parser/expressions.h.generated.h" #endif diff --git a/test/symbolic/klee/viml_expressions_parser.c b/test/symbolic/klee/viml_expressions_parser.c index 9448937430..fd75e8d355 100644 --- a/test/symbolic/klee/viml_expressions_parser.c +++ b/test/symbolic/klee/viml_expressions_parser.c @@ -48,7 +48,8 @@ int main(const int argc, const char *const *const argv, klee_make_symbolic(&shift, sizeof(shift), "shift"); klee_make_symbolic(&flags, sizeof(flags), "flags"); klee_assume(shift < INPUT_SIZE); - klee_assume(flags <= (kExprFlagsMulti|kExprFlagsDisallowEOC)); + klee_assume( + flags <= (kExprFlagsMulti|kExprFlagsDisallowEOC|kExprFlagsParseLet)); #endif ParserLine plines[] = { diff --git a/test/unit/viml/expressions/lexer_spec.lua b/test/unit/viml/expressions/lexer_spec.lua index 75a641c48a..1b57a24ad5 100644 --- a/test/unit/viml/expressions/lexer_spec.lua +++ b/test/unit/viml/expressions/lexer_spec.lua @@ -13,6 +13,7 @@ local conv_ccs = viml_helpers.conv_ccs local new_pstate = viml_helpers.new_pstate local conv_cmp_type = viml_helpers.conv_cmp_type local pstate_set_str = viml_helpers.pstate_set_str +local conv_expr_asgn_type = viml_helpers.conv_expr_asgn_type local shallowcopy = global_helpers.shallowcopy local intchar2lua = global_helpers.intchar2lua @@ -52,6 +53,8 @@ child_call_once(function() [tonumber(lib.kExprLexParenthesis)] = 'Parenthesis', [tonumber(lib.kExprLexComma)] = 'Comma', [tonumber(lib.kExprLexArrow)] = 'Arrow', + + [tonumber(lib.kExprLexAssignment)] = 'Assignment', } eltkn_mul_type_tab = { @@ -118,6 +121,8 @@ local function eltkn2lua(pstate, tkn) ret.data.val = tonumber(tkn.data.num.is_float and tkn.data.num.val.floating or tkn.data.num.val.integer) + elseif ret.type == 'Assignment' then + ret.data = { type = conv_expr_asgn_type(tkn.data.ass.type) } elseif ret.type == 'Invalid' then ret.data = { error = ffi.string(tkn.data.err.msg) } end @@ -198,7 +203,9 @@ describe('Expressions lexer', function() singl_eltkn_test('Question', '?') singl_eltkn_test('Colon', ':') singl_eltkn_test('Dot', '.') + singl_eltkn_test('Assignment', '.=', {type='Concat'}) singl_eltkn_test('Plus', '+') + singl_eltkn_test('Assignment', '+=', {type='Add'}) singl_eltkn_test('Comma', ',') singl_eltkn_test('Multiplication', '*', {type='Mul'}) singl_eltkn_test('Multiplication', '/', {type='Div'}) @@ -266,12 +273,13 @@ describe('Expressions lexer', function() singl_eltkn_test('DoubleQuotedString', '"x\\"', {closed=false}) singl_eltkn_test('DoubleQuotedString', '"\\"x', {closed=false}) singl_eltkn_test('Not', '!') - singl_eltkn_test('Invalid', '=', {error='E15: Expected == or =~: %.*s'}) + singl_eltkn_test('Assignment', '=', {type='Plain'}) comparison_test('==', '!=', 'Equal') comparison_test('=~', '!~', 'Matches') comparison_test('>', '<=', 'Greater') comparison_test('>=', '<', 'GreaterOrEqual') singl_eltkn_test('Minus', '-') + singl_eltkn_test('Assignment', '-=', {type='Subtract'}) singl_eltkn_test('Arrow', '->') singl_eltkn_test('Invalid', '~', {error='E15: Unidentified character: %.*s'}) simple_test({{data=nil, size=0}}, 'EOC', 0, {error='start.col >= #pstr'}) diff --git a/test/unit/viml/expressions/parser_spec.lua b/test/unit/viml/expressions/parser_spec.lua index cfc9fe95ac..810d7bfbc6 100644 --- a/test/unit/viml/expressions/parser_spec.lua +++ b/test/unit/viml/expressions/parser_spec.lua @@ -18,6 +18,7 @@ local conv_ccs = viml_helpers.conv_ccs local new_pstate = viml_helpers.new_pstate local conv_cmp_type = viml_helpers.conv_cmp_type local pstate_set_str = viml_helpers.pstate_set_str +local conv_expr_asgn_type = viml_helpers.conv_expr_asgn_type local mergedicts_copy = global_helpers.mergedicts_copy local format_string = global_helpers.format_string @@ -109,6 +110,7 @@ make_enum_conv_tab(lib, { 'kExprNodeMod', 'kExprNodeOption', 'kExprNodeEnvironment', + 'kExprNodeAssignment', }, 'kExprNode', function(ret) east_node_type_tab = ret end) local function conv_east_node_type(typ) @@ -174,6 +176,8 @@ local function eastnode2lua(pstate, eastnode, checked_nodes) typ = ('%s(ident=%s)'):format( typ, ffi.string(eastnode.data.env.ident, eastnode.data.env.ident_len)) + elseif typ == 'Assignment' then + typ = ('%s(%s)'):format(typ, conv_expr_asgn_type(eastnode.data.ass.type)) end ret_str = typ .. ':' .. ret_str local can_simplify = not ret.children @@ -3976,27 +3980,20 @@ describe('Expressions parser', function() -- 012345 ast = { { - 'Comparison(type=Equal,inv=0,ccs=UseOption):0:3:=', + 'Assignment(Add):0:1: +=', children = { - { - 'BinaryPlus:0:1: +', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - 'Missing:0:3:', - }, - }, + 'PlainIdentifier(scope=0,ident=a):0:0:a', 'PlainIdentifier(scope=0,ident=b):0:4: b', }, }, }, err = { - arg = '= b', - msg = 'E15: Expected == or =~: %.*s', + arg = '+= b', + msg = 'E15: Misplaced assignment: %.*s', }, }, { hl('IdentifierName', 'a'), - hl('BinaryPlus', '+', 1), - hl('InvalidComparison', '='), + hl('InvalidAssignmentWithAddition', '+=', 1), hl('IdentifierName', 'b', 1), }) check_parsing('a + b == c + d', { @@ -7347,4 +7344,6 @@ describe('Expressions parser', function() }, }) end) + -- FIXME: Test assignments thoroughly + -- FIXME: Test that parsing assignments can be used for `:for` pre-`in` part. end) diff --git a/test/unit/viml/helpers.lua b/test/unit/viml/helpers.lua index 9d2d7b61c7..9d8102e023 100644 --- a/test/unit/viml/helpers.lua +++ b/test/unit/viml/helpers.lua @@ -107,6 +107,18 @@ local function conv_ccs(ccs) return conv_enum(ccs_tab, ccs) end +local expr_asgn_type_tab +make_enum_conv_tab(lib, { + 'kExprAsgnPlain', + 'kExprAsgnAdd', + 'kExprAsgnSubtract', + 'kExprAsgnConcat', +}, 'kExprAsgn', function(ret) expr_asgn_type_tab = ret end) + +local function conv_expr_asgn_type(expr_asgn_type) + return conv_enum(expr_asgn_type_tab, expr_asgn_type) +end + return { conv_ccs = conv_ccs, pline2lua = pline2lua, @@ -114,4 +126,5 @@ return { new_pstate = new_pstate, conv_cmp_type = conv_cmp_type, pstate_set_str = pstate_set_str, + conv_expr_asgn_type = conv_expr_asgn_type, } -- cgit From 45445e2e03f1cbfa25dde76ccf3e24d0d297cabe Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 12 Nov 2017 03:52:26 +0300 Subject: unittests: Add some more edge test cases --- src/nvim/viml/parser/expressions.c | 7 ++ test/unit/viml/expressions/parser_spec.lua | 143 +++++++++++++++++++++++++++++ 2 files changed, 150 insertions(+) diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index 13f7131744..07bac89997 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -40,6 +40,13 @@ // E15: Invalid expression: [1, // // < , just exactly one E697 message. +// 6. Some expressions involving calling parenthesis which are treated +// separately by Vim even when not separated by spaces are treated as one +// expression by Neovim: e.g. ":echo (1)(1)" will yield runtime error after +// failing to call "1", while Vim will echo "1 1". Reasoning is the same: +// type of what is in the first expression is generally not known when +// parsing, so to have separate expressions like this separate them with +// spaces. #include #include diff --git a/test/unit/viml/expressions/parser_spec.lua b/test/unit/viml/expressions/parser_spec.lua index 810d7bfbc6..df45cae304 100644 --- a/test/unit/viml/expressions/parser_spec.lua +++ b/test/unit/viml/expressions/parser_spec.lua @@ -1233,6 +1233,132 @@ describe('Expressions parser', function() hl('CallingParenthesis', ')'), hl('CallingParenthesis', ')'), }) + check_parsing('()()', { + -- 0123 + ast = { + { + 'Call:0:2:(', + children = { + { + 'Nested:0:0:(', + children = { + 'Missing:0:1:', + }, + }, + }, + }, + }, + err = { + arg = ')()', + msg = 'E15: Expected value, got parenthesis: %.*s', + }, + }, { + hl('NestingParenthesis', '('), + hl('InvalidNestingParenthesis', ')'), + hl('CallingParenthesis', '('), + hl('CallingParenthesis', ')'), + }) + check_parsing('(@a)()', { + -- 012345 + ast = { + { + 'Call:0:4:(', + children = { + { + 'Nested:0:0:(', + children = { + 'Register(name=a):0:1:@a', + }, + }, + }, + }, + }, + }, { + hl('NestingParenthesis', '('), + hl('Register', '@a'), + hl('NestingParenthesis', ')'), + hl('CallingParenthesis', '('), + hl('CallingParenthesis', ')'), + }) + check_parsing('(@a)(@b)', { + -- 01234567 + ast = { + { + 'Call:0:4:(', + children = { + { + 'Nested:0:0:(', + children = { + 'Register(name=a):0:1:@a', + }, + }, + 'Register(name=b):0:5:@b', + }, + }, + }, + }, { + hl('NestingParenthesis', '('), + hl('Register', '@a'), + hl('NestingParenthesis', ')'), + hl('CallingParenthesis', '('), + hl('Register', '@b'), + hl('CallingParenthesis', ')'), + }) + check_parsing('(@a) (@b)', { + -- 012345678 + ast = { + { + 'OpMissing:0:4:', + children = { + { + 'Nested:0:0:(', + children = { + 'Register(name=a):0:1:@a', + }, + }, + { + 'Nested:0:4: (', + children = { + 'Register(name=b):0:6:@b', + }, + }, + }, + }, + }, + err = { + arg = '(@b)', + msg = 'E15: Missing operator: %.*s', + }, + }, { + hl('NestingParenthesis', '('), + hl('Register', '@a'), + hl('NestingParenthesis', ')'), + hl('InvalidSpacing', ' '), + hl('NestingParenthesis', '('), + hl('Register', '@b'), + hl('NestingParenthesis', ')'), + }, { + [1] = { + ast = { + ast = { + { + 'Nested:0:0:(', + children = { + 'Register(name=a):0:1:@a', + REMOVE_THIS, + }, + }, + }, + err = REMOVE_THIS, + }, + hl_fs = { + [4] = REMOVE_THIS, + [5] = REMOVE_THIS, + [6] = REMOVE_THIS, + [7] = REMOVE_THIS, + }, + }, + }) end) itp('works with variable names, including curly braces ones', function() check_parsing('var', { @@ -1543,6 +1669,21 @@ describe('Expressions parser', function() hl('FigureBrace', '{'), hl('Register', '@a'), }) + check_parsing('a ()', { + -- 0123 + ast = { + { + 'Call:0:1: (', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('CallingParenthesis', '(', 1), + hl('CallingParenthesis', ')'), + }) end) itp('works with lambdas and dictionaries', function() check_parsing('{}', { @@ -7346,4 +7487,6 @@ describe('Expressions parser', function() end) -- FIXME: Test assignments thoroughly -- FIXME: Test that parsing assignments can be used for `:for` pre-`in` part. + -- FIXME: Somehow make functional tests use the same code. Or, at least, + -- create an automated script which will do the import. end) -- cgit From 556451a7f2fd513db33b9d7ac1b653d356b7b915 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 12 Nov 2017 16:59:36 +0300 Subject: unittests,syntax: Check for sanity of highlight_init_cmdline Also fixes some errors found. --- src/nvim/syntax.c | 14 ++- src/nvim/syntax.h | 3 + test/functional/ui/cmdline_highlight_spec.lua | 1 - test/unit/viml/expressions/parser_spec.lua | 159 +++++++++++++++++++++++++- 4 files changed, 169 insertions(+), 8 deletions(-) diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index 9f98b26905..c9e99d82f8 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -6020,7 +6020,7 @@ static const char *highlight_init_dark[] = { NULL }; -static const char *highlight_init_cmdline[] = { +const char *const highlight_init_cmdline[] = { // XXX When modifying a list modify it in both valid and invalid halfs. // TODO(ZyX-I): merge valid and invalid groups via a macros. @@ -6047,6 +6047,7 @@ static const char *highlight_init_cmdline[] = { "default link NVimComparison NVimBinaryOperator", "default link NVimComparisonModifier NVimComparison", "default link NVimBinaryPlus NVimBinaryOperator", + "default link NVimBinaryMinus NVimBinaryOperator", "default link NVimConcat NVimBinaryOperator", "default link NVimConcatOrSubscript NVimConcat", "default link NVimOr NVimBinaryOperator", @@ -6108,9 +6109,6 @@ static const char *highlight_init_cmdline[] = { "default link NVimDoubleQuote NVimStringQuote", "default link NVimDoubleQuotedBody NVimStringBody", "default link NVimDoubleQuotedEscape NVimStringSpecial", - // Not actually invalid, but we highlight user that he is doing something - // wrong. - "default link NVimDoubleQuotedUnknownEscape NVimInvalidValue", "default link NVimFigureBrace NVimInternalError", "default link NVimSingleQuotedUnknownEscape NVimInternalError", @@ -6144,6 +6142,7 @@ static const char *highlight_init_cmdline[] = { "default link NVimInvalidComparison NVimInvalidBinaryOperator", "default link NVimInvalidComparisonModifier NVimInvalidComparison", "default link NVimInvalidBinaryPlus NVimInvalidBinaryOperator", + "default link NVimInvalidBinaryMinus NVimInvalidBinaryOperator", "default link NVimInvalidConcat NVimInvalidBinaryOperator", "default link NVimInvalidConcatOrSubscript NVimInvalidConcat", "default link NVimInvalidOr NVimInvalidBinaryOperator", @@ -6217,12 +6216,17 @@ static const char *highlight_init_cmdline[] = { "default link NVimInvalidFigureBrace NVimInvalidDelimiter", "default link NVimInvalidSpacing ErrorMsg", + + // Not actually invalid, but we highlight user that he is doing something + // wrong. + "default link NVimDoubleQuotedUnknownEscape NVimInvalidValue", + NULL, }; /// Create default links for NVim* highlight groups used for cmdline coloring void syn_init_cmdline_highlight(bool reset, bool init) { - for (size_t i = 0 ; i < ARRAY_SIZE(highlight_init_cmdline) ; i++) { + for (size_t i = 0 ; highlight_init_cmdline[i] != NULL ; i++) { do_highlight(highlight_init_cmdline[i], reset, init); } } diff --git a/src/nvim/syntax.h b/src/nvim/syntax.h index bb733ead30..c9b0665ec8 100644 --- a/src/nvim/syntax.h +++ b/src/nvim/syntax.h @@ -45,6 +45,9 @@ typedef struct { } color_name_table_T; extern color_name_table_T color_name_table[]; +/// Array of highlight definitions, used for unit testing +extern const char *const highlight_init_cmdline[]; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "syntax.h.generated.h" #endif diff --git a/test/functional/ui/cmdline_highlight_spec.lua b/test/functional/ui/cmdline_highlight_spec.lua index b16b4a5602..ab195f9f1e 100644 --- a/test/functional/ui/cmdline_highlight_spec.lua +++ b/test/functional/ui/cmdline_highlight_spec.lua @@ -899,7 +899,6 @@ describe('Expressions coloring support', function() ]]) end) -- FIXME: Test expr coloring when using -u NORC and -u NONE. - -- FIXME: Test all highlight groups, using long expression. -- FIXME: Test different ways of triggering expression highlighting (:=, -- i=, :e, "=). end) diff --git a/test/unit/viml/expressions/parser_spec.lua b/test/unit/viml/expressions/parser_spec.lua index df45cae304..cfcd63f005 100644 --- a/test/unit/viml/expressions/parser_spec.lua +++ b/test/unit/viml/expressions/parser_spec.lua @@ -7,11 +7,13 @@ local make_enum_conv_tab = helpers.make_enum_conv_tab local child_call_once = helpers.child_call_once local alloc_log_new = helpers.alloc_log_new local kvi_destroy = helpers.kvi_destroy +local array_size = helpers.array_size local conv_enum = helpers.conv_enum local debug_log = helpers.debug_log local ptr2key = helpers.ptr2key local cimport = helpers.cimport local ffi = helpers.ffi +local neq = helpers.neq local eq = helpers.eq local conv_ccs = viml_helpers.conv_ccs @@ -26,10 +28,158 @@ local format_luav = global_helpers.format_luav local intchar2lua = global_helpers.intchar2lua local REMOVE_THIS = global_helpers.REMOVE_THIS -local lib = cimport('./src/nvim/viml/parser/expressions.h') +local lib = cimport('./src/nvim/viml/parser/expressions.h', + './src/nvim/syntax.h') local alloc_log = alloc_log_new() +local predefined_hl_defs = { + -- From highlight_init_both + Conceal=true, + Cursor=true, + lCursor=true, + DiffText=true, + ErrorMsg=true, + IncSearch=true, + ModeMsg=true, + NonText=true, + PmenuSbar=true, + StatusLine=true, + StatusLineNC=true, + TabLineFill=true, + TabLineSel=true, + TermCursor=true, + VertSplit=true, + WildMenu=true, + EndOfBuffer=true, + QuickFixLine=true, + Substitute=true, + Whitespace=true, + + -- From highlight_init_(dark|light) + ColorColumn=true, + CursorColumn=true, + CursorLine=true, + CursorLineNr=true, + DiffAdd=true, + DiffChange=true, + DiffDelete=true, + Directory=true, + FoldColumn=true, + Folded=true, + LineNr=true, + MatchParen=true, + MoreMsg=true, + Pmenu=true, + PmenuSel=true, + PmenuThumb=true, + Question=true, + Search=true, + SignColumn=true, + SpecialKey=true, + SpellBad=true, + SpellCap=true, + SpellLocal=true, + SpellRare=true, + TabLine=true, + Title=true, + Visual=true, + WarningMsg=true, + Normal=true, + + -- From syncolor.vim, if &background + Comment=true, + Constant=true, + Special=true, + Identifier=true, + Statement=true, + PreProc=true, + Type=true, + Underlined=true, + Ignore=true, + + -- From syncolor.vim, below if &background + Error=true, + Todo=true, + + -- From syncolor.vim, links at the bottom + String=true, + Character=true, + Number=true, + Boolean=true, + Float=true, + Function=true, + Conditional=true, + Repeat=true, + Label=true, + Operator=true, + Keyword=true, + Exception=true, + Include=true, + Define=true, + Macro=true, + PreCondit=true, + StorageClass=true, + Structure=true, + Typedef=true, + Tag=true, + SpecialChar=true, + Delimiter=true, + SpecialComment=true, + Debug=true, +} + +local nvim_hl_defs = {} + +child_call_once(function() + local i = 0 + while lib.highlight_init_cmdline[i] ~= nil do + local hl_args = lib.highlight_init_cmdline[i] + local s = ffi.string(hl_args) + local err, msg = pcall(function() + if s:sub(1, 13) == 'default link ' then + local new_grp, grp_link = s:match('^default link (%w+) (%w+)$') + neq(nil, new_grp) + -- Note: group to link to must be already defined at the time of + -- linking, otherwise it will be created as cleared. So existence + -- of the group is checked here and not in the next pass over + -- nvim_hl_defs. + eq(true, not not (nvim_hl_defs[grp_link] + or predefined_hl_defs[grp_link])) + nvim_hl_defs[new_grp] = {'link', grp_link} + else + local new_grp, grp_args = s:match('^(%w+) (.*)') + neq(nil, new_grp) + eq(false, not not (nvim_hl_defs[grp_link] + or predefined_hl_defs[grp_link])) + nvim_hl_defs[new_grp] = {'definition', grp_args} + end + end) + if not err then + msg = format_string( + 'Error while processing string %s at position %u:\n%s', s, i, msg) + error(msg) + end + i = i + 1 + end + for k, _ in ipairs(nvim_hl_defs) do + eq('NVim', k:sub(1, 4)) + -- NVimInvalid + -- 12345678901 + local err, msg = pcall(function() + if k:sub(5, 11) == 'Invalid' then + neq(nil, nvim_hl_defs['NVim' .. k:sub(12)]) + else + neq(nil, nvim_hl_defs['NVimInvalid' .. k:sub(5)]) + end + end) + if not err then + msg = format_string('Error while processing group %s:\n%s', k, msg) + error(msg) + end + end +end) + local function format_check(expr, flags, ast, hls) -- That forces specific order. print( format_string('\ncheck_parsing(%r, %u, {', expr, flags)) @@ -237,6 +387,8 @@ end) describe('Expressions parser', function() local function check_parsing(str, exp_ast, exp_highlighting_fs, nz_flags_exps) nz_flags_exps = nz_flags_exps or {} + local format_check_data = function() + end for _, flags in ipairs({0, 1, 2, 3}) do debug_log(('Running test case (%s, %u)'):format(str, flags)) local err, msg = pcall(function() @@ -266,7 +418,7 @@ describe('Expressions parser', function() end end if exp_ast == nil then - format_check(str, flags, ast, hls) + format_check(str, ast, hls) else eq(exps.ast, ast) if exp_highlighting_fs then @@ -292,6 +444,9 @@ describe('Expressions parser', function() end local function hl(group, str, shift) return function(next_col) + if nvim_hl_defs['NVim' .. group] == nil then + error(('Unknown group: NVim%s'):format(group)) + end local col = next_col + (shift or 0) return (('%s:%u:%u:%s'):format( 'NVim' .. group, -- cgit From 39c75d31be98e4cbb63a01c398d89e231f71f380 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 12 Nov 2017 18:52:49 +0300 Subject: unittests: Fix automatic test case generation --- test/helpers.lua | 93 +++++++++++++++++++++------- test/unit/viml/expressions/parser_spec.lua | 98 +++++++++++++++++++++++------- 2 files changed, 148 insertions(+), 43 deletions(-) diff --git a/test/helpers.lua b/test/helpers.lua index d24fae745b..6c6611d061 100644 --- a/test/helpers.lua +++ b/test/helpers.lua @@ -310,6 +310,43 @@ local function mergedicts_copy(d1, d2) return ret end +-- dictdiff: find a diff so that mergedicts_copy(d1, diff) is equal to d2 +-- +-- Note: does not copy values from d2 +local function dictdiff(d1, d2) + local ret = {} + local hasdiff = false + for k, v in pairs(d1) do + if d2[k] == nil then + hasdiff = true + ret[k] = REMOVE_THIS + elseif type(v) == type(d2[k]) then + if type(v) == 'table' and type(d2[k]) == 'table' then + local subdiff = dictdiff(v, d2[k]) + if subdiff ~= nil then + hasdiff = true + ret[k] = subdiff + end + elseif v ~= d2[k] then + ret[k] = d2[k] + end + else + ret[k] = d2[k] + end + end + for k, v in pairs(d2) do + if d1[k] == nil then + ret[k] = v + hasdiff = true + end + end + if hasdiff then + return ret + else + return nil + end +end + local function updated(d, d2) for k, v in pairs(d2) do d[k] = v @@ -365,39 +402,52 @@ local SUBTBL = { local format_luav -format_luav = function(v, indent) +format_luav = function(v, indent, opts) + opts = opts or {} local linesep = '\n' - local next_indent = nil + local next_indent_arg = nil if indent == nil then indent = '' linesep = '' else - next_indent = indent .. ' ' + next_indent_arg = indent .. ' ' end + local next_indent = indent .. ' ' local ret = '' if type(v) == 'string' then - ret = tostring(v):gsub('[\'\\]', '\\%0'):gsub('[%z\1-\31]', function(match) - return SUBTBL[match:byte() + 1] - end) - ret = '\'' .. ret .. '\'' - elseif type(v) == 'table' then - local processed_keys = {} - ret = '{' .. linesep - for i, subv in ipairs(v) do - ret = ret .. (next_indent or '') .. format_luav(subv, next_indent) .. ',\n' - processed_keys[i] = true + if opts.literal_strings then + ret = v + else + ret = tostring(v):gsub('[\'\\]', '\\%0'):gsub( + '[%z\1-\31]', function(match) + return SUBTBL[match:byte() + 1] + end) + ret = '\'' .. ret .. '\'' end - for k, subv in pairs(v) do - if not processed_keys[k] then - if type(k) == 'string' and k:match('^[a-zA-Z_][a-zA-Z0-9_]*$') then - ret = ret .. next_indent .. k .. ' = ' - else - ret = ret .. next_indent .. '[' .. format_luav(k) .. '] = ' + elseif type(v) == 'table' then + if v == REMOVE_THIS then + ret = 'REMOVE_THIS' + else + local processed_keys = {} + ret = '{' .. linesep + for i, subv in ipairs(v) do + ret = ('%s%s%s,\n'):format(ret, next_indent, + format_luav(subv, next_indent_arg, opts)) + processed_keys[i] = true + end + for k, subv in pairs(v) do + if not processed_keys[k] then + if type(k) == 'string' and k:match('^[a-zA-Z_][a-zA-Z0-9_]*$') then + ret = ret .. next_indent .. k .. ' = ' + else + ret = ('%s%s[%s] = '):format(ret, next_indent, + format_luav(k, nil, opts)) + end + ret = ret .. format_luav(subv, next_indent_arg, opts) .. ',\n' end - ret = ret .. format_luav(subv, next_indent) .. ',\n' end + ret = ret .. indent .. '}' end - ret = ret .. indent .. '}' elseif type(v) == 'number' then if v % 1 == 0 then ret = ('%d'):format(v) @@ -461,6 +511,7 @@ return { shallowcopy = shallowcopy, deepcopy = deepcopy, mergedicts_copy = mergedicts_copy, + dictdiff = dictdiff, REMOVE_THIS = REMOVE_THIS, concat_tables = concat_tables, dedent = dedent, diff --git a/test/unit/viml/expressions/parser_spec.lua b/test/unit/viml/expressions/parser_spec.lua index cfcd63f005..25a41518eb 100644 --- a/test/unit/viml/expressions/parser_spec.lua +++ b/test/unit/viml/expressions/parser_spec.lua @@ -27,6 +27,7 @@ local format_string = global_helpers.format_string local format_luav = global_helpers.format_luav local intchar2lua = global_helpers.intchar2lua local REMOVE_THIS = global_helpers.REMOVE_THIS +local dictdiff = global_helpers.dictdiff local lib = cimport('./src/nvim/viml/parser/expressions.h', './src/nvim/syntax.h') @@ -180,9 +181,31 @@ child_call_once(function() end end) -local function format_check(expr, flags, ast, hls) +local function hls_to_hl_fs(hls) + local ret = {} + local next_col = 0 + for i, v in ipairs(hls) do + local group, line, col, str = v:match('^NVim([a-zA-Z]+):(%d+):(%d+):(.*)$') + col = tonumber(col) + line = tonumber(line) + assert(line == 0) + local col_shift = col - next_col + assert(col_shift >= 0) + next_col = col + #str + ret[i] = format_string('hl(%r, %r%s)', + group, + str, + (col_shift == 0 + and '' + or (', %u'):format(col_shift))) + end + return ret +end + +local function format_check(expr, format_check_data) -- That forces specific order. - print( format_string('\ncheck_parsing(%r, %u, {', expr, flags)) + local zdata = format_check_data[0] + print(format_string('\ncheck_parsing(%r, {', expr, flags)) local digits = ' -- ' local digits2 = ' -- ' for i = 0, #expr - 1 do @@ -195,27 +218,56 @@ local function format_check(expr, flags, ast, hls) if #expr > 10 then print(digits2) end - print(' ast = ' .. format_luav(ast.ast, ' ') .. ',') - if ast.err then + print(' ast = ' .. format_luav(zdata.ast.ast, ' ') .. ',') + if zdata.ast.err then print(' err = {') - print(' arg = ' .. format_luav(ast.err.arg) .. ',') - print(' msg = ' .. format_luav(ast.err.msg) .. ',') + print(' arg = ' .. format_luav(zdata.ast.err.arg) .. ',') + print(' msg = ' .. format_luav(zdata.ast.err.msg) .. ',') print(' },') end print('}, {') - local next_col = 0 - for _, v in ipairs(hls) do - local group, line, col, str = v:match('NVim([a-zA-Z]+):(%d+):(%d+):(.*)') - col = tonumber(col) - line = tonumber(line) - assert(line == 0) - local col_shift = col - next_col - assert(col_shift >= 0) - next_col = col + #str - print(format_string(' hl(%r, %r%s),', - group, - str, - (col_shift == 0 and '' or (', %u'):format(col_shift)))) + for _, v in ipairs(zdata.hl_fs) do + print(' ' .. v .. ',') + end + local diffs = {} + local diffs_num = 0 + for flags, v in pairs(format_check_data) do + if flags ~= 0 then + diffs[flags] = dictdiff(zdata, v) + if diffs[flags] then + if flags == 3 then + if (dictdiff(format_check_data[1], format_check_data[3]) == nil + or dictdiff(format_check_data[2], format_check_data[3]) == nil) then + diffs[flags] = nil + else + diffs_num = diffs_num + 1 + end + else + diffs_num = diffs_num + 1 + end + end + end + end + if diffs_num ~= 0 then + print('}, {') + local flags = 1 + while diffs_num ~= 0 do + if diffs[flags] then + diffs_num = diffs_num - 1 + local diff = diffs[flags] + print((' [%u] = {'):format(flags)) + if diff.ast then + print(' ast = ' .. format_luav(diff.ast, ' ')) + end + if diff.hl_fs then + print(' hl_fs = ' .. format_luav(diff.hl_fs, ' ', { + literal_strings=true + })) + end + print(' },') + end + flags = flags + 1 + end end print('})') end @@ -387,8 +439,7 @@ end) describe('Expressions parser', function() local function check_parsing(str, exp_ast, exp_highlighting_fs, nz_flags_exps) nz_flags_exps = nz_flags_exps or {} - local format_check_data = function() - end + local format_check_data = {} for _, flags in ipairs({0, 1, 2, 3}) do debug_log(('Running test case (%s, %u)'):format(str, flags)) local err, msg = pcall(function() @@ -418,7 +469,7 @@ describe('Expressions parser', function() end end if exp_ast == nil then - format_check(str, ast, hls) + format_check_data[flags] = {ast=ast, hl_fs=hls_to_hl_fs(hls)} else eq(exps.ast, ast) if exp_highlighting_fs then @@ -441,6 +492,9 @@ describe('Expressions parser', function() error(msg) end end + if exp_ast == nil then + format_check(str, format_check_data) + end end local function hl(group, str, shift) return function(next_col) -- cgit From 342239a9c53cf4857d18c0583d4cab1fdca534fa Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 13 Nov 2017 01:10:39 +0300 Subject: unittests,viml/parser/expressions: Start adding asgn parsing tests --- src/nvim/viml/parser/expressions.c | 50 +++-- test/helpers.lua | 53 ++++- test/symbolic/klee/viml_expressions_parser.c | 14 ++ test/unit/viml/expressions/parser_spec.lua | 280 ++++++++++++++++++++++++--- 4 files changed, 348 insertions(+), 49 deletions(-) diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index 07bac89997..4bd3652292 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -1363,7 +1363,6 @@ static inline ParserPosition recol_pos(const ParserPosition pos, } \ } while (0) -// TODO(ZyX-I): actual condition /// Check whether it is possible to have next expression after current /// /// For :echo: `:echo @a @a` is a valid expression. `:echo (@a @a)` is not. @@ -1901,6 +1900,7 @@ ExprAST viml_pexpr_parse(ParserState *const pstate, const int flags) bool highlighted_prev_spacing = false; // Lambda node, valid when parsing lambda arguments only. ExprASTNode *lambda_node = NULL; + size_t asgn_level = 0; do { const bool is_concat_or_subscript = ( want_node == kENodeValue @@ -2063,6 +2063,9 @@ viml_pexpr_parse_process_token: && tok_type != kExprLexDot && (tok_type != kExprLexComma || !is_single_assignment) && tok_type != kExprLexAssignment) { + if (flags & kExprFlagsMulti && MAY_HAVE_NEXT_EXPR) { + goto viml_pexpr_parse_end; + } ERROR_FROM_TOKEN_AND_MSG( cur_token, _("E15: Expected assignment operator or subscript: %.*s")); @@ -2429,6 +2432,10 @@ viml_pexpr_parse_valid_colon: ExprASTNode *new_top_node = *new_top_node_p; switch (new_top_node->type) { case kExprNodeListLiteral: { + if (pt_is_assignment(cur_pt) && new_top_node->children == NULL) { + ERROR_FROM_TOKEN_AND_MSG( + cur_token, _("E475: Unable to assign to empty list: %.*s")); + } HL_CUR_TOKEN(List); break; } @@ -2447,14 +2454,18 @@ viml_pexpr_parse_bracket_closing_error: } kvi_push(ast_stack, new_top_node_p); want_node = kENodeOperator; - if (cur_pt == kEPTSingleAssignment) { - kv_drop(pt_stack, 1); - } else if (cur_pt == kEPTAssignment) { - assert(ast.err.msg); - } else if (cur_pt == kEPTExpr - && kv_size(pt_stack) > 1 - && pt_is_assignment(kv_Z(pt_stack, 1))) { - kv_drop(pt_stack, 1); + if (kv_size(ast_stack) <= asgn_level) { + assert(kv_size(ast_stack) == asgn_level); + asgn_level = 0; + if (cur_pt == kEPTSingleAssignment) { + kv_drop(pt_stack, 1); + } else if (cur_pt == kEPTAssignment) { + assert(ast.err.msg); + } else if (cur_pt == kEPTExpr + && kv_size(pt_stack) > 1 + && pt_is_assignment(kv_Z(pt_stack, 1))) { + kv_drop(pt_stack, 1); + } } } else { if (want_node == kENodeValue) { @@ -2480,6 +2491,7 @@ viml_pexpr_parse_bracket_closing_error: ADD_OP_NODE(cur_node); HL_CUR_TOKEN(SubscriptBracket); if (pt_is_assignment(cur_pt)) { + asgn_level = kv_size(ast_stack); kvi_push(pt_stack, kEPTExpr); } } @@ -2586,10 +2598,14 @@ viml_pexpr_parse_figure_brace_closing_error: } kvi_push(ast_stack, new_top_node_p); want_node = kENodeOperator; - if (cur_pt == kEPTExpr - && kv_size(pt_stack) > 1 - && pt_is_assignment(kv_Z(pt_stack, 1))) { - kv_drop(pt_stack, 1); + if (kv_size(ast_stack) <= asgn_level) { + assert(kv_size(ast_stack) == asgn_level); + if (cur_pt == kEPTExpr + && kv_size(pt_stack) > 1 + && pt_is_assignment(kv_Z(pt_stack, 1))) { + kv_drop(pt_stack, 1); + asgn_level = 0; + } } } else { if (want_node == kENodeValue) { @@ -2635,6 +2651,10 @@ viml_pexpr_parse_figure_brace_closing_error: } while (0), Curly); } + if (pt_is_assignment(cur_pt) + && !pt_is_assignment(kv_last(pt_stack))) { + asgn_level = kv_size(ast_stack); + } } break; } @@ -2755,6 +2775,10 @@ viml_pexpr_parse_figure_brace_closing_error: case kExprLexDot: { ADD_VALUE_IF_MISSING(_("E15: Unexpected dot: %.*s")); if (prev_token.type == kExprLexSpacing) { + if (cur_pt == kEPTAssignment) { + ERROR_FROM_TOKEN_AND_MSG( + cur_token, _("E15: Cannot concatenate in assignments: %.*s")); + } NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeConcat); HL_CUR_TOKEN(Concat); } else { diff --git a/test/helpers.lua b/test/helpers.lua index 6c6611d061..ada690a4d2 100644 --- a/test/helpers.lua +++ b/test/helpers.lua @@ -264,6 +264,9 @@ local function which(exe) end local function shallowcopy(orig) + if type(orig) ~= 'table' then + return orig + end local copy = {} for orig_key, orig_value in pairs(orig) do copy[orig_key] = orig_value @@ -312,7 +315,7 @@ end -- dictdiff: find a diff so that mergedicts_copy(d1, diff) is equal to d2 -- --- Note: does not copy values from d2 +-- Note: does not do copies of d2 values used. local function dictdiff(d1, d2) local ret = {} local hasdiff = false @@ -321,7 +324,7 @@ local function dictdiff(d1, d2) hasdiff = true ret[k] = REMOVE_THIS elseif type(v) == type(d2[k]) then - if type(v) == 'table' and type(d2[k]) == 'table' then + if type(v) == 'table' then local subdiff = dictdiff(v, d2[k]) if subdiff ~= nil then hasdiff = true @@ -329,14 +332,16 @@ local function dictdiff(d1, d2) end elseif v ~= d2[k] then ret[k] = d2[k] + hasdiff = true end else ret[k] = d2[k] + hasdiff = true end end for k, v in pairs(d2) do if d1[k] == nil then - ret[k] = v + ret[k] = shallowcopy(v) hasdiff = true end end @@ -406,13 +411,18 @@ format_luav = function(v, indent, opts) opts = opts or {} local linesep = '\n' local next_indent_arg = nil + local indent_shift = opts.indent_shift or ' ' + local next_indent + local nl = '\n' if indent == nil then indent = '' linesep = '' + next_indent = '' + nl = ' ' else - next_indent_arg = indent .. ' ' + next_indent_arg = indent .. indent_shift + next_indent = indent .. indent_shift end - local next_indent = indent .. ' ' local ret = '' if type(v) == 'string' then if opts.literal_strings then @@ -430,10 +440,12 @@ format_luav = function(v, indent, opts) else local processed_keys = {} ret = '{' .. linesep + local non_empty = false for i, subv in ipairs(v) do - ret = ('%s%s%s,\n'):format(ret, next_indent, - format_luav(subv, next_indent_arg, opts)) + ret = ('%s%s%s,%s'):format(ret, next_indent, + format_luav(subv, next_indent_arg, opts), nl) processed_keys[i] = true + non_empty = true end for k, subv in pairs(v) do if not processed_keys[k] then @@ -443,9 +455,13 @@ format_luav = function(v, indent, opts) ret = ('%s%s[%s] = '):format(ret, next_indent, format_luav(k, nil, opts)) end - ret = ret .. format_luav(subv, next_indent_arg, opts) .. ',\n' + ret = ret .. format_luav(subv, next_indent_arg, opts) .. ',' .. nl + non_empty = true end end + if nl == ' ' and non_empty then + ret = ret:sub(1, -3) + end ret = ret .. indent .. '}' end elseif type(v) == 'number' then @@ -495,6 +511,25 @@ local function intchar2lua(ch) return (20 <= ch and ch < 127) and ('%c'):format(ch) or ch end +local fixtbl_metatable = { + __newindex = function() + assert(false) + end, +} + +local function fixtbl(tbl) + return setmetatable(tbl, fixtbl_metatable) +end + +local function fixtbl_rec(tbl) + for k, v in pairs(tbl) do + if type(v) == 'table' then + fixtbl_rec(v) + end + end + return fixtbl(tbl) +end + return { eq = eq, neq = neq, @@ -519,4 +554,6 @@ return { format_string = format_string, intchar2lua = intchar2lua, updated = updated, + fixtbl = fixtbl, + fixtbl_rec = fixtbl_rec, } diff --git a/test/symbolic/klee/viml_expressions_parser.c b/test/symbolic/klee/viml_expressions_parser.c index fd75e8d355..9a876ed3fa 100644 --- a/test/symbolic/klee/viml_expressions_parser.c +++ b/test/symbolic/klee/viml_expressions_parser.c @@ -93,6 +93,20 @@ int main(const int argc, const char *const *const argv, const ExprAST ast = viml_pexpr_parse(&pstate, (int)flags); assert(ast.root != NULL || ast.err.msg); + if (flags & kExprFlagsParseLet) { + assert(ast.err.msg != NULL + || ast.root->type == kExprNodeAssignment + || (ast.root->type == kExprNodeListLiteral + && ast.root->children != NULL) + || ast.root->type == kExprNodeComplexIdentifier + || ast.root->type == kExprNodeCurlyBracesIdentifier + || ast.root->type == kExprNodePlainIdentifier + || ast.root->type == kExprNodeRegister + || ast.root->type == kExprNodeEnvironment + || ast.root->type == kExprNodeOption + || ast.root->type == kExprNodeSubscript + || ast.root->type == kExprNodeConcatOrSubscript); + } // Can’t possibly have more highlight tokens then there are bytes in string. assert(kv_size(colors) <= INPUT_SIZE - shift); kvi_destroy(colors); diff --git a/test/unit/viml/expressions/parser_spec.lua b/test/unit/viml/expressions/parser_spec.lua index 25a41518eb..d6d8dd0807 100644 --- a/test/unit/viml/expressions/parser_spec.lua +++ b/test/unit/viml/expressions/parser_spec.lua @@ -202,12 +202,20 @@ local function hls_to_hl_fs(hls) return ret end -local function format_check(expr, format_check_data) +local function format_check(expr, format_check_data, opts) -- That forces specific order. - local zdata = format_check_data[0] - print(format_string('\ncheck_parsing(%r, {', expr, flags)) - local digits = ' -- ' - local digits2 = ' -- ' + local zflags = opts.flags[1] + local zdata = format_check_data[zflags] + local dig_len = 0 + if opts.funcname then + print(format_string('\n%s(%r, {', opts.funcname, expr)) + dig_len = #opts.funcname + 2 + else + print(format_string('\n_check_parsing(%r, %r, {', opts, expr)) + dig_len = #('_check_parsing(, \'') + #(format_string('%r', opts)) + end + local digits = ' --' .. (' '):rep(dig_len - #(' --')) + local digits2 = digits:sub(1, -10) for i = 0, #expr - 1 do if i % 10 == 0 then digits2 = ('%s%10u'):format(digits2, i / 10) @@ -232,12 +240,15 @@ local function format_check(expr, format_check_data) local diffs = {} local diffs_num = 0 for flags, v in pairs(format_check_data) do - if flags ~= 0 then + if flags ~= zflags then diffs[flags] = dictdiff(zdata, v) if diffs[flags] then - if flags == 3 then - if (dictdiff(format_check_data[1], format_check_data[3]) == nil - or dictdiff(format_check_data[2], format_check_data[3]) == nil) then + if flags == 3 + zflags then + if (dictdiff(format_check_data[1 + zflags], + format_check_data[3 + zflags]) == nil + or dictdiff(format_check_data[2 + zflags], + format_check_data[3 + zflags]) == nil) + then diffs[flags] = nil else diffs_num = diffs_num + 1 @@ -437,10 +448,12 @@ child_call_once(function() end) describe('Expressions parser', function() - local function check_parsing(str, exp_ast, exp_highlighting_fs, nz_flags_exps) + local function _check_parsing(opts, str, exp_ast, exp_highlighting_fs, + nz_flags_exps) + local zflags = opts.flags[1] nz_flags_exps = nz_flags_exps or {} local format_check_data = {} - for _, flags in ipairs({0, 1, 2, 3}) do + for _, flags in ipairs(opts.flags) do debug_log(('Running test case (%s, %u)'):format(str, flags)) local err, msg = pcall(function() if os.getenv('NVIM_TEST_PARSER_SPEC_PRINT_TEST_CASE') == '1' then @@ -452,25 +465,25 @@ describe('Expressions parser', function() local east = lib.viml_pexpr_parse(pstate, flags) local ast = east2lua(pstate, east) local hls = phl2lua(pstate) - local exps = { - ast = exp_ast, - hl_fs = exp_highlighting_fs, - } - local add_exps = nz_flags_exps[flags] - if not add_exps and flags == 3 then - add_exps = nz_flags_exps[1] or nz_flags_exps[2] - end - if add_exps then - if add_exps.ast then - exps.ast = mergedicts_copy(exps.ast, add_exps.ast) - end - if add_exps.hl_fs then - exps.hl_fs = mergedicts_copy(exps.hl_fs, add_exps.hl_fs) - end - end if exp_ast == nil then format_check_data[flags] = {ast=ast, hl_fs=hls_to_hl_fs(hls)} else + local exps = { + ast = exp_ast, + hl_fs = exp_highlighting_fs, + } + local add_exps = nz_flags_exps[flags] + if not add_exps and flags == 3 + zflags then + add_exps = nz_flags_exps[1 + zflags] or nz_flags_exps[2 + zflags] + end + if add_exps then + if add_exps.ast then + exps.ast = mergedicts_copy(exps.ast, add_exps.ast) + end + if add_exps.hl_fs then + exps.hl_fs = mergedicts_copy(exps.hl_fs, add_exps.hl_fs) + end + end eq(exps.ast, ast) if exp_highlighting_fs then local exp_highlighting = {} @@ -493,9 +506,18 @@ describe('Expressions parser', function() end end if exp_ast == nil then - format_check(str, format_check_data) + format_check(str, format_check_data, opts) end end + local function check_parsing(...) + return _check_parsing({flags={0, 1, 2, 3}, funcname='check_parsing'}, ...) + end + local function check_asgn_parsing(...) + return _check_parsing({ + flags={4, 5, 6, 7}, + funcname='check_asgn_parsing', + }, ...) + end local function hl(group, str, shift) return function(next_col) if nvim_hl_defs['NVim' .. group] == nil then @@ -7694,6 +7716,208 @@ describe('Expressions parser', function() }, }) end) + itp('works with assignments', function() + check_asgn_parsing('a=b', { + -- 012 + ast = { + { + 'Assignment(Plain):0:1:=', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainIdentifier(scope=0,ident=b):0:2:b', + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('PlainAssignment', '='), + hl('IdentifierName', 'b'), + }) + + check_asgn_parsing('a+=b', { + -- 0123 + ast = { + { + 'Assignment(Add):0:1:+=', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainIdentifier(scope=0,ident=b):0:3:b', + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('AssignmentWithAddition', '+='), + hl('IdentifierName', 'b'), + }) + + check_asgn_parsing('a-=b', { + -- 0123 + ast = { + { + 'Assignment(Subtract):0:1:-=', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainIdentifier(scope=0,ident=b):0:3:b', + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('AssignmentWithSubtraction', '-='), + hl('IdentifierName', 'b'), + }) + + check_asgn_parsing('a.=b', { + -- 0123 + ast = { + { + 'Assignment(Concat):0:1:.=', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainIdentifier(scope=0,ident=b):0:3:b', + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('AssignmentWithConcatenation', '.='), + hl('IdentifierName', 'b'), + }) + + check_asgn_parsing('a', { + -- 0 + ast = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + }, + }, { + hl('IdentifierName', 'a'), + }) + + check_asgn_parsing('a b', { + -- 012 + ast = { + { + 'OpMissing:0:1:', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainIdentifier(scope=0,ident=b):0:1: b', + }, + }, + }, + err = { + arg = 'b', + msg = 'E15: Expected assignment operator or subscript: %.*s', + }, + }, { + hl('IdentifierName', 'a'), + hl('InvalidSpacing', ' '), + hl('IdentifierName', 'b'), + }, { + [5] = { + ast = { + ast = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + }, + err = REMOVE_THIS, + }, + hl_fs = { + [2] = REMOVE_THIS, + [3] = REMOVE_THIS, + } + }, + }) + + check_asgn_parsing('[a, b, c]', { + -- 012345678 + ast = { + { + 'ListLiteral:0:0:[', + children = { + { + 'Comma:0:2:,', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + { + 'Comma:0:5:,', + children = { + 'PlainIdentifier(scope=0,ident=b):0:3: b', + 'PlainIdentifier(scope=0,ident=c):0:6: c', + }, + }, + }, + }, + }, + }, + }, + }, { + hl('List', '['), + hl('IdentifierName', 'a'), + hl('Comma', ','), + hl('IdentifierName', 'b', 1), + hl('Comma', ','), + hl('IdentifierName', 'c', 1), + hl('List', ']'), + }) + + check_asgn_parsing('[a, b]', { + -- 012345 + ast = { + { + 'ListLiteral:0:0:[', + children = { + { + 'Comma:0:2:,', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + 'PlainIdentifier(scope=0,ident=b):0:3: b', + }, + }, + }, + }, + }, + }, { + hl('List', '['), + hl('IdentifierName', 'a'), + hl('Comma', ','), + hl('IdentifierName', 'b', 1), + hl('List', ']'), + }) + + check_asgn_parsing('[a]', { + -- 012 + ast = { + { + 'ListLiteral:0:0:[', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + }, + }, + }, + }, { + hl('List', '['), + hl('IdentifierName', 'a'), + hl('List', ']'), + }) + + check_asgn_parsing('[]', { + -- 01 + ast = { + 'ListLiteral:0:0:[', + }, + err = { + arg = ']', + msg = 'E475: Unable to assign to empty list: %.*s', + }, + }, { + hl('List', '['), + hl('InvalidList', ']'), + }) + + -- check_asgn_parsing('a[1 + 2] += 3') + -- check_asgn_parsing('a[{-> {b{3}: 4}[5]}()] += 6') + -- check_asgn_parsing('a{1}.2[{-> {b{3}: 4}[5]}()]') + end) -- FIXME: Test assignments thoroughly -- FIXME: Test that parsing assignments can be used for `:for` pre-`in` part. -- FIXME: Somehow make functional tests use the same code. Or, at least, -- cgit From c287893225bad586af486b37546f5982e5b1cd03 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 19 Nov 2017 19:22:54 +0300 Subject: viml/parser/expressions,unittests: Do better testing, fix found issues --- src/nvim/viml/parser/expressions.c | 40 +- test/functional/ui/cmdline_highlight_spec.lua | 1 + test/unit/viml/expressions/parser_spec.lua | 771 +++++++++++++++++++++++++- 3 files changed, 786 insertions(+), 26 deletions(-) diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index 4bd3652292..0824b3ca7d 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -2031,18 +2031,7 @@ viml_pexpr_parse_process_token: } break; } - case kEPTSingleAssignment: { - if (tok_type == kExprLexBracket && !cur_token.data.brc.closing) { - ERROR_FROM_TOKEN_AND_MSG( - cur_token, - _("E475: Nested lists not allowed when assigning: %.*s")); - kv_drop(pt_stack, 2); - assert(kv_size(pt_stack)); - assert(kv_last(pt_stack) == kEPTExpr); - break; - } - FALLTHROUGH; - } + case kEPTSingleAssignment: case kEPTAssignment: { if (want_node == kENodeValue && tok_type != kExprLexBracket @@ -2062,7 +2051,13 @@ viml_pexpr_parse_process_token: || cur_token.data.brc.closing) && tok_type != kExprLexDot && (tok_type != kExprLexComma || !is_single_assignment) - && tok_type != kExprLexAssignment) { + && tok_type != kExprLexAssignment + // Curly brace identifiers: will contain plain identifier or + // another curly brace in position where operator is wanted. + && !((tok_type == kExprLexPlainIdentifier + || (tok_type == kExprLexFigureBrace + && !cur_token.data.brc.closing)) + && prev_token.type != kExprLexSpacing)) { if (flags & kExprFlagsMulti && MAY_HAVE_NEXT_EXPR) { goto viml_pexpr_parse_end; } @@ -2457,9 +2452,7 @@ viml_pexpr_parse_bracket_closing_error: if (kv_size(ast_stack) <= asgn_level) { assert(kv_size(ast_stack) == asgn_level); asgn_level = 0; - if (cur_pt == kEPTSingleAssignment) { - kv_drop(pt_stack, 1); - } else if (cur_pt == kEPTAssignment) { + if (cur_pt == kEPTAssignment) { assert(ast.err.msg); } else if (cur_pt == kEPTExpr && kv_size(pt_stack) > 1 @@ -2467,10 +2460,12 @@ viml_pexpr_parse_bracket_closing_error: kv_drop(pt_stack, 1); } } + if (cur_pt == kEPTSingleAssignment && kv_size(ast_stack) == 1) { + kv_drop(pt_stack, 1); + } } else { if (want_node == kENodeValue) { // Value means list literal or list assignment. - HL_CUR_TOKEN(List); NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeListLiteral); *top_node_p = cur_node; kvi_push(ast_stack, &cur_node->children); @@ -2479,7 +2474,12 @@ viml_pexpr_parse_bracket_closing_error: // Additional assignment parse type allows to easily forbid nested // lists. kvi_push(pt_stack, kEPTSingleAssignment); + } else if (cur_pt == kEPTSingleAssignment) { + ERROR_FROM_TOKEN_AND_MSG( + cur_token, + _("E475: Nested lists not allowed when assigning: %.*s")); } + HL_CUR_TOKEN(List); } else { // Operator means subscript, also in assignment. But in assignment // subscript may be pretty much any expression, so need to push @@ -2491,7 +2491,8 @@ viml_pexpr_parse_bracket_closing_error: ADD_OP_NODE(cur_node); HL_CUR_TOKEN(SubscriptBracket); if (pt_is_assignment(cur_pt)) { - asgn_level = kv_size(ast_stack); + assert(want_node == kENodeValue); // Subtract 1 for NULL at top. + asgn_level = kv_size(ast_stack) - 1; kvi_push(pt_stack, kEPTExpr); } } @@ -2653,7 +2654,8 @@ viml_pexpr_parse_figure_brace_closing_error: } if (pt_is_assignment(cur_pt) && !pt_is_assignment(kv_last(pt_stack))) { - asgn_level = kv_size(ast_stack); + assert(want_node == kENodeValue); // Subtract 1 for NULL at top. + asgn_level = kv_size(ast_stack) - 1; } } break; diff --git a/test/functional/ui/cmdline_highlight_spec.lua b/test/functional/ui/cmdline_highlight_spec.lua index ab195f9f1e..023673738d 100644 --- a/test/functional/ui/cmdline_highlight_spec.lua +++ b/test/functional/ui/cmdline_highlight_spec.lua @@ -901,4 +901,5 @@ describe('Expressions coloring support', function() -- FIXME: Test expr coloring when using -u NORC and -u NONE. -- FIXME: Test different ways of triggering expression highlighting (:=, -- i=, :e, "=). + -- FIXME: Test with various invalid unicode and multibyte characters. end) diff --git a/test/unit/viml/expressions/parser_spec.lua b/test/unit/viml/expressions/parser_spec.lua index d6d8dd0807..eca76aa9cd 100644 --- a/test/unit/viml/expressions/parser_spec.lua +++ b/test/unit/viml/expressions/parser_spec.lua @@ -268,12 +268,12 @@ local function format_check(expr, format_check_data, opts) local diff = diffs[flags] print((' [%u] = {'):format(flags)) if diff.ast then - print(' ast = ' .. format_luav(diff.ast, ' ')) + print(' ast = ' .. format_luav(diff.ast, ' ') .. ',') end if diff.hl_fs then print(' hl_fs = ' .. format_luav(diff.hl_fs, ' ', { literal_strings=true - })) + }) .. ',') end print(' },') end @@ -7914,12 +7914,769 @@ describe('Expressions parser', function() hl('InvalidList', ']'), }) - -- check_asgn_parsing('a[1 + 2] += 3') - -- check_asgn_parsing('a[{-> {b{3}: 4}[5]}()] += 6') - -- check_asgn_parsing('a{1}.2[{-> {b{3}: 4}[5]}()]') + check_asgn_parsing('a[1] += 3', { + -- 012345678 + ast = { + { + 'Assignment(Add):0:4: +=', + children = { + { + 'Subscript:0:1:[', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'Integer(val=1):0:2:1', + }, + }, + 'Integer(val=3):0:7: 3', + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('SubscriptBracket', '['), + hl('Number', '1'), + hl('SubscriptBracket', ']'), + hl('AssignmentWithAddition', '+=', 1), + hl('Number', '3', 1), + }) + + check_asgn_parsing('a[1 + 2] += 3', { + -- 0123456789012 + -- 0 1 + ast = { + { + 'Assignment(Add):0:8: +=', + children = { + { + 'Subscript:0:1:[', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'BinaryPlus:0:3: +', + children = { + 'Integer(val=1):0:2:1', + 'Integer(val=2):0:5: 2', + }, + }, + }, + }, + 'Integer(val=3):0:11: 3', + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('SubscriptBracket', '['), + hl('Number', '1'), + hl('BinaryPlus', '+', 1), + hl('Number', '2', 1), + hl('SubscriptBracket', ']'), + hl('AssignmentWithAddition', '+=', 1), + hl('Number', '3', 1), + }) + + check_asgn_parsing('a[{-> {b{3}: 4}[5]}()] += 6', { + -- 012345678901234567890123456 + -- 0 1 2 + ast = { + { + 'Assignment(Add):0:22: +=', + children = { + { + 'Subscript:0:1:[', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'Call:0:19:(', + children = { + { + 'Lambda(\\di):0:2:{', + children = { + { + 'Arrow:0:3:->', + children = { + { + 'Subscript:0:15:[', + children = { + { + 'DictLiteral(-di):0:5: {', + children = { + { + 'Colon:0:11::', + children = { + { + 'ComplexIdentifier:0:8:', + children = { + 'PlainIdentifier(scope=0,ident=b):0:7:b', + { + 'CurlyBracesIdentifier(--i):0:8:{', + children = { + 'Integer(val=3):0:9:3', + }, + }, + }, + }, + 'Integer(val=4):0:12: 4', + }, + }, + }, + }, + 'Integer(val=5):0:16:5', + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + 'Integer(val=6):0:25: 6', + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('SubscriptBracket', '['), + hl('Lambda', '{'), + hl('Arrow', '->'), + hl('Dict', '{', 1), + hl('IdentifierName', 'b'), + hl('Curly', '{'), + hl('Number', '3'), + hl('Curly', '}'), + hl('Colon', ':'), + hl('Number', '4', 1), + hl('Dict', '}'), + hl('SubscriptBracket', '['), + hl('Number', '5'), + hl('SubscriptBracket', ']'), + hl('Lambda', '}'), + hl('CallingParenthesis', '('), + hl('CallingParenthesis', ')'), + hl('SubscriptBracket', ']'), + hl('AssignmentWithAddition', '+=', 1), + hl('Number', '6', 1), + }) + + check_asgn_parsing('a{1}.2[{-> {b{3}: 4}[5]}()]', { + -- 012345678901234567890123456 + -- 0 1 2 + ast = { + { + 'Subscript:0:6:[', + children = { + { + 'ConcatOrSubscript:0:4:.', + children = { + { + 'ComplexIdentifier:0:1:', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'CurlyBracesIdentifier(--i):0:1:{', + children = { + 'Integer(val=1):0:2:1', + }, + }, + }, + }, + 'PlainKey(key=2):0:5:2', + }, + }, + { + 'Call:0:24:(', + children = { + { + 'Lambda(\\di):0:7:{', + children = { + { + 'Arrow:0:8:->', + children = { + { + 'Subscript:0:20:[', + children = { + { + 'DictLiteral(-di):0:10: {', + children = { + { + 'Colon:0:16::', + children = { + { + 'ComplexIdentifier:0:13:', + children = { + 'PlainIdentifier(scope=0,ident=b):0:12:b', + { + 'CurlyBracesIdentifier(--i):0:13:{', + children = { + 'Integer(val=3):0:14:3', + }, + }, + }, + }, + 'Integer(val=4):0:17: 4', + }, + }, + }, + }, + 'Integer(val=5):0:21:5', + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('Curly', '{'), + hl('Number', '1'), + hl('Curly', '}'), + hl('ConcatOrSubscript', '.'), + hl('IdentifierKey', '2'), + hl('SubscriptBracket', '['), + hl('Lambda', '{'), + hl('Arrow', '->'), + hl('Dict', '{', 1), + hl('IdentifierName', 'b'), + hl('Curly', '{'), + hl('Number', '3'), + hl('Curly', '}'), + hl('Colon', ':'), + hl('Number', '4', 1), + hl('Dict', '}'), + hl('SubscriptBracket', '['), + hl('Number', '5'), + hl('SubscriptBracket', ']'), + hl('Lambda', '}'), + hl('CallingParenthesis', '('), + hl('CallingParenthesis', ')'), + hl('SubscriptBracket', ']'), + }) + + check_asgn_parsing('a', { + -- 0 + ast = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + }, + }, { + hl('IdentifierName', 'a'), + }) + + check_asgn_parsing('{a}', { + -- 012 + ast = { + { + 'CurlyBracesIdentifier(--i):0:0:{', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + }, + }, + }, + }, { + hl('FigureBrace', '{'), + hl('IdentifierName', 'a'), + hl('Curly', '}'), + }) + + check_asgn_parsing('{a}b', { + -- 0123 + ast = { + { + 'ComplexIdentifier:0:3:', + children = { + { + 'CurlyBracesIdentifier(--i):0:0:{', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + }, + }, + 'PlainIdentifier(scope=0,ident=b):0:3:b', + }, + }, + }, + }, { + hl('FigureBrace', '{'), + hl('IdentifierName', 'a'), + hl('Curly', '}'), + hl('IdentifierName', 'b'), + }) + + check_asgn_parsing('a{b}c', { + -- 01234 + ast = { + { + 'ComplexIdentifier:0:1:', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'ComplexIdentifier:0:4:', + children = { + { + 'CurlyBracesIdentifier(--i):0:1:{', + children = { + 'PlainIdentifier(scope=0,ident=b):0:2:b', + }, + }, + 'PlainIdentifier(scope=0,ident=c):0:4:c', + }, + }, + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('Curly', '{'), + hl('IdentifierName', 'b'), + hl('Curly', '}'), + hl('IdentifierName', 'c'), + }) + + check_asgn_parsing('a{b}c[0]', { + -- 01234567 + ast = { + { + 'Subscript:0:5:[', + children = { + { + 'ComplexIdentifier:0:1:', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'ComplexIdentifier:0:4:', + children = { + { + 'CurlyBracesIdentifier(--i):0:1:{', + children = { + 'PlainIdentifier(scope=0,ident=b):0:2:b', + }, + }, + 'PlainIdentifier(scope=0,ident=c):0:4:c', + }, + }, + }, + }, + 'Integer(val=0):0:6:0', + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('Curly', '{'), + hl('IdentifierName', 'b'), + hl('Curly', '}'), + hl('IdentifierName', 'c'), + hl('SubscriptBracket', '['), + hl('Number', '0'), + hl('SubscriptBracket', ']'), + }) + + check_asgn_parsing('a{b}c.0', { + -- 0123456 + ast = { + { + 'ConcatOrSubscript:0:5:.', + children = { + { + 'ComplexIdentifier:0:1:', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'ComplexIdentifier:0:4:', + children = { + { + 'CurlyBracesIdentifier(--i):0:1:{', + children = { + 'PlainIdentifier(scope=0,ident=b):0:2:b', + }, + }, + 'PlainIdentifier(scope=0,ident=c):0:4:c', + }, + }, + }, + }, + 'PlainKey(key=0):0:6:0', + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('Curly', '{'), + hl('IdentifierName', 'b'), + hl('Curly', '}'), + hl('IdentifierName', 'c'), + hl('ConcatOrSubscript', '.'), + hl('IdentifierKey', '0'), + }) + + check_asgn_parsing('[a{b}c[0].0]', { + -- 012345678901 + -- 0 1 + ast = { + { + 'ListLiteral:0:0:[', + children = { + { + 'ConcatOrSubscript:0:9:.', + children = { + { + 'Subscript:0:6:[', + children = { + { + 'ComplexIdentifier:0:2:', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + { + 'ComplexIdentifier:0:5:', + children = { + { + 'CurlyBracesIdentifier(--i):0:2:{', + children = { + 'PlainIdentifier(scope=0,ident=b):0:3:b', + }, + }, + 'PlainIdentifier(scope=0,ident=c):0:5:c', + }, + }, + }, + }, + 'Integer(val=0):0:7:0', + }, + }, + 'PlainKey(key=0):0:10:0', + }, + }, + }, + }, + }, + }, { + hl('List', '['), + hl('IdentifierName', 'a'), + hl('Curly', '{'), + hl('IdentifierName', 'b'), + hl('Curly', '}'), + hl('IdentifierName', 'c'), + hl('SubscriptBracket', '['), + hl('Number', '0'), + hl('SubscriptBracket', ']'), + hl('ConcatOrSubscript', '.'), + hl('IdentifierKey', '0'), + hl('List', ']'), + }) + + check_asgn_parsing('{a}{b}', { + -- 012345 + ast = { + { + 'ComplexIdentifier:0:3:', + children = { + { + 'CurlyBracesIdentifier(--i):0:0:{', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + }, + }, + { + 'CurlyBracesIdentifier(--i):0:3:{', + children = { + 'PlainIdentifier(scope=0,ident=b):0:4:b', + }, + }, + }, + }, + }, + }, { + hl('FigureBrace', '{'), + hl('IdentifierName', 'a'), + hl('Curly', '}'), + hl('Curly', '{'), + hl('IdentifierName', 'b'), + hl('Curly', '}'), + }) + + check_asgn_parsing('a.b{c}{d}', { + -- 012345678 + ast = { + { + 'OpMissing:0:3:', + children = { + { + 'ConcatOrSubscript:0:1:.', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainKey(key=b):0:2:b', + }, + }, + { + 'ComplexIdentifier:0:6:', + children = { + { + 'CurlyBracesIdentifier(--i):0:3:{', + children = { + 'PlainIdentifier(scope=0,ident=c):0:4:c', + }, + }, + { + 'CurlyBracesIdentifier(--i):0:6:{', + children = { + 'PlainIdentifier(scope=0,ident=d):0:7:d', + }, + }, + }, + }, + }, + }, + }, + err = { + arg = '{c}{d}', + msg = 'E15: Missing operator: %.*s', + }, + }, { + hl('IdentifierName', 'a'), + hl('ConcatOrSubscript', '.'), + hl('IdentifierKey', 'b'), + hl('InvalidFigureBrace', '{'), + hl('IdentifierName', 'c'), + hl('Curly', '}'), + hl('Curly', '{'), + hl('IdentifierName', 'd'), + hl('Curly', '}'), + }) + + check_asgn_parsing('[a] = 1', { + -- 0123456 + ast = { + { + 'Assignment(Plain):0:3: =', + children = { + { + 'ListLiteral:0:0:[', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + }, + }, + 'Integer(val=1):0:5: 1', + }, + }, + }, + }, { + hl('List', '['), + hl('IdentifierName', 'a'), + hl('List', ']'), + hl('PlainAssignment', '=', 1), + hl('Number', '1', 1), + }) + + check_asgn_parsing('[a[b], [c, [d, [e]]]] = 1', { + -- 0123456789012345678901234 + -- 0 1 2 + ast = { + { + 'Assignment(Plain):0:21: =', + children = { + { + 'ListLiteral:0:0:[', + children = { + { + 'Comma:0:5:,', + children = { + { + 'Subscript:0:2:[', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + 'PlainIdentifier(scope=0,ident=b):0:3:b', + }, + }, + { + 'ListLiteral:0:6: [', + children = { + { + 'Comma:0:9:,', + children = { + 'PlainIdentifier(scope=0,ident=c):0:8:c', + { + 'ListLiteral:0:10: [', + children = { + { + 'Comma:0:13:,', + children = { + 'PlainIdentifier(scope=0,ident=d):0:12:d', + { + 'ListLiteral:0:14: [', + children = { + 'PlainIdentifier(scope=0,ident=e):0:16:e', + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + 'Integer(val=1):0:23: 1', + }, + }, + }, + err = { + arg = '[c, [d, [e]]]] = 1', + msg = 'E475: Nested lists not allowed when assigning: %.*s', + }, + }, { + hl('List', '['), + hl('IdentifierName', 'a'), + hl('SubscriptBracket', '['), + hl('IdentifierName', 'b'), + hl('SubscriptBracket', ']'), + hl('Comma', ','), + hl('InvalidList', '[', 1), + hl('IdentifierName', 'c'), + hl('Comma', ','), + hl('InvalidList', '[', 1), + hl('IdentifierName', 'd'), + hl('Comma', ','), + hl('InvalidList', '[', 1), + hl('IdentifierName', 'e'), + hl('List', ']'), + hl('List', ']'), + hl('List', ']'), + hl('List', ']'), + hl('PlainAssignment', '=', 1), + hl('Number', '1', 1), + }) + + check_asgn_parsing('$X += 1', { + -- 0123456 + ast = { + { + 'Assignment(Add):0:2: +=', + children = { + 'Environment(ident=X):0:0:$X', + 'Integer(val=1):0:5: 1', + }, + }, + }, + }, { + hl('EnvironmentSigil', '$'), + hl('EnvironmentName', 'X'), + hl('AssignmentWithAddition', '+=', 1), + hl('Number', '1', 1), + }) + + check_asgn_parsing('@a .= 1', { + -- 0123456 + ast = { + { + 'Assignment(Concat):0:2: .=', + children = { + 'Register(name=a):0:0:@a', + 'Integer(val=1):0:5: 1', + }, + }, + }, + }, { + hl('Register', '@a'), + hl('AssignmentWithConcatenation', '.=', 1), + hl('Number', '1', 1), + }) + + check_asgn_parsing('&option -= 1', { + -- 012345678901 + -- 0 1 + ast = { + { + 'Assignment(Subtract):0:7: -=', + children = { + 'Option(scope=0,ident=option):0:0:&option', + 'Integer(val=1):0:10: 1', + }, + }, + }, + }, { + hl('OptionSigil', '&'), + hl('OptionName', 'option'), + hl('AssignmentWithSubtraction', '-=', 1), + hl('Number', '1', 1), + }) + + check_asgn_parsing('[$X, @a, &l:option] = [1, 2, 3]', { + -- 0123456789012345678901234567890 + -- 0 1 2 3 + ast = { + { + 'Assignment(Plain):0:19: =', + children = { + { + 'ListLiteral:0:0:[', + children = { + { + 'Comma:0:3:,', + children = { + 'Environment(ident=X):0:1:$X', + { + 'Comma:0:7:,', + children = { + 'Register(name=a):0:4: @a', + 'Option(scope=l,ident=option):0:8: &l:option', + }, + }, + }, + }, + }, + }, + { + 'ListLiteral:0:21: [', + children = { + { + 'Comma:0:24:,', + children = { + 'Integer(val=1):0:23:1', + { + 'Comma:0:27:,', + children = { + 'Integer(val=2):0:25: 2', + 'Integer(val=3):0:28: 3', + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, { + hl('List', '['), + hl('EnvironmentSigil', '$'), + hl('EnvironmentName', 'X'), + hl('Comma', ','), + hl('Register', '@a', 1), + hl('Comma', ','), + hl('OptionSigil', '&', 1), + hl('OptionScope', 'l'), + hl('OptionScopeDelimiter', ':'), + hl('OptionName', 'option'), + hl('List', ']'), + hl('PlainAssignment', '=', 1), + hl('List', '[', 1), + hl('Number', '1'), + hl('Comma', ','), + hl('Number', '2', 1), + hl('Comma', ','), + hl('Number', '3', 1), + hl('List', ']'), + }) end) - -- FIXME: Test assignments thoroughly - -- FIXME: Test that parsing assignments can be used for `:for` pre-`in` part. -- FIXME: Somehow make functional tests use the same code. Or, at least, -- create an automated script which will do the import. end) -- cgit From 764cf3251de4f141fa451633dbdb48440ddf218a Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 19 Nov 2017 19:27:21 +0300 Subject: charset: Add missing include needed for vim_str2nr --- src/nvim/charset.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/nvim/charset.h b/src/nvim/charset.h index 7198c8d405..eb64b6128a 100644 --- a/src/nvim/charset.h +++ b/src/nvim/charset.h @@ -4,6 +4,7 @@ #include "nvim/types.h" #include "nvim/pos.h" #include "nvim/buffer_defs.h" +#include "nvim/eval/typval.h" /// Return the folded-case equivalent of the given character /// -- cgit From 53fa435a1f081e1d7e1890e3ecd978b85c39e0eb Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 19 Nov 2017 19:34:15 +0300 Subject: functests: Fix ui/cmdline test --- test/functional/ui/cmdline_spec.lua | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/test/functional/ui/cmdline_spec.lua b/test/functional/ui/cmdline_spec.lua index 0f8302b036..136ab09d4f 100644 --- a/test/functional/ui/cmdline_spec.lua +++ b/test/functional/ui/cmdline_spec.lua @@ -226,7 +226,11 @@ describe('external cmdline', function() prompt = "", special = {'"', true}, },{ - content = { { {}, "1+2" } }, + content = { + { {}, "1" }, + { {}, "+" }, + { {}, "2" }, + }, firstc = "=", indent = 0, pos = 3, @@ -303,7 +307,7 @@ describe('external cmdline', function() pos = 0, prompt = "", }}, cmdline) - eq({{{{}, 'function Foo()'}}}, block) + eq({ { { {}, 'function Foo()'} } }, block) end) feed('line1') @@ -314,8 +318,8 @@ describe('external cmdline', function() ~ | | ]], nil, nil, function() - eq({{{{}, 'function Foo()'}}, - {{{}, ' line1'}}}, block) + eq({ { { {}, 'function Foo()'} }, + { { {}, ' line1'} } }, block) end) block = {} @@ -327,8 +331,8 @@ describe('external cmdline', function() ~ | ^ | ]], nil, nil, function() - eq({{{{}, 'function Foo()'}}, - {{{}, ' line1'}}}, block) + eq({ { { {}, 'function Foo()'} }, + { { {}, ' line1'} } }, block) end) -- cgit From a94255a7ac22649311f858e39b217543d0d7e5e8 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 19 Nov 2017 20:20:06 +0300 Subject: tests: Use single test file for unit and functional parser tests --- test/functional/api/vim_spec.lua | 7132 +---------------------- test/unit/viml/expressions/parser_spec.lua | 8180 +------------------------- test/unit/viml/expressions/parser_tests.lua | 8185 +++++++++++++++++++++++++++ 3 files changed, 8241 insertions(+), 15256 deletions(-) create mode 100644 test/unit/viml/expressions/parser_tests.lua diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index 714b1988fb..3939bc9b52 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -766,6 +766,11 @@ describe('api', function() elseif typ == 'Environment' then typ = ('%s(ident=%s)'):format(typ, east_api_node.ident) east_api_node.ident = nil + elseif typ == 'Assignment' then + local aug = east_api_node.augmentation + if aug == '' then aug = 'Plain' end + typ = ('%s(%s)'):format(typ, aug) + east_api_node.augmentation = nil end typ = ('%s:%u:%u:%s'):format( typ, east_api_node.start[1], east_api_node.start[2], @@ -798,6 +803,9 @@ describe('api', function() end if east_api.ast then east_api.ast = {simplify_east_api_node(line, east_api.ast)} + if #east_api.ast == 0 then + east_api.ast = nil + end end if east_api.len == #line then east_api.len = nil @@ -819,11 +827,19 @@ describe('api', function() [1] = "m", [2] = "E", [3] = "mE", + [4] = "l", + [5] = "lm", + [6] = "lE", + [7] = "lmE", } - local function check_parsing(str, exp_ast, exp_highlighting_fs, - nz_flags_exps) + local function _check_parsing(opts, str, exp_ast, exp_highlighting_fs, + nz_flags_exps) + if type(str) ~= 'string' then + return + end + local zflags = opts.flags[1] nz_flags_exps = nz_flags_exps or {} - for _, flags in ipairs({0, 1, 2, 3}) do + for _, flags in ipairs(opts.flags) do local err, msg = pcall(function() local east_api = meths.parse_expression(str, FLAGS_TO_STR[flags], true) local east_hl = east_api.highlight @@ -835,8 +851,8 @@ describe('api', function() hl_fs = exp_highlighting_fs, } local add_exps = nz_flags_exps[flags] - if not add_exps and flags == 3 then - add_exps = nz_flags_exps[1] or nz_flags_exps[2] + if not add_exps and flags == 3 + zflags then + add_exps = nz_flags_exps[1 + zflags] or nz_flags_exps[2 + zflags] end if add_exps then if add_exps.ast then @@ -873,7096 +889,22 @@ describe('api', function() str)), (col + #str) end end - it('works with + and @a', function() - check_parsing('@a', { - ast = { - 'Register(name=a):0:0:@a', - }, - }, { - hl('Register', '@a'), - }) - check_parsing('+@a', { - ast = { - { - 'UnaryPlus:0:0:+', - children = { - 'Register(name=a):0:1:@a', - }, - }, - }, - }, { - hl('UnaryPlus', '+'), - hl('Register', '@a'), - }) - check_parsing('@a+@b', { - ast = { - { - 'BinaryPlus:0:2:+', - children = { - 'Register(name=a):0:0:@a', - 'Register(name=b):0:3:@b', - }, - }, - }, - }, { - hl('Register', '@a'), - hl('BinaryPlus', '+'), - hl('Register', '@b'), - }) - check_parsing('@a+@b+@c', { - ast = { - { - 'BinaryPlus:0:5:+', - children = { - { - 'BinaryPlus:0:2:+', - children = { - 'Register(name=a):0:0:@a', - 'Register(name=b):0:3:@b', - }, - }, - 'Register(name=c):0:6:@c', - }, - }, - }, - }, { - hl('Register', '@a'), - hl('BinaryPlus', '+'), - hl('Register', '@b'), - hl('BinaryPlus', '+'), - hl('Register', '@c'), - }) - check_parsing('+@a+@b', { - ast = { - { - 'BinaryPlus:0:3:+', - children = { - { - 'UnaryPlus:0:0:+', - children = { - 'Register(name=a):0:1:@a', - }, - }, - 'Register(name=b):0:4:@b', - }, - }, - }, - }, { - hl('UnaryPlus', '+'), - hl('Register', '@a'), - hl('BinaryPlus', '+'), - hl('Register', '@b'), - }) - check_parsing('+@a++@b', { - ast = { - { - 'BinaryPlus:0:3:+', - children = { - { - 'UnaryPlus:0:0:+', - children = { - 'Register(name=a):0:1:@a', - }, - }, - { - 'UnaryPlus:0:4:+', - children = { - 'Register(name=b):0:5:@b', - }, - }, - }, - }, - }, - }, { - hl('UnaryPlus', '+'), - hl('Register', '@a'), - hl('BinaryPlus', '+'), - hl('UnaryPlus', '+'), - hl('Register', '@b'), - }) - check_parsing('@a@b', { - ast = { - { - 'OpMissing:0:2:', - children = { - 'Register(name=a):0:0:@a', - 'Register(name=b):0:2:@b', - }, - }, - }, - err = { - arg = '@b', - msg = 'E15: Missing operator: %.*s', - }, - }, { - hl('Register', '@a'), - hl('InvalidRegister', '@b'), - }, { - [1] = { - ast = { - len = 2, - err = REMOVE_THIS, - ast = { - 'Register(name=a):0:0:@a' - }, - }, - hl_fs = { - [2] = REMOVE_THIS, - }, - }, - }) - check_parsing(' @a \t @b', { - ast = { - { - 'OpMissing:0:3:', - children = { - 'Register(name=a):0:0: @a', - 'Register(name=b):0:3: \t @b', - }, - }, - }, - err = { - arg = '@b', - msg = 'E15: Missing operator: %.*s', - }, - }, { - hl('Register', '@a', 1), - hl('InvalidSpacing', ' \t '), - hl('Register', '@b'), - }, { - [1] = { - ast = { - len = 6, - err = REMOVE_THIS, - ast = { - 'Register(name=a):0:0: @a' - }, - }, - hl_fs = { - [2] = REMOVE_THIS, - [3] = REMOVE_THIS, - }, - }, - }) - check_parsing('+', { - ast = { - 'UnaryPlus:0:0:+', - }, - err = { - arg = '', - msg = 'E15: Expected value, got EOC: %.*s', - }, - }, { - hl('UnaryPlus', '+'), - }) - check_parsing(' +', { - ast = { - 'UnaryPlus:0:0: +', - }, - err = { - arg = '', - msg = 'E15: Expected value, got EOC: %.*s', - }, - }, { - hl('UnaryPlus', '+', 1), - }) - check_parsing('@a+ ', { - ast = { - { - 'BinaryPlus:0:2:+', - children = { - 'Register(name=a):0:0:@a', - }, - }, - }, - err = { - arg = '', - msg = 'E15: Expected value, got EOC: %.*s', - }, - }, { - hl('Register', '@a'), - hl('BinaryPlus', '+'), - }) - end) - it('works with @a, + and parenthesis', function() - check_parsing('(@a)', { - ast = { - { - 'Nested:0:0:(', - children = { - 'Register(name=a):0:1:@a', - }, - }, - }, - }, { - hl('NestingParenthesis', '('), - hl('Register', '@a'), - hl('NestingParenthesis', ')'), - }) - check_parsing('()', { - ast = { - { - 'Nested:0:0:(', - children = { - 'Missing:0:1:', - }, - }, - }, - err = { - arg = ')', - msg = 'E15: Expected value, got parenthesis: %.*s', - }, - }, { - hl('NestingParenthesis', '('), - hl('InvalidNestingParenthesis', ')'), - }) - check_parsing(')', { - ast = { - { - 'Nested:0:0:', - children = { - 'Missing:0:0:', - }, - }, - }, - err = { - arg = ')', - msg = 'E15: Expected value, got parenthesis: %.*s', - }, - }, { - hl('InvalidNestingParenthesis', ')'), - }) - check_parsing('+)', { - ast = { - { - 'Nested:0:1:', - children = { - { - 'UnaryPlus:0:0:+', - children = { - 'Missing:0:1:', - }, - }, - }, - }, - }, - err = { - arg = ')', - msg = 'E15: Expected value, got parenthesis: %.*s', - }, - }, { - hl('UnaryPlus', '+'), - hl('InvalidNestingParenthesis', ')'), - }) - check_parsing('+@a(@b)', { - ast = { - { - 'UnaryPlus:0:0:+', - children = { - { - 'Call:0:3:(', - children = { - 'Register(name=a):0:1:@a', - 'Register(name=b):0:4:@b', - }, - }, - }, - }, - }, - }, { - hl('UnaryPlus', '+'), - hl('Register', '@a'), - hl('CallingParenthesis', '('), - hl('Register', '@b'), - hl('CallingParenthesis', ')'), - }) - check_parsing('@a+@b(@c)', { - ast = { - { - 'BinaryPlus:0:2:+', - children = { - 'Register(name=a):0:0:@a', - { - 'Call:0:5:(', - children = { - 'Register(name=b):0:3:@b', - 'Register(name=c):0:6:@c', - }, - }, - }, - }, - }, - }, { - hl('Register', '@a'), - hl('BinaryPlus', '+'), - hl('Register', '@b'), - hl('CallingParenthesis', '('), - hl('Register', '@c'), - hl('CallingParenthesis', ')'), - }) - check_parsing('@a()', { - ast = { - { - 'Call:0:2:(', - children = { - 'Register(name=a):0:0:@a', - }, - }, - }, - }, { - hl('Register', '@a'), - hl('CallingParenthesis', '('), - hl('CallingParenthesis', ')'), - }) - check_parsing('@a ()', { - ast = { - { - 'OpMissing:0:2:', - children = { - 'Register(name=a):0:0:@a', - { - 'Nested:0:2: (', - children = { - 'Missing:0:4:', - }, - }, - }, - }, - }, - err = { - arg = '()', - msg = 'E15: Missing operator: %.*s', - }, - }, { - hl('Register', '@a'), - hl('InvalidSpacing', ' '), - hl('NestingParenthesis', '('), - hl('InvalidNestingParenthesis', ')'), - }, { - [1] = { - ast = { - len = 3, - err = REMOVE_THIS, - ast = { - 'Register(name=a):0:0:@a', - }, - }, - hl_fs = { - [2] = REMOVE_THIS, - [3] = REMOVE_THIS, - [4] = REMOVE_THIS, - }, - }, - }) - check_parsing('@a + (@b)', { - ast = { - { - 'BinaryPlus:0:2: +', - children = { - 'Register(name=a):0:0:@a', - { - 'Nested:0:4: (', - children = { - 'Register(name=b):0:6:@b', - }, - }, - }, - }, - }, - }, { - hl('Register', '@a'), - hl('BinaryPlus', '+', 1), - hl('NestingParenthesis', '(', 1), - hl('Register', '@b'), - hl('NestingParenthesis', ')'), - }) - check_parsing('@a + (+@b)', { - ast = { - { - 'BinaryPlus:0:2: +', - children = { - 'Register(name=a):0:0:@a', - { - 'Nested:0:4: (', - children = { - { - 'UnaryPlus:0:6:+', - children = { - 'Register(name=b):0:7:@b', - }, - }, - }, - }, - }, - }, - }, - }, { - hl('Register', '@a'), - hl('BinaryPlus', '+', 1), - hl('NestingParenthesis', '(', 1), - hl('UnaryPlus', '+'), - hl('Register', '@b'), - hl('NestingParenthesis', ')'), - }) - check_parsing('@a + (@b + @c)', { - ast = { - { - 'BinaryPlus:0:2: +', - children = { - 'Register(name=a):0:0:@a', - { - 'Nested:0:4: (', - children = { - { - 'BinaryPlus:0:8: +', - children = { - 'Register(name=b):0:6:@b', - 'Register(name=c):0:10: @c', - }, - }, - }, - }, - }, - }, - }, - }, { - hl('Register', '@a'), - hl('BinaryPlus', '+', 1), - hl('NestingParenthesis', '(', 1), - hl('Register', '@b'), - hl('BinaryPlus', '+', 1), - hl('Register', '@c', 1), - hl('NestingParenthesis', ')'), - }) - check_parsing('(@a)+@b', { - ast = { - { - 'BinaryPlus:0:4:+', - children = { - { - 'Nested:0:0:(', - children = { - 'Register(name=a):0:1:@a', - }, - }, - 'Register(name=b):0:5:@b', - }, - }, - }, - }, { - hl('NestingParenthesis', '('), - hl('Register', '@a'), - hl('NestingParenthesis', ')'), - hl('BinaryPlus', '+'), - hl('Register', '@b'), - }) - check_parsing('@a+(@b)(@c)', { - -- 01234567890 - ast = { - { - 'BinaryPlus:0:2:+', - children = { - 'Register(name=a):0:0:@a', - { - 'Call:0:7:(', - children = { - { - 'Nested:0:3:(', - children = { 'Register(name=b):0:4:@b' }, - }, - 'Register(name=c):0:8:@c', - }, - }, - }, - }, - }, - }, { - hl('Register', '@a'), - hl('BinaryPlus', '+'), - hl('NestingParenthesis', '('), - hl('Register', '@b'), - hl('NestingParenthesis', ')'), - hl('CallingParenthesis', '('), - hl('Register', '@c'), - hl('CallingParenthesis', ')'), - }) - check_parsing('@a+((@b))(@c)', { - -- 01234567890123456890123456789 - -- 0 1 2 - ast = { - { - 'BinaryPlus:0:2:+', - children = { - 'Register(name=a):0:0:@a', - { - 'Call:0:9:(', - children = { - { - 'Nested:0:3:(', - children = { - { - 'Nested:0:4:(', - children = { 'Register(name=b):0:5:@b' } - }, - }, - }, - 'Register(name=c):0:10:@c', - }, - }, - }, - }, - }, - }, { - hl('Register', '@a'), - hl('BinaryPlus', '+'), - hl('NestingParenthesis', '('), - hl('NestingParenthesis', '('), - hl('Register', '@b'), - hl('NestingParenthesis', ')'), - hl('NestingParenthesis', ')'), - hl('CallingParenthesis', '('), - hl('Register', '@c'), - hl('CallingParenthesis', ')'), - }) - check_parsing('@a+((@b))+@c', { - -- 01234567890123456890123456789 - -- 0 1 2 - ast = { - { - 'BinaryPlus:0:9:+', - children = { - { - 'BinaryPlus:0:2:+', - children = { - 'Register(name=a):0:0:@a', - { - 'Nested:0:3:(', - children = { - { - 'Nested:0:4:(', - children = { 'Register(name=b):0:5:@b' } - }, - }, - }, - }, - }, - 'Register(name=c):0:10:@c', - }, - }, - }, - }, { - hl('Register', '@a'), - hl('BinaryPlus', '+'), - hl('NestingParenthesis', '('), - hl('NestingParenthesis', '('), - hl('Register', '@b'), - hl('NestingParenthesis', ')'), - hl('NestingParenthesis', ')'), - hl('BinaryPlus', '+'), - hl('Register', '@c'), - }) - check_parsing( - '@a + (@b + @c) + @d(@e) + (+@f) + ((+@g(@h))(@j)(@k))(@l)', {--[[ - | | | | | | | | || | | || | | ||| || || || || - 000000000011111111112222222222333333333344444444445555555 - 012345678901234567890123456789012345678901234567890123456 - ]] - ast = {{ - 'BinaryPlus:0:31: +', - children = { - { - 'BinaryPlus:0:23: +', - children = { - { - 'BinaryPlus:0:14: +', - children = { - { - 'BinaryPlus:0:2: +', - children = { - 'Register(name=a):0:0:@a', - { - 'Nested:0:4: (', - children = { - { - 'BinaryPlus:0:8: +', - children = { - 'Register(name=b):0:6:@b', - 'Register(name=c):0:10: @c', - }, - }, - }, - }, - }, - }, - { - 'Call:0:19:(', - children = { - 'Register(name=d):0:16: @d', - 'Register(name=e):0:20:@e', - }, - }, - }, - }, - { - 'Nested:0:25: (', - children = { - { - 'UnaryPlus:0:27:+', - children = { - 'Register(name=f):0:28:@f', - }, - }, - }, - }, - }, - }, - { - 'Call:0:53:(', - children = { - { - 'Nested:0:33: (', - children = { - { - 'Call:0:48:(', - children = { - { - 'Call:0:44:(', - children = { - { - 'Nested:0:35:(', - children = { - { - 'UnaryPlus:0:36:+', - children = { - { - 'Call:0:39:(', - children = { - 'Register(name=g):0:37:@g', - 'Register(name=h):0:40:@h', - }, - }, - }, - }, - }, - }, - 'Register(name=j):0:45:@j', - }, - }, - 'Register(name=k):0:49:@k', - }, - }, - }, - }, - 'Register(name=l):0:54:@l', - }, - }, - }, - }}, - }, { - hl('Register', '@a'), - hl('BinaryPlus', '+', 1), - hl('NestingParenthesis', '(', 1), - hl('Register', '@b'), - hl('BinaryPlus', '+', 1), - hl('Register', '@c', 1), - hl('NestingParenthesis', ')'), - hl('BinaryPlus', '+', 1), - hl('Register', '@d', 1), - hl('CallingParenthesis', '('), - hl('Register', '@e'), - hl('CallingParenthesis', ')'), - hl('BinaryPlus', '+', 1), - hl('NestingParenthesis', '(', 1), - hl('UnaryPlus', '+'), - hl('Register', '@f'), - hl('NestingParenthesis', ')'), - hl('BinaryPlus', '+', 1), - hl('NestingParenthesis', '(', 1), - hl('NestingParenthesis', '('), - hl('UnaryPlus', '+'), - hl('Register', '@g'), - hl('CallingParenthesis', '('), - hl('Register', '@h'), - hl('CallingParenthesis', ')'), - hl('NestingParenthesis', ')'), - hl('CallingParenthesis', '('), - hl('Register', '@j'), - hl('CallingParenthesis', ')'), - hl('CallingParenthesis', '('), - hl('Register', '@k'), - hl('CallingParenthesis', ')'), - hl('NestingParenthesis', ')'), - hl('CallingParenthesis', '('), - hl('Register', '@l'), - hl('CallingParenthesis', ')'), - }) - check_parsing('@a)', { - -- 012 - ast = { - { - 'Nested:0:2:', - children = { - 'Register(name=a):0:0:@a', - }, - }, - }, - err = { - arg = ')', - msg = 'E15: Unexpected closing parenthesis: %.*s', - }, - }, { - hl('Register', '@a'), - hl('InvalidNestingParenthesis', ')'), - }) - check_parsing('(@a', { - -- 012 - ast = { - { - 'Nested:0:0:(', - children = { - 'Register(name=a):0:1:@a', - }, - }, - }, - err = { - arg = '(@a', - msg = 'E110: Missing closing parenthesis for nested expression: %.*s', - }, - }, { - hl('NestingParenthesis', '('), - hl('Register', '@a'), - }) - check_parsing('@a(@b', { - -- 01234 - ast = { - { - 'Call:0:2:(', - children = { - 'Register(name=a):0:0:@a', - 'Register(name=b):0:3:@b', - }, - }, - }, - err = { - arg = '(@b', - msg = 'E116: Missing closing parenthesis for function call: %.*s', - }, - }, { - hl('Register', '@a'), - hl('CallingParenthesis', '('), - hl('Register', '@b'), - }) - check_parsing('@a(@b, @c, @d, @e)', { - -- 012345678901234567 - -- 0 1 - ast = { - { - 'Call:0:2:(', - children = { - 'Register(name=a):0:0:@a', - { - 'Comma:0:5:,', - children = { - 'Register(name=b):0:3:@b', - { - 'Comma:0:9:,', - children = { - 'Register(name=c):0:6: @c', - { - 'Comma:0:13:,', - children = { - 'Register(name=d):0:10: @d', - 'Register(name=e):0:14: @e', - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, { - hl('Register', '@a'), - hl('CallingParenthesis', '('), - hl('Register', '@b'), - hl('Comma', ','), - hl('Register', '@c', 1), - hl('Comma', ','), - hl('Register', '@d', 1), - hl('Comma', ','), - hl('Register', '@e', 1), - hl('CallingParenthesis', ')'), - }) - check_parsing('@a(@b(@c))', { - -- 01234567890123456789012345678901234567 - -- 0 1 2 3 - ast = { - { - 'Call:0:2:(', - children = { - 'Register(name=a):0:0:@a', - { - 'Call:0:5:(', - children = { - 'Register(name=b):0:3:@b', - 'Register(name=c):0:6:@c', - }, - }, - }, - }, - }, - }, { - hl('Register', '@a'), - hl('CallingParenthesis', '('), - hl('Register', '@b'), - hl('CallingParenthesis', '('), - hl('Register', '@c'), - hl('CallingParenthesis', ')'), - hl('CallingParenthesis', ')'), - }) - check_parsing('@a(@b(@c(@d(@e), @f(@g(@h), @i(@j)))))', { - -- 01234567890123456789012345678901234567 - -- 0 1 2 3 - ast = { - { - 'Call:0:2:(', - children = { - 'Register(name=a):0:0:@a', - { - 'Call:0:5:(', - children = { - 'Register(name=b):0:3:@b', - { - 'Call:0:8:(', - children = { - 'Register(name=c):0:6:@c', - { - 'Comma:0:15:,', - children = { - { - 'Call:0:11:(', - children = { - 'Register(name=d):0:9:@d', - 'Register(name=e):0:12:@e', - }, - }, - { - 'Call:0:19:(', - children = { - 'Register(name=f):0:16: @f', - { - 'Comma:0:26:,', - children = { - { - 'Call:0:22:(', - children = { - 'Register(name=g):0:20:@g', - 'Register(name=h):0:23:@h', - }, - }, - { - 'Call:0:30:(', - children = { - 'Register(name=i):0:27: @i', - 'Register(name=j):0:31:@j', - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, { - hl('Register', '@a'), - hl('CallingParenthesis', '('), - hl('Register', '@b'), - hl('CallingParenthesis', '('), - hl('Register', '@c'), - hl('CallingParenthesis', '('), - hl('Register', '@d'), - hl('CallingParenthesis', '('), - hl('Register', '@e'), - hl('CallingParenthesis', ')'), - hl('Comma', ','), - hl('Register', '@f', 1), - hl('CallingParenthesis', '('), - hl('Register', '@g'), - hl('CallingParenthesis', '('), - hl('Register', '@h'), - hl('CallingParenthesis', ')'), - hl('Comma', ','), - hl('Register', '@i', 1), - hl('CallingParenthesis', '('), - hl('Register', '@j'), - hl('CallingParenthesis', ')'), - hl('CallingParenthesis', ')'), - hl('CallingParenthesis', ')'), - hl('CallingParenthesis', ')'), - hl('CallingParenthesis', ')'), - }) - end) - it('works with variable names, including curly braces ones', function() - check_parsing('var', { - ast = { - 'PlainIdentifier(scope=0,ident=var):0:0:var', - }, - }, { - hl('IdentifierName', 'var'), - }) - check_parsing('g:var', { - ast = { - 'PlainIdentifier(scope=g,ident=var):0:0:g:var', - }, - }, { - hl('IdentifierScope', 'g'), - hl('IdentifierScopeDelimiter', ':'), - hl('IdentifierName', 'var'), - }) - check_parsing('g:', { - ast = { - 'PlainIdentifier(scope=g,ident=):0:0:g:', - }, - }, { - hl('IdentifierScope', 'g'), - hl('IdentifierScopeDelimiter', ':'), - }) - check_parsing('{a}', { - -- 012 - ast = { - { - 'CurlyBracesIdentifier:0:0:{', - children = { - 'PlainIdentifier(scope=0,ident=a):0:1:a', - }, - }, - }, - }, { - hl('Curly', '{'), - hl('IdentifierName', 'a'), - hl('Curly', '}'), - }) - check_parsing('{a:b}', { - -- 012 - ast = { - { - 'CurlyBracesIdentifier:0:0:{', - children = { - 'PlainIdentifier(scope=a,ident=b):0:1:a:b', - }, - }, - }, - }, { - hl('Curly', '{'), - hl('IdentifierScope', 'a'), - hl('IdentifierScopeDelimiter', ':'), - hl('IdentifierName', 'b'), - hl('Curly', '}'), - }) - check_parsing('{a:@b}', { - -- 012345 - ast = { - { - 'CurlyBracesIdentifier:0:0:{', - children = { - { - 'OpMissing:0:3:', - children={ - 'PlainIdentifier(scope=a,ident=):0:1:a:', - 'Register(name=b):0:3:@b', - }, - }, - }, - }, - }, - err = { - arg = '@b}', - msg = 'E15: Missing operator: %.*s', - }, - }, { - hl('Curly', '{'), - hl('IdentifierScope', 'a'), - hl('IdentifierScopeDelimiter', ':'), - hl('InvalidRegister', '@b'), - hl('Curly', '}'), - }) - check_parsing('{@a}', { - ast = { - { - 'CurlyBracesIdentifier:0:0:{', - children = { - 'Register(name=a):0:1:@a', - }, - }, - }, - }, { - hl('Curly', '{'), - hl('Register', '@a'), - hl('Curly', '}'), - }) - check_parsing('{@a}{@b}', { - -- 01234567 - ast = { - { - 'ComplexIdentifier:0:4:', - children = { - { - 'CurlyBracesIdentifier:0:0:{', - children = { - 'Register(name=a):0:1:@a', - }, - }, - { - 'CurlyBracesIdentifier:0:4:{', - children = { - 'Register(name=b):0:5:@b', - }, - }, - }, - }, - }, - }, { - hl('Curly', '{'), - hl('Register', '@a'), - hl('Curly', '}'), - hl('Curly', '{'), - hl('Register', '@b'), - hl('Curly', '}'), - }) - check_parsing('g:{@a}', { - -- 01234567 - ast = { - { - 'ComplexIdentifier:0:2:', - children = { - 'PlainIdentifier(scope=g,ident=):0:0:g:', - { - 'CurlyBracesIdentifier:0:2:{', - children = { - 'Register(name=a):0:3:@a', - }, - }, - }, - }, - }, - }, { - hl('IdentifierScope', 'g'), - hl('IdentifierScopeDelimiter', ':'), - hl('Curly', '{'), - hl('Register', '@a'), - hl('Curly', '}'), - }) - check_parsing('{@a}_test', { - -- 012345678 - ast = { - { - 'ComplexIdentifier:0:4:', - children = { - { - 'CurlyBracesIdentifier:0:0:{', - children = { - 'Register(name=a):0:1:@a', - }, - }, - 'PlainIdentifier(scope=0,ident=_test):0:4:_test', - }, - }, - }, - }, { - hl('Curly', '{'), - hl('Register', '@a'), - hl('Curly', '}'), - hl('IdentifierName', '_test'), - }) - check_parsing('g:{@a}_test', { - -- 01234567890 - ast = { - { - 'ComplexIdentifier:0:2:', - children = { - 'PlainIdentifier(scope=g,ident=):0:0:g:', - { - 'ComplexIdentifier:0:6:', - children = { - { - 'CurlyBracesIdentifier:0:2:{', - children = { - 'Register(name=a):0:3:@a', - }, - }, - 'PlainIdentifier(scope=0,ident=_test):0:6:_test', - }, - }, - }, - }, - }, - }, { - hl('IdentifierScope', 'g'), - hl('IdentifierScopeDelimiter', ':'), - hl('Curly', '{'), - hl('Register', '@a'), - hl('Curly', '}'), - hl('IdentifierName', '_test'), - }) - check_parsing('g:{@a}_test()', { - -- 0123456789012 - ast = { - { - 'Call:0:11:(', - children = { - { - 'ComplexIdentifier:0:2:', - children = { - 'PlainIdentifier(scope=g,ident=):0:0:g:', - { - 'ComplexIdentifier:0:6:', - children = { - { - 'CurlyBracesIdentifier:0:2:{', - children = { - 'Register(name=a):0:3:@a', - }, - }, - 'PlainIdentifier(scope=0,ident=_test):0:6:_test', - }, - }, - }, - }, - }, - }, - }, - }, { - hl('IdentifierScope', 'g'), - hl('IdentifierScopeDelimiter', ':'), - hl('Curly', '{'), - hl('Register', '@a'), - hl('Curly', '}'), - hl('IdentifierName', '_test'), - hl('CallingParenthesis', '('), - hl('CallingParenthesis', ')'), - }) - check_parsing('{@a} ()', { - -- 0123456789012 - ast = { - { - 'Call:0:4: (', - children = { - { - 'CurlyBracesIdentifier:0:0:{', - children = { - 'Register(name=a):0:1:@a', - }, - }, - }, - }, - }, - }, { - hl('Curly', '{'), - hl('Register', '@a'), - hl('Curly', '}'), - hl('CallingParenthesis', '(', 1), - hl('CallingParenthesis', ')'), - }) - check_parsing('g:{@a} ()', { - -- 0123456789012 - ast = { - { - 'Call:0:6: (', - children = { - { - 'ComplexIdentifier:0:2:', - children = { - 'PlainIdentifier(scope=g,ident=):0:0:g:', - { - 'CurlyBracesIdentifier:0:2:{', - children = { - 'Register(name=a):0:3:@a', - }, - }, - }, - }, - }, - }, - }, - }, { - hl('IdentifierScope', 'g'), - hl('IdentifierScopeDelimiter', ':'), - hl('Curly', '{'), - hl('Register', '@a'), - hl('Curly', '}'), - hl('CallingParenthesis', '(', 1), - hl('CallingParenthesis', ')'), - }) - check_parsing('{@a', { - -- 012 - ast = { - { - 'UnknownFigure:0:0:{', - children = { - 'Register(name=a):0:1:@a', - }, - }, - }, - err = { - arg = '{@a', - msg = 'E15: Missing closing figure brace: %.*s', - }, - }, { - hl('FigureBrace', '{'), - hl('Register', '@a'), - }) - end) - it('works with lambdas and dictionaries', function() - check_parsing('{}', { - ast = { - 'DictLiteral:0:0:{', - }, - }, { - hl('Dict', '{'), - hl('Dict', '}'), - }) - check_parsing('{->@a}', { - ast = { - { - 'Lambda:0:0:{', - children = { - { - 'Arrow:0:1:->', - children = { - 'Register(name=a):0:3:@a', - }, - }, - }, - }, - }, - }, { - hl('Lambda', '{'), - hl('Arrow', '->'), - hl('Register', '@a'), - hl('Lambda', '}'), - }) - check_parsing('{->@a+@b}', { - -- 012345678 - ast = { - { - 'Lambda:0:0:{', - children = { - { - 'Arrow:0:1:->', - children = { - { - 'BinaryPlus:0:5:+', - children = { - 'Register(name=a):0:3:@a', - 'Register(name=b):0:6:@b', - }, - }, - }, - }, - }, - }, - }, - }, { - hl('Lambda', '{'), - hl('Arrow', '->'), - hl('Register', '@a'), - hl('BinaryPlus', '+'), - hl('Register', '@b'), - hl('Lambda', '}'), - }) - check_parsing('{a->@a}', { - -- 012345678 - ast = { - { - 'Lambda:0:0:{', - children = { - 'PlainIdentifier(scope=0,ident=a):0:1:a', - { - 'Arrow:0:2:->', - children = { - 'Register(name=a):0:4:@a', - }, - }, - }, - }, - }, - }, { - hl('Lambda', '{'), - hl('IdentifierName', 'a'), - hl('Arrow', '->'), - hl('Register', '@a'), - hl('Lambda', '}'), - }) - check_parsing('{a,b->@a}', { - -- 012345678 - ast = { - { - 'Lambda:0:0:{', - children = { - { - 'Comma:0:2:,', - children = { - 'PlainIdentifier(scope=0,ident=a):0:1:a', - 'PlainIdentifier(scope=0,ident=b):0:3:b', - }, - }, - { - 'Arrow:0:4:->', - children = { - 'Register(name=a):0:6:@a', - }, - }, - }, - }, - }, - }, { - hl('Lambda', '{'), - hl('IdentifierName', 'a'), - hl('Comma', ','), - hl('IdentifierName', 'b'), - hl('Arrow', '->'), - hl('Register', '@a'), - hl('Lambda', '}'), - }) - check_parsing('{a,b,c->@a}', { - -- 01234567890 - ast = { - { - 'Lambda:0:0:{', - children = { - { - 'Comma:0:2:,', - children = { - 'PlainIdentifier(scope=0,ident=a):0:1:a', - { - 'Comma:0:4:,', - children = { - 'PlainIdentifier(scope=0,ident=b):0:3:b', - 'PlainIdentifier(scope=0,ident=c):0:5:c', - }, - }, - }, - }, - { - 'Arrow:0:6:->', - children = { - 'Register(name=a):0:8:@a', - }, - }, - }, - }, - }, - }, { - hl('Lambda', '{'), - hl('IdentifierName', 'a'), - hl('Comma', ','), - hl('IdentifierName', 'b'), - hl('Comma', ','), - hl('IdentifierName', 'c'), - hl('Arrow', '->'), - hl('Register', '@a'), - hl('Lambda', '}'), - }) - check_parsing('{a,b,c,d->@a}', { - -- 0123456789012 - ast = { - { - 'Lambda:0:0:{', - children = { - { - 'Comma:0:2:,', - children = { - 'PlainIdentifier(scope=0,ident=a):0:1:a', - { - 'Comma:0:4:,', - children = { - 'PlainIdentifier(scope=0,ident=b):0:3:b', - { - 'Comma:0:6:,', - children = { - 'PlainIdentifier(scope=0,ident=c):0:5:c', - 'PlainIdentifier(scope=0,ident=d):0:7:d', - }, - }, - }, - }, - }, - }, - { - 'Arrow:0:8:->', - children = { - 'Register(name=a):0:10:@a', - }, - }, - }, - }, - }, - }, { - hl('Lambda', '{'), - hl('IdentifierName', 'a'), - hl('Comma', ','), - hl('IdentifierName', 'b'), - hl('Comma', ','), - hl('IdentifierName', 'c'), - hl('Comma', ','), - hl('IdentifierName', 'd'), - hl('Arrow', '->'), - hl('Register', '@a'), - hl('Lambda', '}'), - }) - check_parsing('{a,b,c,d,->@a}', { - -- 01234567890123 - ast = { - { - 'Lambda:0:0:{', - children = { - { - 'Comma:0:2:,', - children = { - 'PlainIdentifier(scope=0,ident=a):0:1:a', - { - 'Comma:0:4:,', - children = { - 'PlainIdentifier(scope=0,ident=b):0:3:b', - { - 'Comma:0:6:,', - children = { - 'PlainIdentifier(scope=0,ident=c):0:5:c', - { - 'Comma:0:8:,', - children = { - 'PlainIdentifier(scope=0,ident=d):0:7:d', - }, - }, - }, - }, - }, - }, - }, - }, - { - 'Arrow:0:9:->', - children = { - 'Register(name=a):0:11:@a', - }, - }, - }, - }, - }, - }, { - hl('Lambda', '{'), - hl('IdentifierName', 'a'), - hl('Comma', ','), - hl('IdentifierName', 'b'), - hl('Comma', ','), - hl('IdentifierName', 'c'), - hl('Comma', ','), - hl('IdentifierName', 'd'), - hl('Comma', ','), - hl('Arrow', '->'), - hl('Register', '@a'), - hl('Lambda', '}'), - }) - check_parsing('{a,b->{c,d->{e,f->@a}}}', { - -- 01234567890123456789012 - -- 0 1 2 - ast = { - { - 'Lambda:0:0:{', - children = { - { - 'Comma:0:2:,', - children = { - 'PlainIdentifier(scope=0,ident=a):0:1:a', - 'PlainIdentifier(scope=0,ident=b):0:3:b', - }, - }, - { - 'Arrow:0:4:->', - children = { - { - 'Lambda:0:6:{', - children = { - { - 'Comma:0:8:,', - children = { - 'PlainIdentifier(scope=0,ident=c):0:7:c', - 'PlainIdentifier(scope=0,ident=d):0:9:d', - }, - }, - { - 'Arrow:0:10:->', - children = { - { - 'Lambda:0:12:{', - children = { - { - 'Comma:0:14:,', - children = { - 'PlainIdentifier(scope=0,ident=e):0:13:e', - 'PlainIdentifier(scope=0,ident=f):0:15:f', - }, - }, - { - 'Arrow:0:16:->', - children = { - 'Register(name=a):0:18:@a', - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, { - hl('Lambda', '{'), - hl('IdentifierName', 'a'), - hl('Comma', ','), - hl('IdentifierName', 'b'), - hl('Arrow', '->'), - hl('Lambda', '{'), - hl('IdentifierName', 'c'), - hl('Comma', ','), - hl('IdentifierName', 'd'), - hl('Arrow', '->'), - hl('Lambda', '{'), - hl('IdentifierName', 'e'), - hl('Comma', ','), - hl('IdentifierName', 'f'), - hl('Arrow', '->'), - hl('Register', '@a'), - hl('Lambda', '}'), - hl('Lambda', '}'), - hl('Lambda', '}'), - }) - check_parsing('{a,b->c,d}', { - -- 0123456789 - ast = { - { - 'Lambda:0:0:{', - children = { - { - 'Comma:0:2:,', - children = { - 'PlainIdentifier(scope=0,ident=a):0:1:a', - 'PlainIdentifier(scope=0,ident=b):0:3:b', - }, - }, - { - 'Arrow:0:4:->', - children = { - { - 'Comma:0:7:,', - children = { - 'PlainIdentifier(scope=0,ident=c):0:6:c', - 'PlainIdentifier(scope=0,ident=d):0:8:d', - }, - }, - }, - }, - }, - }, - }, - err = { - arg = ',d}', - msg = 'E15: Comma outside of call, lambda or literal: %.*s', - }, - }, { - hl('Lambda', '{'), - hl('IdentifierName', 'a'), - hl('Comma', ','), - hl('IdentifierName', 'b'), - hl('Arrow', '->'), - hl('IdentifierName', 'c'), - hl('InvalidComma', ','), - hl('IdentifierName', 'd'), - hl('Lambda', '}'), - }) - check_parsing('a,b,c,d', { - -- 0123456789 - ast = { - { - 'Comma:0:1:,', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - { - 'Comma:0:3:,', - children = { - 'PlainIdentifier(scope=0,ident=b):0:2:b', - { - 'Comma:0:5:,', - children = { - 'PlainIdentifier(scope=0,ident=c):0:4:c', - 'PlainIdentifier(scope=0,ident=d):0:6:d', - }, - }, - }, - }, - }, - }, - }, - err = { - arg = ',b,c,d', - msg = 'E15: Comma outside of call, lambda or literal: %.*s', - }, - }, { - hl('IdentifierName', 'a'), - hl('InvalidComma', ','), - hl('IdentifierName', 'b'), - hl('InvalidComma', ','), - hl('IdentifierName', 'c'), - hl('InvalidComma', ','), - hl('IdentifierName', 'd'), - }) - check_parsing('a,b,c,d,', { - -- 0123456789 - ast = { - { - 'Comma:0:1:,', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - { - 'Comma:0:3:,', - children = { - 'PlainIdentifier(scope=0,ident=b):0:2:b', - { - 'Comma:0:5:,', - children = { - 'PlainIdentifier(scope=0,ident=c):0:4:c', - { - 'Comma:0:7:,', - children = { - 'PlainIdentifier(scope=0,ident=d):0:6:d', - }, - }, - }, - }, - }, - }, - }, - }, - }, - err = { - arg = ',b,c,d,', - msg = 'E15: Comma outside of call, lambda or literal: %.*s', - }, - }, { - hl('IdentifierName', 'a'), - hl('InvalidComma', ','), - hl('IdentifierName', 'b'), - hl('InvalidComma', ','), - hl('IdentifierName', 'c'), - hl('InvalidComma', ','), - hl('IdentifierName', 'd'), - hl('InvalidComma', ','), - }) - check_parsing(',', { - -- 0123456789 - ast = { - { - 'Comma:0:0:,', - children = { - 'Missing:0:0:', - }, - }, - }, - err = { - arg = ',', - msg = 'E15: Expected value, got comma: %.*s', - }, - }, { - hl('InvalidComma', ','), - }) - check_parsing('{,a->@a}', { - -- 0123456789 - ast = { - { - 'CurlyBracesIdentifier:0:0:{', - children = { - { - 'Arrow:0:3:->', - children = { - { - 'Comma:0:1:,', - children = { - 'Missing:0:1:', - 'PlainIdentifier(scope=0,ident=a):0:2:a', - }, - }, - 'Register(name=a):0:5:@a', - }, - }, - }, - }, - }, - err = { - arg = ',a->@a}', - msg = 'E15: Expected value, got comma: %.*s', - }, - }, { - hl('Curly', '{'), - hl('InvalidComma', ','), - hl('IdentifierName', 'a'), - hl('InvalidArrow', '->'), - hl('Register', '@a'), - hl('Curly', '}'), - }) - check_parsing('}', { - -- 0123456789 - ast = { - 'UnknownFigure:0:0:', - }, - err = { - arg = '}', - msg = 'E15: Unexpected closing figure brace: %.*s', - }, - }, { - hl('InvalidFigureBrace', '}'), - }) - check_parsing('{->}', { - -- 0123456789 - ast = { - { - 'Lambda:0:0:{', - children = { - 'Arrow:0:1:->', - }, - }, - }, - err = { - arg = '}', - msg = 'E15: Expected value, got closing figure brace: %.*s', - }, - }, { - hl('Lambda', '{'), - hl('Arrow', '->'), - hl('InvalidLambda', '}'), - }) - check_parsing('{a,b}', { - -- 0123456789 - ast = { - { - 'Lambda:0:0:{', - children = { - { - 'Comma:0:2:,', - children = { - 'PlainIdentifier(scope=0,ident=a):0:1:a', - 'PlainIdentifier(scope=0,ident=b):0:3:b', - }, - }, - }, - }, - }, - err = { - arg = '}', - msg = 'E15: Expected lambda arguments list or arrow: %.*s', - }, - }, { - hl('Lambda', '{'), - hl('IdentifierName', 'a'), - hl('Comma', ','), - hl('IdentifierName', 'b'), - hl('InvalidLambda', '}'), - }) - check_parsing('{a,}', { - -- 0123456789 - ast = { - { - 'Lambda:0:0:{', - children = { - { - 'Comma:0:2:,', - children = { - 'PlainIdentifier(scope=0,ident=a):0:1:a', - }, - }, - }, - }, - }, - err = { - arg = '}', - msg = 'E15: Expected lambda arguments list or arrow: %.*s', - }, - }, { - hl('Lambda', '{'), - hl('IdentifierName', 'a'), - hl('Comma', ','), - hl('InvalidLambda', '}'), - }) - check_parsing('{@a:@b}', { - -- 0123456789 - ast = { - { - 'DictLiteral:0:0:{', - children = { - { - 'Colon:0:3::', - children = { - 'Register(name=a):0:1:@a', - 'Register(name=b):0:4:@b', - }, - }, - }, - }, - }, - }, { - hl('Dict', '{'), - hl('Register', '@a'), - hl('Colon', ':'), - hl('Register', '@b'), - hl('Dict', '}'), - }) - check_parsing('{@a:@b,@c:@d}', { - -- 0123456789012 - -- 0 1 - ast = { - { - 'DictLiteral:0:0:{', - children = { - { - 'Comma:0:6:,', - children = { - { - 'Colon:0:3::', - children = { - 'Register(name=a):0:1:@a', - 'Register(name=b):0:4:@b', - }, - }, - { - 'Colon:0:9::', - children = { - 'Register(name=c):0:7:@c', - 'Register(name=d):0:10:@d', - }, - }, - }, - }, - }, - }, - }, - }, { - hl('Dict', '{'), - hl('Register', '@a'), - hl('Colon', ':'), - hl('Register', '@b'), - hl('Comma', ','), - hl('Register', '@c'), - hl('Colon', ':'), - hl('Register', '@d'), - hl('Dict', '}'), - }) - check_parsing('{@a:@b,@c:@d,@e:@f,}', { - -- 01234567890123456789 - -- 0 1 - ast = { - { - 'DictLiteral:0:0:{', - children = { - { - 'Comma:0:6:,', - children = { - { - 'Colon:0:3::', - children = { - 'Register(name=a):0:1:@a', - 'Register(name=b):0:4:@b', - }, - }, - { - 'Comma:0:12:,', - children = { - { - 'Colon:0:9::', - children = { - 'Register(name=c):0:7:@c', - 'Register(name=d):0:10:@d', - }, - }, - { - 'Comma:0:18:,', - children = { - { - 'Colon:0:15::', - children = { - 'Register(name=e):0:13:@e', - 'Register(name=f):0:16:@f', - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, { - hl('Dict', '{'), - hl('Register', '@a'), - hl('Colon', ':'), - hl('Register', '@b'), - hl('Comma', ','), - hl('Register', '@c'), - hl('Colon', ':'), - hl('Register', '@d'), - hl('Comma', ','), - hl('Register', '@e'), - hl('Colon', ':'), - hl('Register', '@f'), - hl('Comma', ','), - hl('Dict', '}'), - }) - check_parsing('{@a:@b,@c:@d,@e:@f,@g:}', { - -- 01234567890123456789012 - -- 0 1 2 - ast = { - { - 'DictLiteral:0:0:{', - children = { - { - 'Comma:0:6:,', - children = { - { - 'Colon:0:3::', - children = { - 'Register(name=a):0:1:@a', - 'Register(name=b):0:4:@b', - }, - }, - { - 'Comma:0:12:,', - children = { - { - 'Colon:0:9::', - children = { - 'Register(name=c):0:7:@c', - 'Register(name=d):0:10:@d', - }, - }, - { - 'Comma:0:18:,', - children = { - { - 'Colon:0:15::', - children = { - 'Register(name=e):0:13:@e', - 'Register(name=f):0:16:@f', - }, - }, - { - 'Colon:0:21::', - children = { - 'Register(name=g):0:19:@g', - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - err = { - arg = '}', - msg = 'E15: Expected value, got closing figure brace: %.*s', - }, - }, { - hl('Dict', '{'), - hl('Register', '@a'), - hl('Colon', ':'), - hl('Register', '@b'), - hl('Comma', ','), - hl('Register', '@c'), - hl('Colon', ':'), - hl('Register', '@d'), - hl('Comma', ','), - hl('Register', '@e'), - hl('Colon', ':'), - hl('Register', '@f'), - hl('Comma', ','), - hl('Register', '@g'), - hl('Colon', ':'), - hl('InvalidDict', '}'), - }) - check_parsing('{@a:@b,}', { - -- 01234567890123 - -- 0 1 - ast = { - { - 'DictLiteral:0:0:{', - children = { - { - 'Comma:0:6:,', - children = { - { - 'Colon:0:3::', - children = { - 'Register(name=a):0:1:@a', - 'Register(name=b):0:4:@b', - }, - }, - }, - }, - }, - }, - }, - }, { - hl('Dict', '{'), - hl('Register', '@a'), - hl('Colon', ':'), - hl('Register', '@b'), - hl('Comma', ','), - hl('Dict', '}'), - }) - check_parsing('{({f -> g})(@h)(@i)}', { - -- 01234567890123456789 - -- 0 1 - ast = { - { - 'CurlyBracesIdentifier:0:0:{', - children = { - { - 'Call:0:15:(', - children = { - { - 'Call:0:11:(', - children = { - { - 'Nested:0:1:(', - children = { - { - 'Lambda:0:2:{', - children = { - 'PlainIdentifier(scope=0,ident=f):0:3:f', - { - 'Arrow:0:4: ->', - children = { - 'PlainIdentifier(scope=0,ident=g):0:7: g', - }, - }, - }, - }, - }, - }, - 'Register(name=h):0:12:@h', - }, - }, - 'Register(name=i):0:16:@i', - }, - }, - }, - }, - }, - }, { - hl('Curly', '{'), - hl('NestingParenthesis', '('), - hl('Lambda', '{'), - hl('IdentifierName', 'f'), - hl('Arrow', '->', 1), - hl('IdentifierName', 'g', 1), - hl('Lambda', '}'), - hl('NestingParenthesis', ')'), - hl('CallingParenthesis', '('), - hl('Register', '@h'), - hl('CallingParenthesis', ')'), - hl('CallingParenthesis', '('), - hl('Register', '@i'), - hl('CallingParenthesis', ')'), - hl('Curly', '}'), - }) - check_parsing('a:{b()}c', { - -- 01234567 - ast = { - { - 'ComplexIdentifier:0:2:', - children = { - 'PlainIdentifier(scope=a,ident=):0:0:a:', - { - 'ComplexIdentifier:0:7:', - children = { - { - 'CurlyBracesIdentifier:0:2:{', - children = { - { - 'Call:0:4:(', - children = { - 'PlainIdentifier(scope=0,ident=b):0:3:b', - }, - }, - }, - }, - 'PlainIdentifier(scope=0,ident=c):0:7:c', - }, - }, - }, - }, - }, - }, { - hl('IdentifierScope', 'a'), - hl('IdentifierScopeDelimiter', ':'), - hl('Curly', '{'), - hl('IdentifierName', 'b'), - hl('CallingParenthesis', '('), - hl('CallingParenthesis', ')'), - hl('Curly', '}'), - hl('IdentifierName', 'c'), - }) - check_parsing('a:{{b, c -> @d + @e + ({f -> g})(@h)}(@i)}j', { - -- 01234567890123456789012345678901234567890123456 - -- 0 1 2 3 4 - ast = { - { - 'ComplexIdentifier:0:2:', - children = { - 'PlainIdentifier(scope=a,ident=):0:0:a:', - { - 'ComplexIdentifier:0:42:', - children = { - { - 'CurlyBracesIdentifier:0:2:{', - children = { - { - 'Call:0:37:(', - children = { - { - 'Lambda:0:3:{', - children = { - { - 'Comma:0:5:,', - children = { - 'PlainIdentifier(scope=0,ident=b):0:4:b', - 'PlainIdentifier(scope=0,ident=c):0:6: c', - }, - }, - { - 'Arrow:0:8: ->', - children = { - { - 'BinaryPlus:0:19: +', - children = { - { - 'BinaryPlus:0:14: +', - children = { - 'Register(name=d):0:11: @d', - 'Register(name=e):0:16: @e', - }, - }, - { - 'Call:0:32:(', - children = { - { - 'Nested:0:21: (', - children = { - { - 'Lambda:0:23:{', - children = { - 'PlainIdentifier(scope=0,ident=f):0:24:f', - { - 'Arrow:0:25: ->', - children = { - 'PlainIdentifier(scope=0,ident=g):0:28: g', - }, - }, - }, - }, - }, - }, - 'Register(name=h):0:33:@h', - }, - }, - }, - }, - }, - }, - }, - }, - 'Register(name=i):0:38:@i', - }, - }, - }, - }, - 'PlainIdentifier(scope=0,ident=j):0:42:j', - }, - }, - }, - }, - }, - }, { - hl('IdentifierScope', 'a'), - hl('IdentifierScopeDelimiter', ':'), - hl('Curly', '{'), - hl('Lambda', '{'), - hl('IdentifierName', 'b'), - hl('Comma', ','), - hl('IdentifierName', 'c', 1), - hl('Arrow', '->', 1), - hl('Register', '@d', 1), - hl('BinaryPlus', '+', 1), - hl('Register', '@e', 1), - hl('BinaryPlus', '+', 1), - hl('NestingParenthesis', '(', 1), - hl('Lambda', '{'), - hl('IdentifierName', 'f'), - hl('Arrow', '->', 1), - hl('IdentifierName', 'g', 1), - hl('Lambda', '}'), - hl('NestingParenthesis', ')'), - hl('CallingParenthesis', '('), - hl('Register', '@h'), - hl('CallingParenthesis', ')'), - hl('Lambda', '}'), - hl('CallingParenthesis', '('), - hl('Register', '@i'), - hl('CallingParenthesis', ')'), - hl('Curly', '}'), - hl('IdentifierName', 'j'), - }) - check_parsing('{@a + @b : @c + @d, @e + @f : @g + @i}', { - -- 01234567890123456789012345678901234567 - -- 0 1 2 3 - ast = { - { - 'DictLiteral:0:0:{', - children = { - { - 'Comma:0:18:,', - children = { - { - 'Colon:0:8: :', - children = { - { - 'BinaryPlus:0:3: +', - children = { - 'Register(name=a):0:1:@a', - 'Register(name=b):0:5: @b', - }, - }, - { - 'BinaryPlus:0:13: +', - children = { - 'Register(name=c):0:10: @c', - 'Register(name=d):0:15: @d', - }, - }, - }, - }, - { - 'Colon:0:27: :', - children = { - { - 'BinaryPlus:0:22: +', - children = { - 'Register(name=e):0:19: @e', - 'Register(name=f):0:24: @f', - }, - }, - { - 'BinaryPlus:0:32: +', - children = { - 'Register(name=g):0:29: @g', - 'Register(name=i):0:34: @i', - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, { - hl('Dict', '{'), - hl('Register', '@a'), - hl('BinaryPlus', '+', 1), - hl('Register', '@b', 1), - hl('Colon', ':', 1), - hl('Register', '@c', 1), - hl('BinaryPlus', '+', 1), - hl('Register', '@d', 1), - hl('Comma', ','), - hl('Register', '@e', 1), - hl('BinaryPlus', '+', 1), - hl('Register', '@f', 1), - hl('Colon', ':', 1), - hl('Register', '@g', 1), - hl('BinaryPlus', '+', 1), - hl('Register', '@i', 1), - hl('Dict', '}'), - }) - check_parsing('-> -> ->', { - -- 01234567 - ast = { - { - 'Arrow:0:0:->', - children = { - 'Missing:0:0:', - { - 'Arrow:0:2: ->', - children = { - 'Missing:0:2:', - { - 'Arrow:0:5: ->', - children = { - 'Missing:0:5:', - }, - }, - }, - }, - }, - }, - }, - err = { - arg = '-> -> ->', - msg = 'E15: Unexpected arrow: %.*s', - }, - }, { - hl('InvalidArrow', '->'), - hl('InvalidArrow', '->', 1), - hl('InvalidArrow', '->', 1), - }) - check_parsing('a -> b -> c -> d', { - -- 0123456789012345 - -- 0 1 - ast = { - { - 'Arrow:0:1: ->', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - { - 'Arrow:0:6: ->', - children = { - 'PlainIdentifier(scope=0,ident=b):0:4: b', - { - 'Arrow:0:11: ->', - children = { - 'PlainIdentifier(scope=0,ident=c):0:9: c', - 'PlainIdentifier(scope=0,ident=d):0:14: d', - }, - }, - }, - }, - }, - }, - }, - err = { - arg = '-> b -> c -> d', - msg = 'E15: Arrow outside of lambda: %.*s', - }, - }, { - hl('IdentifierName', 'a'), - hl('InvalidArrow', '->', 1), - hl('IdentifierName', 'b', 1), - hl('InvalidArrow', '->', 1), - hl('IdentifierName', 'c', 1), - hl('InvalidArrow', '->', 1), - hl('IdentifierName', 'd', 1), - }) - check_parsing('{a -> b -> c}', { - -- 0123456789012 - -- 0 1 - ast = { - { - 'Lambda:0:0:{', - children = { - 'PlainIdentifier(scope=0,ident=a):0:1:a', - { - 'Arrow:0:2: ->', - children = { - { - 'Arrow:0:7: ->', - children = { - 'PlainIdentifier(scope=0,ident=b):0:5: b', - 'PlainIdentifier(scope=0,ident=c):0:10: c', - }, - }, - }, - }, - }, - }, - }, - err = { - arg = '-> c}', - msg = 'E15: Arrow outside of lambda: %.*s', - }, - }, { - hl('Lambda', '{'), - hl('IdentifierName', 'a'), - hl('Arrow', '->', 1), - hl('IdentifierName', 'b', 1), - hl('InvalidArrow', '->', 1), - hl('IdentifierName', 'c', 1), - hl('Lambda', '}'), - }) - check_parsing('{a: -> b}', { - -- 012345678 - ast = { - { - 'CurlyBracesIdentifier:0:0:{', - children = { - { - 'Arrow:0:3: ->', - children = { - 'PlainIdentifier(scope=a,ident=):0:1:a:', - 'PlainIdentifier(scope=0,ident=b):0:6: b', - }, - }, - }, - }, - }, - err = { - arg = '-> b}', - msg = 'E15: Arrow outside of lambda: %.*s', - }, - }, { - hl('Curly', '{'), - hl('IdentifierScope', 'a'), - hl('IdentifierScopeDelimiter', ':'), - hl('InvalidArrow', '->', 1), - hl('IdentifierName', 'b', 1), - hl('Curly', '}'), - }) - - check_parsing('{a:b -> b}', { - -- 0123456789 - ast = { - { - 'CurlyBracesIdentifier:0:0:{', - children = { - { - 'Arrow:0:4: ->', - children = { - 'PlainIdentifier(scope=a,ident=b):0:1:a:b', - 'PlainIdentifier(scope=0,ident=b):0:7: b', - }, - }, - }, - }, - }, - err = { - arg = '-> b}', - msg = 'E15: Arrow outside of lambda: %.*s', - }, - }, { - hl('Curly', '{'), - hl('IdentifierScope', 'a'), - hl('IdentifierScopeDelimiter', ':'), - hl('IdentifierName', 'b'), - hl('InvalidArrow', '->', 1), - hl('IdentifierName', 'b', 1), - hl('Curly', '}'), - }) - - check_parsing('{a#b -> b}', { - -- 0123456789 - ast = { - { - 'CurlyBracesIdentifier:0:0:{', - children = { - { - 'Arrow:0:4: ->', - children = { - 'PlainIdentifier(scope=0,ident=a#b):0:1:a#b', - 'PlainIdentifier(scope=0,ident=b):0:7: b', - }, - }, - }, - }, - }, - err = { - arg = '-> b}', - msg = 'E15: Arrow outside of lambda: %.*s', - }, - }, { - hl('Curly', '{'), - hl('IdentifierName', 'a#b'), - hl('InvalidArrow', '->', 1), - hl('IdentifierName', 'b', 1), - hl('Curly', '}'), - }) - check_parsing('{a : b : c}', { - -- 01234567890 - -- 0 1 - ast = { - { - 'DictLiteral:0:0:{', - children = { - { - 'Colon:0:2: :', - children = { - 'PlainIdentifier(scope=0,ident=a):0:1:a', - { - 'Colon:0:6: :', - children = { - 'PlainIdentifier(scope=0,ident=b):0:4: b', - 'PlainIdentifier(scope=0,ident=c):0:8: c', - }, - }, - }, - }, - }, - }, - }, - err = { - arg = ': c}', - msg = 'E15: Colon outside of dictionary or ternary operator: %.*s', - }, - }, { - hl('Dict', '{'), - hl('IdentifierName', 'a'), - hl('Colon', ':', 1), - hl('IdentifierName', 'b', 1), - hl('InvalidColon', ':', 1), - hl('IdentifierName', 'c', 1), - hl('Dict', '}'), - }) - check_parsing('{', { - -- 0 - ast = { - 'UnknownFigure:0:0:{', - }, - err = { - arg = '{', - msg = 'E15: Missing closing figure brace: %.*s', - }, - }, { - hl('FigureBrace', '{'), - }) - check_parsing('{a', { - -- 01 - ast = { - { - 'UnknownFigure:0:0:{', - children = { - 'PlainIdentifier(scope=0,ident=a):0:1:a', - }, - }, - }, - err = { - arg = '{a', - msg = 'E15: Missing closing figure brace: %.*s', - }, - }, { - hl('FigureBrace', '{'), - hl('IdentifierName', 'a'), - }) - check_parsing('{a,b', { - -- 0123 - ast = { - { - 'Lambda:0:0:{', - children = { - { - 'Comma:0:2:,', - children = { - 'PlainIdentifier(scope=0,ident=a):0:1:a', - 'PlainIdentifier(scope=0,ident=b):0:3:b', - }, - }, - }, - }, - }, - err = { - arg = '{a,b', - msg = 'E15: Missing closing figure brace for lambda: %.*s', - }, - }, { - hl('Lambda', '{'), - hl('IdentifierName', 'a'), - hl('Comma', ','), - hl('IdentifierName', 'b'), - }) - check_parsing('{a,b->', { - -- 012345 - ast = { - { - 'Lambda:0:0:{', - children = { - { - 'Comma:0:2:,', - children = { - 'PlainIdentifier(scope=0,ident=a):0:1:a', - 'PlainIdentifier(scope=0,ident=b):0:3:b', - }, - }, - 'Arrow:0:4:->', - }, - }, - }, - err = { - arg = '', - msg = 'E15: Expected value, got EOC: %.*s', - }, - }, { - hl('Lambda', '{'), - hl('IdentifierName', 'a'), - hl('Comma', ','), - hl('IdentifierName', 'b'), - hl('Arrow', '->'), - }) - check_parsing('{a,b->c', { - -- 0123456 - ast = { - { - 'Lambda:0:0:{', - children = { - { - 'Comma:0:2:,', - children = { - 'PlainIdentifier(scope=0,ident=a):0:1:a', - 'PlainIdentifier(scope=0,ident=b):0:3:b', - }, - }, - { - 'Arrow:0:4:->', - children = { - 'PlainIdentifier(scope=0,ident=c):0:6:c', - }, - }, - }, - }, - }, - err = { - arg = '{a,b->c', - msg = 'E15: Missing closing figure brace for lambda: %.*s', - }, - }, { - hl('Lambda', '{'), - hl('IdentifierName', 'a'), - hl('Comma', ','), - hl('IdentifierName', 'b'), - hl('Arrow', '->'), - hl('IdentifierName', 'c'), - }) - check_parsing('{a : b', { - -- 012345 - ast = { - { - 'DictLiteral:0:0:{', - children = { - { - 'Colon:0:2: :', - children = { - 'PlainIdentifier(scope=0,ident=a):0:1:a', - 'PlainIdentifier(scope=0,ident=b):0:4: b', - }, - }, - }, - }, - }, - err = { - arg = '{a : b', - msg = 'E723: Missing end of Dictionary \'}\': %.*s', - }, - }, { - hl('Dict', '{'), - hl('IdentifierName', 'a'), - hl('Colon', ':', 1), - hl('IdentifierName', 'b', 1), - }) - check_parsing('{a : b,', { - -- 0123456 - ast = { - { - 'DictLiteral:0:0:{', - children = { - { - 'Comma:0:6:,', - children = { - { - 'Colon:0:2: :', - children = { - 'PlainIdentifier(scope=0,ident=a):0:1:a', - 'PlainIdentifier(scope=0,ident=b):0:4: b', - }, - }, - }, - }, - }, - }, - }, - err = { - arg = '', - msg = 'E15: Expected value, got EOC: %.*s', - }, - }, { - hl('Dict', '{'), - hl('IdentifierName', 'a'), - hl('Colon', ':', 1), - hl('IdentifierName', 'b', 1), - hl('Comma', ','), - }) - end) - it('works with ternary operator', function() - check_parsing('a ? b : c', { - -- 012345678 - ast = { - { - 'Ternary:0:1: ?', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - { - 'TernaryValue:0:5: :', - children = { - 'PlainIdentifier(scope=0,ident=b):0:3: b', - 'PlainIdentifier(scope=0,ident=c):0:7: c', - }, - }, - }, - }, - }, - }, { - hl('IdentifierName', 'a'), - hl('Ternary', '?', 1), - hl('IdentifierName', 'b', 1), - hl('TernaryColon', ':', 1), - hl('IdentifierName', 'c', 1), - }) - check_parsing('@a?@b?@c:@d:@e', { - -- 01234567890123 - -- 0 1 - ast = { - { - 'Ternary:0:2:?', - children = { - 'Register(name=a):0:0:@a', - { - 'TernaryValue:0:11::', - children = { - { - 'Ternary:0:5:?', - children = { - 'Register(name=b):0:3:@b', - { - 'TernaryValue:0:8::', - children = { - 'Register(name=c):0:6:@c', - 'Register(name=d):0:9:@d', - }, - }, - }, - }, - 'Register(name=e):0:12:@e', - }, - }, - }, - }, - }, - }, { - hl('Register', '@a'), - hl('Ternary', '?'), - hl('Register', '@b'), - hl('Ternary', '?'), - hl('Register', '@c'), - hl('TernaryColon', ':'), - hl('Register', '@d'), - hl('TernaryColon', ':'), - hl('Register', '@e'), - }) - check_parsing('@a?@b:@c?@d:@e', { - -- 01234567890123 - -- 0 1 - ast = { - { - 'Ternary:0:2:?', - children = { - 'Register(name=a):0:0:@a', - { - 'TernaryValue:0:5::', - children = { - 'Register(name=b):0:3:@b', - { - 'Ternary:0:8:?', - children = { - 'Register(name=c):0:6:@c', - { - 'TernaryValue:0:11::', - children = { - 'Register(name=d):0:9:@d', - 'Register(name=e):0:12:@e', - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, { - hl('Register', '@a'), - hl('Ternary', '?'), - hl('Register', '@b'), - hl('TernaryColon', ':'), - hl('Register', '@c'), - hl('Ternary', '?'), - hl('Register', '@d'), - hl('TernaryColon', ':'), - hl('Register', '@e'), - }) - check_parsing('@a?@b?@c?@d:@e?@f:@g:@h?@i:@j:@k', { - -- 01234567890123456789012345678901 - -- 0 1 2 3 - ast = { - { - 'Ternary:0:2:?', - children = { - 'Register(name=a):0:0:@a', - { - 'TernaryValue:0:29::', - children = { - { - 'Ternary:0:5:?', - children = { - 'Register(name=b):0:3:@b', - { - 'TernaryValue:0:20::', - children = { - { - 'Ternary:0:8:?', - children = { - 'Register(name=c):0:6:@c', - { - 'TernaryValue:0:11::', - children = { - 'Register(name=d):0:9:@d', - { - 'Ternary:0:14:?', - children = { - 'Register(name=e):0:12:@e', - { - 'TernaryValue:0:17::', - children = { - 'Register(name=f):0:15:@f', - 'Register(name=g):0:18:@g', - }, - }, - }, - }, - }, - }, - }, - }, - { - 'Ternary:0:23:?', - children = { - 'Register(name=h):0:21:@h', - { - 'TernaryValue:0:26::', - children = { - 'Register(name=i):0:24:@i', - 'Register(name=j):0:27:@j', - }, - }, - }, - }, - }, - }, - }, - }, - 'Register(name=k):0:30:@k', - }, - }, - }, - }, - }, - }, { - hl('Register', '@a'), - hl('Ternary', '?'), - hl('Register', '@b'), - hl('Ternary', '?'), - hl('Register', '@c'), - hl('Ternary', '?'), - hl('Register', '@d'), - hl('TernaryColon', ':'), - hl('Register', '@e'), - hl('Ternary', '?'), - hl('Register', '@f'), - hl('TernaryColon', ':'), - hl('Register', '@g'), - hl('TernaryColon', ':'), - hl('Register', '@h'), - hl('Ternary', '?'), - hl('Register', '@i'), - hl('TernaryColon', ':'), - hl('Register', '@j'), - hl('TernaryColon', ':'), - hl('Register', '@k'), - }) - check_parsing('?', { - -- 0 - ast = { - { - 'Ternary:0:0:?', - children = { - 'Missing:0:0:', - 'TernaryValue:0:0:?', - }, - }, - }, - err = { - arg = '?', - msg = 'E15: Expected value, got question mark: %.*s', - }, - }, { - hl('InvalidTernary', '?'), - }) - - check_parsing('?:', { - -- 01 - ast = { - { - 'Ternary:0:0:?', - children = { - 'Missing:0:0:', - { - 'TernaryValue:0:1::', - children = { - 'Missing:0:1:', - }, - }, - }, - }, - }, - err = { - arg = '?:', - msg = 'E15: Expected value, got question mark: %.*s', - }, - }, { - hl('InvalidTernary', '?'), - hl('InvalidTernaryColon', ':'), - }) - - check_parsing('?::', { - -- 012 - ast = { - { - 'Colon:0:2::', - children = { - { - 'Ternary:0:0:?', - children = { - 'Missing:0:0:', - { - 'TernaryValue:0:1::', - children = { - 'Missing:0:1:', - 'Missing:0:2:', - }, - }, - }, - }, - }, - }, - }, - err = { - arg = '?::', - msg = 'E15: Expected value, got question mark: %.*s', - }, - }, { - hl('InvalidTernary', '?'), - hl('InvalidTernaryColon', ':'), - hl('InvalidColon', ':'), - }) - - check_parsing('a?b', { - -- 012 - ast = { - { - 'Ternary:0:1:?', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - { - 'TernaryValue:0:1:?', - children = { - 'PlainIdentifier(scope=0,ident=b):0:2:b', - }, - }, - }, - }, - }, - err = { - arg = '?b', - msg = 'E109: Missing \':\' after \'?\': %.*s', - }, - }, { - hl('IdentifierName', 'a'), - hl('Ternary', '?'), - hl('IdentifierName', 'b'), - }) - check_parsing('a?b:', { - -- 0123 - ast = { - { - 'Ternary:0:1:?', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - { - 'TernaryValue:0:1:?', - children = { - 'PlainIdentifier(scope=b,ident=):0:2:b:', - }, - }, - }, - }, - }, - err = { - arg = '?b:', - msg = 'E109: Missing \':\' after \'?\': %.*s', - }, - }, { - hl('IdentifierName', 'a'), - hl('Ternary', '?'), - hl('IdentifierScope', 'b'), - hl('IdentifierScopeDelimiter', ':'), - }) - - check_parsing('a?b::c', { - -- 012345 - ast = { - { - 'Ternary:0:1:?', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - { - 'TernaryValue:0:4::', - children = { - 'PlainIdentifier(scope=b,ident=):0:2:b:', - 'PlainIdentifier(scope=0,ident=c):0:5:c', - }, - }, - }, - }, - }, - }, { - hl('IdentifierName', 'a'), - hl('Ternary', '?'), - hl('IdentifierScope', 'b'), - hl('IdentifierScopeDelimiter', ':'), - hl('TernaryColon', ':'), - hl('IdentifierName', 'c'), - }) - - check_parsing('a?b :', { - -- 01234 - ast = { - { - 'Ternary:0:1:?', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - { - 'TernaryValue:0:3: :', - children = { - 'PlainIdentifier(scope=0,ident=b):0:2:b', - }, - }, - }, - }, - }, - err = { - arg = '', - msg = 'E15: Expected value, got EOC: %.*s', - }, - }, { - hl('IdentifierName', 'a'), - hl('Ternary', '?'), - hl('IdentifierName', 'b'), - hl('TernaryColon', ':', 1), - }) - - check_parsing('(@a?@b:@c)?@d:@e', { - -- 0123456789012345 - -- 0 1 - ast = { - { - 'Ternary:0:10:?', - children = { - { - 'Nested:0:0:(', - children = { - { - 'Ternary:0:3:?', - children = { - 'Register(name=a):0:1:@a', - { - 'TernaryValue:0:6::', - children = { - 'Register(name=b):0:4:@b', - 'Register(name=c):0:7:@c', - }, - }, - }, - }, - }, - }, - { - 'TernaryValue:0:13::', - children = { - 'Register(name=d):0:11:@d', - 'Register(name=e):0:14:@e', - }, - }, - }, - }, - }, - }, { - hl('NestingParenthesis', '('), - hl('Register', '@a'), - hl('Ternary', '?'), - hl('Register', '@b'), - hl('TernaryColon', ':'), - hl('Register', '@c'), - hl('NestingParenthesis', ')'), - hl('Ternary', '?'), - hl('Register', '@d'), - hl('TernaryColon', ':'), - hl('Register', '@e'), - }) - - check_parsing('(@a?@b:@c)?(@d?@e:@f):(@g?@h:@i)', { - -- 01234567890123456789012345678901 - -- 0 1 2 3 - ast = { - { - 'Ternary:0:10:?', - children = { - { - 'Nested:0:0:(', - children = { - { - 'Ternary:0:3:?', - children = { - 'Register(name=a):0:1:@a', - { - 'TernaryValue:0:6::', - children = { - 'Register(name=b):0:4:@b', - 'Register(name=c):0:7:@c', - }, - }, - }, - }, - }, - }, - { - 'TernaryValue:0:21::', - children = { - { - 'Nested:0:11:(', - children = { - { - 'Ternary:0:14:?', - children = { - 'Register(name=d):0:12:@d', - { - 'TernaryValue:0:17::', - children = { - 'Register(name=e):0:15:@e', - 'Register(name=f):0:18:@f', - }, - }, - }, - }, - }, - }, - { - 'Nested:0:22:(', - children = { - { - 'Ternary:0:25:?', - children = { - 'Register(name=g):0:23:@g', - { - 'TernaryValue:0:28::', - children = { - 'Register(name=h):0:26:@h', - 'Register(name=i):0:29:@i', - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, { - hl('NestingParenthesis', '('), - hl('Register', '@a'), - hl('Ternary', '?'), - hl('Register', '@b'), - hl('TernaryColon', ':'), - hl('Register', '@c'), - hl('NestingParenthesis', ')'), - hl('Ternary', '?'), - hl('NestingParenthesis', '('), - hl('Register', '@d'), - hl('Ternary', '?'), - hl('Register', '@e'), - hl('TernaryColon', ':'), - hl('Register', '@f'), - hl('NestingParenthesis', ')'), - hl('TernaryColon', ':'), - hl('NestingParenthesis', '('), - hl('Register', '@g'), - hl('Ternary', '?'), - hl('Register', '@h'), - hl('TernaryColon', ':'), - hl('Register', '@i'), - hl('NestingParenthesis', ')'), - }) - - check_parsing('(@a?@b:@c)?@d?@e:@f:@g?@h:@i', { - -- 0123456789012345678901234567 - -- 0 1 2 - ast = { - { - 'Ternary:0:10:?', - children = { - { - 'Nested:0:0:(', - children = { - { - 'Ternary:0:3:?', - children = { - 'Register(name=a):0:1:@a', - { - 'TernaryValue:0:6::', - children = { - 'Register(name=b):0:4:@b', - 'Register(name=c):0:7:@c', - }, - }, - }, - }, - }, - }, - { - 'TernaryValue:0:19::', - children = { - { - 'Ternary:0:13:?', - children = { - 'Register(name=d):0:11:@d', - { - 'TernaryValue:0:16::', - children = { - 'Register(name=e):0:14:@e', - 'Register(name=f):0:17:@f', - }, - }, - }, - }, - { - 'Ternary:0:22:?', - children = { - 'Register(name=g):0:20:@g', - { - 'TernaryValue:0:25::', - children = { - 'Register(name=h):0:23:@h', - 'Register(name=i):0:26:@i', - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, { - hl('NestingParenthesis', '('), - hl('Register', '@a'), - hl('Ternary', '?'), - hl('Register', '@b'), - hl('TernaryColon', ':'), - hl('Register', '@c'), - hl('NestingParenthesis', ')'), - hl('Ternary', '?'), - hl('Register', '@d'), - hl('Ternary', '?'), - hl('Register', '@e'), - hl('TernaryColon', ':'), - hl('Register', '@f'), - hl('TernaryColon', ':'), - hl('Register', '@g'), - hl('Ternary', '?'), - hl('Register', '@h'), - hl('TernaryColon', ':'), - hl('Register', '@i'), - }) - check_parsing('a?b{cdef}g:h', { - -- 012345678901 - -- 0 1 - ast = { - { - 'Ternary:0:1:?', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - { - 'TernaryValue:0:10::', - children = { - { - 'ComplexIdentifier:0:3:', - children = { - 'PlainIdentifier(scope=0,ident=b):0:2:b', - { - 'ComplexIdentifier:0:9:', - children = { - { - 'CurlyBracesIdentifier:0:3:{', - children = { - 'PlainIdentifier(scope=0,ident=cdef):0:4:cdef', - }, - }, - 'PlainIdentifier(scope=0,ident=g):0:9:g', - }, - }, - }, - }, - 'PlainIdentifier(scope=0,ident=h):0:11:h', - }, - }, - }, - }, - }, - }, { - hl('IdentifierName', 'a'), - hl('Ternary', '?'), - hl('IdentifierName', 'b'), - hl('Curly', '{'), - hl('IdentifierName', 'cdef'), - hl('Curly', '}'), - hl('IdentifierName', 'g'), - hl('TernaryColon', ':'), - hl('IdentifierName', 'h'), - }) - check_parsing('a ? b : c : d', { - -- 0123456789012 - -- 0 1 - ast = { - { - 'Colon:0:9: :', - children = { - { - 'Ternary:0:1: ?', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - { - 'TernaryValue:0:5: :', - children = { - 'PlainIdentifier(scope=0,ident=b):0:3: b', - 'PlainIdentifier(scope=0,ident=c):0:7: c', - }, - }, - }, - }, - 'PlainIdentifier(scope=0,ident=d):0:11: d', - }, - }, - }, - err = { - arg = ': d', - msg = 'E15: Colon outside of dictionary or ternary operator: %.*s', - }, - }, { - hl('IdentifierName', 'a'), - hl('Ternary', '?', 1), - hl('IdentifierName', 'b', 1), - hl('TernaryColon', ':', 1), - hl('IdentifierName', 'c', 1), - hl('InvalidColon', ':', 1), - hl('IdentifierName', 'd', 1), - }) - end) - it('works with comparison operators', function() - check_parsing('a == b', { - -- 012345 - ast = { - { - 'Comparison(type=Equal,inv=0,ccs=UseOption):0:1: ==', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - 'PlainIdentifier(scope=0,ident=b):0:4: b', - }, - }, - }, - }, { - hl('IdentifierName', 'a'), - hl('Comparison', '==', 1), - hl('IdentifierName', 'b', 1), - }) - - check_parsing('a ==? b', { - -- 0123456 - ast = { - { - 'Comparison(type=Equal,inv=0,ccs=IgnoreCase):0:1: ==?', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - 'PlainIdentifier(scope=0,ident=b):0:5: b', - }, - }, - }, - }, { - hl('IdentifierName', 'a'), - hl('Comparison', '==', 1), - hl('ComparisonModifier', '?'), - hl('IdentifierName', 'b', 1), - }) - - check_parsing('a ==# b', { - -- 0123456 - ast = { - { - 'Comparison(type=Equal,inv=0,ccs=MatchCase):0:1: ==#', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - 'PlainIdentifier(scope=0,ident=b):0:5: b', - }, - }, - }, - }, { - hl('IdentifierName', 'a'), - hl('Comparison', '==', 1), - hl('ComparisonModifier', '#'), - hl('IdentifierName', 'b', 1), - }) - - check_parsing('a !=# b', { - -- 0123456 - ast = { - { - 'Comparison(type=Equal,inv=1,ccs=MatchCase):0:1: !=#', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - 'PlainIdentifier(scope=0,ident=b):0:5: b', - }, - }, - }, - }, { - hl('IdentifierName', 'a'), - hl('Comparison', '!=', 1), - hl('ComparisonModifier', '#'), - hl('IdentifierName', 'b', 1), - }) - - check_parsing('a <=# b', { - -- 0123456 - ast = { - { - 'Comparison(type=Greater,inv=1,ccs=MatchCase):0:1: <=#', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - 'PlainIdentifier(scope=0,ident=b):0:5: b', - }, - }, - }, - }, { - hl('IdentifierName', 'a'), - hl('Comparison', '<=', 1), - hl('ComparisonModifier', '#'), - hl('IdentifierName', 'b', 1), - }) - - check_parsing('a >=# b', { - -- 0123456 - ast = { - { - 'Comparison(type=GreaterOrEqual,inv=0,ccs=MatchCase):0:1: >=#', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - 'PlainIdentifier(scope=0,ident=b):0:5: b', - }, - }, - }, - }, { - hl('IdentifierName', 'a'), - hl('Comparison', '>=', 1), - hl('ComparisonModifier', '#'), - hl('IdentifierName', 'b', 1), - }) - - check_parsing('a ># b', { - -- 012345 - ast = { - { - 'Comparison(type=Greater,inv=0,ccs=MatchCase):0:1: >#', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - 'PlainIdentifier(scope=0,ident=b):0:4: b', - }, - }, - }, - }, { - hl('IdentifierName', 'a'), - hl('Comparison', '>', 1), - hl('ComparisonModifier', '#'), - hl('IdentifierName', 'b', 1), - }) - - check_parsing('a <# b', { - -- 012345 - ast = { - { - 'Comparison(type=GreaterOrEqual,inv=1,ccs=MatchCase):0:1: <#', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - 'PlainIdentifier(scope=0,ident=b):0:4: b', - }, - }, - }, - }, { - hl('IdentifierName', 'a'), - hl('Comparison', '<', 1), - hl('ComparisonModifier', '#'), - hl('IdentifierName', 'b', 1), - }) - - check_parsing('a is#b', { - -- 012345 - ast = { - { - 'Comparison(type=Identical,inv=0,ccs=MatchCase):0:1: is#', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - 'PlainIdentifier(scope=0,ident=b):0:5:b', - }, - }, - }, - }, { - hl('IdentifierName', 'a'), - hl('Comparison', 'is', 1), - hl('ComparisonModifier', '#'), - hl('IdentifierName', 'b'), - }) - - check_parsing('a is?b', { - -- 012345 - ast = { - { - 'Comparison(type=Identical,inv=0,ccs=IgnoreCase):0:1: is?', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - 'PlainIdentifier(scope=0,ident=b):0:5:b', - }, - }, - }, - }, { - hl('IdentifierName', 'a'), - hl('Comparison', 'is', 1), - hl('ComparisonModifier', '?'), - hl('IdentifierName', 'b'), - }) - - check_parsing('a isnot b', { - -- 012345678 - ast = { - { - 'Comparison(type=Identical,inv=1,ccs=UseOption):0:1: isnot', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - 'PlainIdentifier(scope=0,ident=b):0:7: b', - }, - }, - }, - }, { - hl('IdentifierName', 'a'), - hl('Comparison', 'isnot', 1), - hl('IdentifierName', 'b', 1), - }) - - check_parsing('a < b < c', { - -- 012345678 - ast = { - { - 'Comparison(type=GreaterOrEqual,inv=1,ccs=UseOption):0:1: <', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - { - 'Comparison(type=GreaterOrEqual,inv=1,ccs=UseOption):0:5: <', - children = { - 'PlainIdentifier(scope=0,ident=b):0:3: b', - 'PlainIdentifier(scope=0,ident=c):0:7: c', - }, - }, - }, - }, - }, - err = { - arg = ' < c', - msg = 'E15: Operator is not associative: %.*s', - }, - }, { - hl('IdentifierName', 'a'), - hl('Comparison', '<', 1), - hl('IdentifierName', 'b', 1), - hl('InvalidComparison', '<', 1), - hl('IdentifierName', 'c', 1), - }) - - check_parsing('a < b <# c', { - -- 012345678 - ast = { - { - 'Comparison(type=GreaterOrEqual,inv=1,ccs=UseOption):0:1: <', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - { - 'Comparison(type=GreaterOrEqual,inv=1,ccs=MatchCase):0:5: <#', - children = { - 'PlainIdentifier(scope=0,ident=b):0:3: b', - 'PlainIdentifier(scope=0,ident=c):0:8: c', - }, - }, - }, - }, - }, - err = { - arg = ' <# c', - msg = 'E15: Operator is not associative: %.*s', - }, - }, { - hl('IdentifierName', 'a'), - hl('Comparison', '<', 1), - hl('IdentifierName', 'b', 1), - hl('InvalidComparison', '<', 1), - hl('InvalidComparisonModifier', '#'), - hl('IdentifierName', 'c', 1), - }) - - check_parsing('a += b', { - -- 012345 - ast = { - { - 'Comparison(type=Equal,inv=0,ccs=UseOption):0:3:=', - children = { - { - 'BinaryPlus:0:1: +', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - 'Missing:0:3:', - }, - }, - 'PlainIdentifier(scope=0,ident=b):0:4: b', - }, - }, - }, - err = { - arg = '= b', - msg = 'E15: Expected == or =~: %.*s', - }, - }, { - hl('IdentifierName', 'a'), - hl('BinaryPlus', '+', 1), - hl('InvalidComparison', '='), - hl('IdentifierName', 'b', 1), - }) - check_parsing('a + b == c + d', { - -- 01234567890123 - -- 0 1 - ast = { - { - 'Comparison(type=Equal,inv=0,ccs=UseOption):0:5: ==', - children = { - { - 'BinaryPlus:0:1: +', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - 'PlainIdentifier(scope=0,ident=b):0:3: b', - }, - }, - { - 'BinaryPlus:0:10: +', - children = { - 'PlainIdentifier(scope=0,ident=c):0:8: c', - 'PlainIdentifier(scope=0,ident=d):0:12: d', - }, - }, - }, - }, - }, - }, { - hl('IdentifierName', 'a'), - hl('BinaryPlus', '+', 1), - hl('IdentifierName', 'b', 1), - hl('Comparison', '==', 1), - hl('IdentifierName', 'c', 1), - hl('BinaryPlus', '+', 1), - hl('IdentifierName', 'd', 1), - }) - check_parsing('+ a == + b', { - -- 0123456789 - ast = { - { - 'Comparison(type=Equal,inv=0,ccs=UseOption):0:3: ==', - children = { - { - 'UnaryPlus:0:0:+', - children = { - 'PlainIdentifier(scope=0,ident=a):0:1: a', - }, - }, - { - 'UnaryPlus:0:6: +', - children = { - 'PlainIdentifier(scope=0,ident=b):0:8: b', - }, - }, - }, - }, - }, - }, { - hl('UnaryPlus', '+'), - hl('IdentifierName', 'a', 1), - hl('Comparison', '==', 1), - hl('UnaryPlus', '+', 1), - hl('IdentifierName', 'b', 1), - }) - end) - it('works with concat/subscript', function() - check_parsing('.', { - -- 0 - ast = { - { - 'ConcatOrSubscript:0:0:.', - children = { - 'Missing:0:0:', - }, - }, - }, - err = { - arg = '.', - msg = 'E15: Unexpected dot: %.*s', - }, - }, { - hl('InvalidConcatOrSubscript', '.'), - }) - - check_parsing('a.', { - -- 01 - ast = { - { - 'ConcatOrSubscript:0:1:.', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - }, - }, - }, - err = { - arg = '', - msg = 'E15: Expected value, got EOC: %.*s', - }, - }, { - hl('IdentifierName', 'a'), - hl('ConcatOrSubscript', '.'), - }) - - check_parsing('a.b', { - -- 012 - ast = { - { - 'ConcatOrSubscript:0:1:.', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - 'PlainKey(key=b):0:2:b', - }, - }, - }, - }, { - hl('IdentifierName', 'a'), - hl('ConcatOrSubscript', '.'), - hl('IdentifierKey', 'b'), - }) - - check_parsing('1.2', { - -- 012 - ast = { - 'Float(val=1.200000e+00):0:0:1.2', - }, - }, { - hl('Float', '1.2'), - }) - - check_parsing('1.2 + 1.3e-5', { - -- 012345678901 - -- 0 1 - ast = { - { - 'BinaryPlus:0:3: +', - children = { - 'Float(val=1.200000e+00):0:0:1.2', - 'Float(val=1.300000e-05):0:5: 1.3e-5', - }, - }, - }, - }, { - hl('Float', '1.2'), - hl('BinaryPlus', '+', 1), - hl('Float', '1.3e-5', 1), - }) - - check_parsing('a . 1.2 + 1.3e-5', { - -- 0123456789012345 - -- 0 1 - ast = { - { - 'BinaryPlus:0:7: +', - children = { - { - 'Concat:0:1: .', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - { - 'ConcatOrSubscript:0:5:.', - children = { - 'Integer(val=1):0:3: 1', - 'PlainKey(key=2):0:6:2', - }, - }, - }, - }, - 'Float(val=1.300000e-05):0:9: 1.3e-5', - }, - }, - }, - }, { - hl('IdentifierName', 'a'), - hl('Concat', '.', 1), - hl('Number', '1', 1), - hl('ConcatOrSubscript', '.'), - hl('IdentifierKey', '2'), - hl('BinaryPlus', '+', 1), - hl('Float', '1.3e-5', 1), - }) - - check_parsing('1.3e-5 + 1.2 . a', { - -- 0123456789012345 - -- 0 1 - ast = { - { - 'Concat:0:12: .', - children = { - { - 'BinaryPlus:0:6: +', - children = { - 'Float(val=1.300000e-05):0:0:1.3e-5', - 'Float(val=1.200000e+00):0:8: 1.2', - }, - }, - 'PlainIdentifier(scope=0,ident=a):0:14: a', - }, - }, - }, - }, { - hl('Float', '1.3e-5'), - hl('BinaryPlus', '+', 1), - hl('Float', '1.2', 1), - hl('Concat', '.', 1), - hl('IdentifierName', 'a', 1), - }) - - check_parsing('1.3e-5 + a . 1.2', { - -- 0123456789012345 - -- 0 1 - ast = { - { - 'Concat:0:10: .', - children = { - { - 'BinaryPlus:0:6: +', - children = { - 'Float(val=1.300000e-05):0:0:1.3e-5', - 'PlainIdentifier(scope=0,ident=a):0:8: a', - }, - }, - { - 'ConcatOrSubscript:0:14:.', - children = { - 'Integer(val=1):0:12: 1', - 'PlainKey(key=2):0:15:2', - }, - }, - }, - }, - }, - }, { - hl('Float', '1.3e-5'), - hl('BinaryPlus', '+', 1), - hl('IdentifierName', 'a', 1), - hl('Concat', '.', 1), - hl('Number', '1', 1), - hl('ConcatOrSubscript', '.'), - hl('IdentifierKey', '2'), - }) - - check_parsing('1.2.3', { - -- 01234 - ast = { - { - 'ConcatOrSubscript:0:3:.', - children = { - { - 'ConcatOrSubscript:0:1:.', - children = { - 'Integer(val=1):0:0:1', - 'PlainKey(key=2):0:2:2', - }, - }, - 'PlainKey(key=3):0:4:3', - }, - }, - }, - }, { - hl('Number', '1'), - hl('ConcatOrSubscript', '.'), - hl('IdentifierKey', '2'), - hl('ConcatOrSubscript', '.'), - hl('IdentifierKey', '3'), - }) - - check_parsing('a.1.2', { - -- 01234 - ast = { - { - 'ConcatOrSubscript:0:3:.', - children = { - { - 'ConcatOrSubscript:0:1:.', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - 'PlainKey(key=1):0:2:1', - }, - }, - 'PlainKey(key=2):0:4:2', - }, - }, - }, - }, { - hl('IdentifierName', 'a'), - hl('ConcatOrSubscript', '.'), - hl('IdentifierKey', '1'), - hl('ConcatOrSubscript', '.'), - hl('IdentifierKey', '2'), - }) - - check_parsing('a . 1.2', { - -- 0123456 - ast = { - { - 'Concat:0:1: .', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - { - 'ConcatOrSubscript:0:5:.', - children = { - 'Integer(val=1):0:3: 1', - 'PlainKey(key=2):0:6:2', - }, - }, - }, - }, - }, - }, { - hl('IdentifierName', 'a'), - hl('Concat', '.', 1), - hl('Number', '1', 1), - hl('ConcatOrSubscript', '.'), - hl('IdentifierKey', '2'), - }) - - check_parsing('+a . +b', { - -- 0123456 - ast = { - { - 'Concat:0:2: .', - children = { - { - 'UnaryPlus:0:0:+', - children = { - 'PlainIdentifier(scope=0,ident=a):0:1:a', - }, - }, - { - 'UnaryPlus:0:4: +', - children = { - 'PlainIdentifier(scope=0,ident=b):0:6:b', - }, - }, - }, - }, - }, - }, { - hl('UnaryPlus', '+'), - hl('IdentifierName', 'a'), - hl('Concat', '.', 1), - hl('UnaryPlus', '+', 1), - hl('IdentifierName', 'b'), - }) - - check_parsing('a. b', { - -- 0123 - ast = { - { - 'Concat:0:1:.', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - 'PlainIdentifier(scope=0,ident=b):0:2: b', - }, - }, - }, - }, { - hl('IdentifierName', 'a'), - hl('ConcatOrSubscript', '.'), - hl('IdentifierName', 'b', 1), - }) - - check_parsing('a. 1', { - -- 0123 - ast = { - { - 'Concat:0:1:.', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - 'Integer(val=1):0:2: 1', - }, - }, - }, - }, { - hl('IdentifierName', 'a'), - hl('ConcatOrSubscript', '.'), - hl('Number', '1', 1), - }) - end) - it('works with bracket subscripts', function() - check_parsing(':', { - -- 0 - ast = { - { - 'Colon:0:0::', - children = { - 'Missing:0:0:', - }, - }, - }, - err = { - arg = ':', - msg = 'E15: Colon outside of dictionary or ternary operator: %.*s', - }, - }, { - hl('InvalidColon', ':'), - }) - check_parsing('a[]', { - -- 012 - ast = { - { - 'Subscript:0:1:[', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - }, - }, - }, - err = { - arg = ']', - msg = 'E15: Expected value, got closing bracket: %.*s', - }, - }, { - hl('IdentifierName', 'a'), - hl('SubscriptBracket', '['), - hl('InvalidSubscriptBracket', ']'), - }) - check_parsing('a[b:]', { - -- 01234 - ast = { - { - 'Subscript:0:1:[', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - 'PlainIdentifier(scope=b,ident=):0:2:b:', - }, - }, - }, - }, { - hl('IdentifierName', 'a'), - hl('SubscriptBracket', '['), - hl('IdentifierScope', 'b'), - hl('IdentifierScopeDelimiter', ':'), - hl('SubscriptBracket', ']'), - }) - - check_parsing('a[b:c]', { - -- 012345 - ast = { - { - 'Subscript:0:1:[', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - 'PlainIdentifier(scope=b,ident=c):0:2:b:c', - }, - }, - }, - }, { - hl('IdentifierName', 'a'), - hl('SubscriptBracket', '['), - hl('IdentifierScope', 'b'), - hl('IdentifierScopeDelimiter', ':'), - hl('IdentifierName', 'c'), - hl('SubscriptBracket', ']'), - }) - check_parsing('a[b : c]', { - -- 01234567 - ast = { - { - 'Subscript:0:1:[', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - { - 'Colon:0:3: :', - children = { - 'PlainIdentifier(scope=0,ident=b):0:2:b', - 'PlainIdentifier(scope=0,ident=c):0:5: c', - }, - }, - }, - }, - }, - }, { - hl('IdentifierName', 'a'), - hl('SubscriptBracket', '['), - hl('IdentifierName', 'b'), - hl('SubscriptColon', ':', 1), - hl('IdentifierName', 'c', 1), - hl('SubscriptBracket', ']'), - }) - - check_parsing('a[: b]', { - -- 012345 - ast = { - { - 'Subscript:0:1:[', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - { - 'Colon:0:2::', - children = { - 'Missing:0:2:', - 'PlainIdentifier(scope=0,ident=b):0:3: b', - }, - }, - }, - }, - }, - }, { - hl('IdentifierName', 'a'), - hl('SubscriptBracket', '['), - hl('SubscriptColon', ':'), - hl('IdentifierName', 'b', 1), - hl('SubscriptBracket', ']'), - }) - - check_parsing('a[b :]', { - -- 012345 - ast = { - { - 'Subscript:0:1:[', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - { - 'Colon:0:3: :', - children = { - 'PlainIdentifier(scope=0,ident=b):0:2:b', - }, - }, - }, - }, - }, - }, { - hl('IdentifierName', 'a'), - hl('SubscriptBracket', '['), - hl('IdentifierName', 'b'), - hl('SubscriptColon', ':', 1), - hl('SubscriptBracket', ']'), - }) - check_parsing('a[b][c][d](e)(f)(g)', { - -- 0123456789012345678 - -- 0 1 - ast = { - { - 'Call:0:16:(', - children = { - { - 'Call:0:13:(', - children = { - { - 'Call:0:10:(', - children = { - { - 'Subscript:0:7:[', - children = { - { - 'Subscript:0:4:[', - children = { - { - 'Subscript:0:1:[', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - 'PlainIdentifier(scope=0,ident=b):0:2:b', - }, - }, - 'PlainIdentifier(scope=0,ident=c):0:5:c', - }, - }, - 'PlainIdentifier(scope=0,ident=d):0:8:d', - }, - }, - 'PlainIdentifier(scope=0,ident=e):0:11:e', - }, - }, - 'PlainIdentifier(scope=0,ident=f):0:14:f', - }, - }, - 'PlainIdentifier(scope=0,ident=g):0:17:g', - }, - }, - }, - }, { - hl('IdentifierName', 'a'), - hl('SubscriptBracket', '['), - hl('IdentifierName', 'b'), - hl('SubscriptBracket', ']'), - hl('SubscriptBracket', '['), - hl('IdentifierName', 'c'), - hl('SubscriptBracket', ']'), - hl('SubscriptBracket', '['), - hl('IdentifierName', 'd'), - hl('SubscriptBracket', ']'), - hl('CallingParenthesis', '('), - hl('IdentifierName', 'e'), - hl('CallingParenthesis', ')'), - hl('CallingParenthesis', '('), - hl('IdentifierName', 'f'), - hl('CallingParenthesis', ')'), - hl('CallingParenthesis', '('), - hl('IdentifierName', 'g'), - hl('CallingParenthesis', ')'), - }) - check_parsing('{a}{b}{c}[d][e][f]', { - -- 012345678901234567 - -- 0 1 - ast = { - { - 'Subscript:0:15:[', - children = { - { - 'Subscript:0:12:[', - children = { - { - 'Subscript:0:9:[', - children = { - { - 'ComplexIdentifier:0:3:', - children = { - { - 'CurlyBracesIdentifier:0:0:{', - children = { - 'PlainIdentifier(scope=0,ident=a):0:1:a', - }, - }, - { - 'ComplexIdentifier:0:6:', - children = { - { - 'CurlyBracesIdentifier:0:3:{', - children = { - 'PlainIdentifier(scope=0,ident=b):0:4:b', - }, - }, - { - 'CurlyBracesIdentifier:0:6:{', - children = { - 'PlainIdentifier(scope=0,ident=c):0:7:c', - }, - }, - }, - }, - }, - }, - 'PlainIdentifier(scope=0,ident=d):0:10:d', - }, - }, - 'PlainIdentifier(scope=0,ident=e):0:13:e', - }, - }, - 'PlainIdentifier(scope=0,ident=f):0:16:f', - }, - }, - }, - }, { - hl('Curly', '{'), - hl('IdentifierName', 'a'), - hl('Curly', '}'), - hl('Curly', '{'), - hl('IdentifierName', 'b'), - hl('Curly', '}'), - hl('Curly', '{'), - hl('IdentifierName', 'c'), - hl('Curly', '}'), - hl('SubscriptBracket', '['), - hl('IdentifierName', 'd'), - hl('SubscriptBracket', ']'), - hl('SubscriptBracket', '['), - hl('IdentifierName', 'e'), - hl('SubscriptBracket', ']'), - hl('SubscriptBracket', '['), - hl('IdentifierName', 'f'), - hl('SubscriptBracket', ']'), - }) - end) - it('supports list literals', function() - check_parsing('[]', { - -- 01 - ast = { - 'ListLiteral:0:0:[', - }, - }, { - hl('List', '['), - hl('List', ']'), - }) - - check_parsing('[a]', { - -- 012 - ast = { - { - 'ListLiteral:0:0:[', - children = { - 'PlainIdentifier(scope=0,ident=a):0:1:a', - }, - }, - }, - }, { - hl('List', '['), - hl('IdentifierName', 'a'), - hl('List', ']'), - }) - - check_parsing('[a, b]', { - -- 012345 - ast = { - { - 'ListLiteral:0:0:[', - children = { - { - 'Comma:0:2:,', - children = { - 'PlainIdentifier(scope=0,ident=a):0:1:a', - 'PlainIdentifier(scope=0,ident=b):0:3: b', - }, - }, - }, - }, - }, - }, { - hl('List', '['), - hl('IdentifierName', 'a'), - hl('Comma', ','), - hl('IdentifierName', 'b', 1), - hl('List', ']'), - }) - - check_parsing('[a, b, c]', { - -- 012345678 - ast = { - { - 'ListLiteral:0:0:[', - children = { - { - 'Comma:0:2:,', - children = { - 'PlainIdentifier(scope=0,ident=a):0:1:a', - { - 'Comma:0:5:,', - children = { - 'PlainIdentifier(scope=0,ident=b):0:3: b', - 'PlainIdentifier(scope=0,ident=c):0:6: c', - }, - }, - }, - }, - }, - }, - }, - }, { - hl('List', '['), - hl('IdentifierName', 'a'), - hl('Comma', ','), - hl('IdentifierName', 'b', 1), - hl('Comma', ','), - hl('IdentifierName', 'c', 1), - hl('List', ']'), - }) - - check_parsing('[a, b, c, ]', { - -- 01234567890 - -- 0 1 - ast = { - { - 'ListLiteral:0:0:[', - children = { - { - 'Comma:0:2:,', - children = { - 'PlainIdentifier(scope=0,ident=a):0:1:a', - { - 'Comma:0:5:,', - children = { - 'PlainIdentifier(scope=0,ident=b):0:3: b', - { - 'Comma:0:8:,', - children = { - 'PlainIdentifier(scope=0,ident=c):0:6: c', - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, { - hl('List', '['), - hl('IdentifierName', 'a'), - hl('Comma', ','), - hl('IdentifierName', 'b', 1), - hl('Comma', ','), - hl('IdentifierName', 'c', 1), - hl('Comma', ','), - hl('List', ']', 1), - }) - - check_parsing('[a : b, c : d]', { - -- 01234567890123 - -- 0 1 - ast = { - { - 'ListLiteral:0:0:[', - children = { - { - 'Comma:0:6:,', - children = { - { - 'Colon:0:2: :', - children = { - 'PlainIdentifier(scope=0,ident=a):0:1:a', - 'PlainIdentifier(scope=0,ident=b):0:4: b', - }, - }, - { - 'Colon:0:9: :', - children = { - 'PlainIdentifier(scope=0,ident=c):0:7: c', - 'PlainIdentifier(scope=0,ident=d):0:11: d', - }, - }, - }, - }, - }, - }, - }, - err = { - arg = ': b, c : d]', - msg = 'E15: Colon outside of dictionary or ternary operator: %.*s', - }, - }, { - hl('List', '['), - hl('IdentifierName', 'a'), - hl('InvalidColon', ':', 1), - hl('IdentifierName', 'b', 1), - hl('Comma', ','), - hl('IdentifierName', 'c', 1), - hl('InvalidColon', ':', 1), - hl('IdentifierName', 'd', 1), - hl('List', ']'), - }) - - check_parsing(']', { - -- 0 - ast = { - 'ListLiteral:0:0:', - }, - err = { - arg = ']', - msg = 'E15: Unexpected closing figure brace: %.*s', - }, - }, { - hl('InvalidList', ']'), - }) - - check_parsing('a]', { - -- 01 - ast = { - { - 'ListLiteral:0:1:', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - }, - }, - }, - err = { - arg = ']', - msg = 'E15: Unexpected closing figure brace: %.*s', - }, - }, { - hl('IdentifierName', 'a'), - hl('InvalidList', ']'), - }) - - check_parsing('[] []', { - -- 01234 - ast = { - { - 'OpMissing:0:2:', - children = { - 'ListLiteral:0:0:[', - 'ListLiteral:0:2: [', - }, - }, - }, - err = { - arg = '[]', - msg = 'E15: Missing operator: %.*s', - }, - }, { - hl('List', '['), - hl('List', ']'), - hl('InvalidSpacing', ' '), - hl('List', '['), - hl('List', ']'), - }, { - [1] = { - ast = { - len = 3, - err = REMOVE_THIS, - ast = { - 'ListLiteral:0:0:[', - }, - }, - hl_fs = { - [3] = REMOVE_THIS, - [4] = REMOVE_THIS, - [5] = REMOVE_THIS, - }, - }, - }) - - check_parsing('[][]', { - -- 0123 - ast = { - { - 'Subscript:0:2:[', - children = { - 'ListLiteral:0:0:[', - }, - }, - }, - err = { - arg = ']', - msg = 'E15: Expected value, got closing bracket: %.*s', - }, - }, { - hl('List', '['), - hl('List', ']'), - hl('SubscriptBracket', '['), - hl('InvalidSubscriptBracket', ']'), - }) - - check_parsing('[', { - -- 0 - ast = { - 'ListLiteral:0:0:[', - }, - err = { - arg = '', - msg = 'E15: Expected value, got EOC: %.*s', - }, - }, { - hl('List', '['), - }) - - check_parsing('[1', { - -- 01 - ast = { - { - 'ListLiteral:0:0:[', - children = { - 'Integer(val=1):0:1:1', - }, - }, - }, - err = { - arg = '[1', - msg = 'E697: Missing end of List \']\': %.*s', - }, - }, { - hl('List', '['), - hl('Number', '1'), - }) - end) - it('works with strings', function() - check_parsing('\'abc\'', { - -- 01234 - ast = { - 'SingleQuotedString(val="abc"):0:0:\'abc\'', - }, - }, { - hl('SingleQuote', '\''), - hl('SingleQuotedBody', 'abc'), - hl('SingleQuote', '\''), - }) - check_parsing('"abc"', { - -- 01234 - ast = { - 'DoubleQuotedString(val="abc"):0:0:"abc"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedBody', 'abc'), - hl('DoubleQuote', '"'), - }) - check_parsing('\'\'', { - -- 01 - ast = { - 'SingleQuotedString(val=""):0:0:\'\'', - }, - }, { - hl('SingleQuote', '\''), - hl('SingleQuote', '\''), - }) - check_parsing('""', { - -- 01 - ast = { - 'DoubleQuotedString(val=""):0:0:""', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuote', '"'), - }) - check_parsing('"', { - -- 0 - ast = { - 'DoubleQuotedString(val=""):0:0:"', - }, - err = { - arg = '"', - msg = 'E114: Missing double quote: %.*s', - }, - }, { - hl('InvalidDoubleQuote', '"'), - }) - check_parsing('\'', { - -- 0 - ast = { - 'SingleQuotedString(val=""):0:0:\'', - }, - err = { - arg = '\'', - msg = 'E115: Missing single quote: %.*s', - }, - }, { - hl('InvalidSingleQuote', '\''), - }) - check_parsing('"a', { - -- 01 - ast = { - 'DoubleQuotedString(val="a"):0:0:"a', - }, - err = { - arg = '"a', - msg = 'E114: Missing double quote: %.*s', - }, - }, { - hl('InvalidDoubleQuote', '"'), - hl('InvalidDoubleQuotedBody', 'a'), - }) - check_parsing('\'a', { - -- 01 - ast = { - 'SingleQuotedString(val="a"):0:0:\'a', - }, - err = { - arg = '\'a', - msg = 'E115: Missing single quote: %.*s', - }, - }, { - hl('InvalidSingleQuote', '\''), - hl('InvalidSingleQuotedBody', 'a'), - }) - check_parsing('\'abc\'\'def\'', { - -- 0123456789 - ast = { - 'SingleQuotedString(val="abc\'def"):0:0:\'abc\'\'def\'', - }, - }, { - hl('SingleQuote', '\''), - hl('SingleQuotedBody', 'abc'), - hl('SingleQuotedQuote', '\'\''), - hl('SingleQuotedBody', 'def'), - hl('SingleQuote', '\''), - }) - check_parsing('\'abc\'\'', { - -- 012345 - ast = { - 'SingleQuotedString(val="abc\'"):0:0:\'abc\'\'', - }, - err = { - arg = '\'abc\'\'', - msg = 'E115: Missing single quote: %.*s', - }, - }, { - hl('InvalidSingleQuote', '\''), - hl('InvalidSingleQuotedBody', 'abc'), - hl('InvalidSingleQuotedQuote', '\'\''), - }) - check_parsing('\'\'\'\'\'\'\'\'', { - -- 01234567 - ast = { - 'SingleQuotedString(val="\'\'\'"):0:0:\'\'\'\'\'\'\'\'', - }, - }, { - hl('SingleQuote', '\''), - hl('SingleQuotedQuote', '\'\''), - hl('SingleQuotedQuote', '\'\''), - hl('SingleQuotedQuote', '\'\''), - hl('SingleQuote', '\''), - }) - check_parsing('\'\'\'a\'\'\'\'bc\'', { - -- 01234567890 - -- 0 1 - ast = { - 'SingleQuotedString(val="\'a\'\'bc"):0:0:\'\'\'a\'\'\'\'bc\'', - }, - }, { - hl('SingleQuote', '\''), - hl('SingleQuotedQuote', '\'\''), - hl('SingleQuotedBody', 'a'), - hl('SingleQuotedQuote', '\'\''), - hl('SingleQuotedQuote', '\'\''), - hl('SingleQuotedBody', 'bc'), - hl('SingleQuote', '\''), - }) - check_parsing('"\\"\\"\\"\\""', { - -- 0123456789 - ast = { - 'DoubleQuotedString(val="\\"\\"\\"\\""):0:0:"\\"\\"\\"\\""', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedEscape', '\\"'), - hl('DoubleQuotedEscape', '\\"'), - hl('DoubleQuotedEscape', '\\"'), - hl('DoubleQuotedEscape', '\\"'), - hl('DoubleQuote', '"'), - }) - check_parsing('"abc\\"def\\"ghi\\"jkl\\"mno"', { - -- 0123456789012345678901234 - -- 0 1 2 - ast = { - 'DoubleQuotedString(val="abc\\"def\\"ghi\\"jkl\\"mno"):0:0:"abc\\"def\\"ghi\\"jkl\\"mno"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedBody', 'abc'), - hl('DoubleQuotedEscape', '\\"'), - hl('DoubleQuotedBody', 'def'), - hl('DoubleQuotedEscape', '\\"'), - hl('DoubleQuotedBody', 'ghi'), - hl('DoubleQuotedEscape', '\\"'), - hl('DoubleQuotedBody', 'jkl'), - hl('DoubleQuotedEscape', '\\"'), - hl('DoubleQuotedBody', 'mno'), - hl('DoubleQuote', '"'), - }) - check_parsing('"\\b\\e\\f\\r\\t\\\\"', { - -- 0123456789012345 - -- 0 1 - ast = { - [[DoubleQuotedString(val="\8\27\12\13\9\\"):0:0:"\b\e\f\r\t\\"]], - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedEscape', '\\b'), - hl('DoubleQuotedEscape', '\\e'), - hl('DoubleQuotedEscape', '\\f'), - hl('DoubleQuotedEscape', '\\r'), - hl('DoubleQuotedEscape', '\\t'), - hl('DoubleQuotedEscape', '\\\\'), - hl('DoubleQuote', '"'), - }) - check_parsing('"\\n\n"', { - -- 01234 - ast = { - 'DoubleQuotedString(val="\\\n\\\n"):0:0:"\\n\n"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedEscape', '\\n'), - hl('DoubleQuotedBody', '\n'), - hl('DoubleQuote', '"'), - }) - check_parsing('"\\x00"', { - -- 012345 - ast = { - 'DoubleQuotedString(val="\\0"):0:0:"\\x00"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedEscape', '\\x00'), - hl('DoubleQuote', '"'), - }) - check_parsing('"\\xFF"', { - -- 012345 - ast = { - 'DoubleQuotedString(val="\255"):0:0:"\\xFF"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedEscape', '\\xFF'), - hl('DoubleQuote', '"'), - }) - check_parsing('"\\xF"', { - -- 012345 - ast = { - 'DoubleQuotedString(val="\\15"):0:0:"\\xF"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedEscape', '\\xF'), - hl('DoubleQuote', '"'), - }) - check_parsing('"\\u00AB"', { - -- 01234567 - ast = { - 'DoubleQuotedString(val="«"):0:0:"\\u00AB"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedEscape', '\\u00AB'), - hl('DoubleQuote', '"'), - }) - check_parsing('"\\U000000AB"', { - -- 01234567 - ast = { - 'DoubleQuotedString(val="«"):0:0:"\\U000000AB"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedEscape', '\\U000000AB'), - hl('DoubleQuote', '"'), - }) - check_parsing('"\\x"', { - -- 0123 - ast = { - 'DoubleQuotedString(val="x"):0:0:"\\x"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedUnknownEscape', '\\x'), - hl('DoubleQuote', '"'), - }) - - check_parsing('"\\x', { - -- 012 - ast = { - 'DoubleQuotedString(val="x"):0:0:"\\x', - }, - err = { - arg = '"\\x', - msg = 'E114: Missing double quote: %.*s', - }, - }, { - hl('InvalidDoubleQuote', '"'), - hl('InvalidDoubleQuotedUnknownEscape', '\\x'), - }) - - check_parsing('"\\xF', { - -- 0123 - ast = { - 'DoubleQuotedString(val="\\15"):0:0:"\\xF', - }, - err = { - arg = '"\\xF', - msg = 'E114: Missing double quote: %.*s', - }, - }, { - hl('InvalidDoubleQuote', '"'), - hl('InvalidDoubleQuotedEscape', '\\xF'), - }) - - check_parsing('"\\u"', { - -- 0123 - ast = { - 'DoubleQuotedString(val="u"):0:0:"\\u"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedUnknownEscape', '\\u'), - hl('DoubleQuote', '"'), - }) - - check_parsing('"\\u', { - -- 012 - ast = { - 'DoubleQuotedString(val="u"):0:0:"\\u', - }, - err = { - arg = '"\\u', - msg = 'E114: Missing double quote: %.*s', - }, - }, { - hl('InvalidDoubleQuote', '"'), - hl('InvalidDoubleQuotedUnknownEscape', '\\u'), - }) - - check_parsing('"\\U', { - -- 012 - ast = { - 'DoubleQuotedString(val="U"):0:0:"\\U', - }, - err = { - arg = '"\\U', - msg = 'E114: Missing double quote: %.*s', - }, - }, { - hl('InvalidDoubleQuote', '"'), - hl('InvalidDoubleQuotedUnknownEscape', '\\U'), - }) - - check_parsing('"\\U"', { - -- 0123 - ast = { - 'DoubleQuotedString(val="U"):0:0:"\\U"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedUnknownEscape', '\\U'), - hl('DoubleQuote', '"'), - }) - - check_parsing('"\\xFX"', { - -- 012345 - ast = { - 'DoubleQuotedString(val="\\15X"):0:0:"\\xFX"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedEscape', '\\xF'), - hl('DoubleQuotedBody', 'X'), - hl('DoubleQuote', '"'), - }) - - check_parsing('"\\XFX"', { - -- 012345 - ast = { - 'DoubleQuotedString(val="\\15X"):0:0:"\\XFX"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedEscape', '\\XF'), - hl('DoubleQuotedBody', 'X'), - hl('DoubleQuote', '"'), - }) - - check_parsing('"\\xX"', { - -- 01234 - ast = { - 'DoubleQuotedString(val="xX"):0:0:"\\xX"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedUnknownEscape', '\\x'), - hl('DoubleQuotedBody', 'X'), - hl('DoubleQuote', '"'), - }) - - check_parsing('"\\XX"', { - -- 01234 - ast = { - 'DoubleQuotedString(val="XX"):0:0:"\\XX"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedUnknownEscape', '\\X'), - hl('DoubleQuotedBody', 'X'), - hl('DoubleQuote', '"'), - }) - - check_parsing('"\\uX"', { - -- 01234 - ast = { - 'DoubleQuotedString(val="uX"):0:0:"\\uX"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedUnknownEscape', '\\u'), - hl('DoubleQuotedBody', 'X'), - hl('DoubleQuote', '"'), - }) - - check_parsing('"\\UX"', { - -- 01234 - ast = { - 'DoubleQuotedString(val="UX"):0:0:"\\UX"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedUnknownEscape', '\\U'), - hl('DoubleQuotedBody', 'X'), - hl('DoubleQuote', '"'), - }) - - check_parsing('"\\x0X"', { - -- 012345 - ast = { - 'DoubleQuotedString(val="\\0X"):0:0:"\\x0X"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedEscape', '\\x0'), - hl('DoubleQuotedBody', 'X'), - hl('DoubleQuote', '"'), - }) - - check_parsing('"\\X0X"', { - -- 012345 - ast = { - 'DoubleQuotedString(val="\\0X"):0:0:"\\X0X"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedEscape', '\\X0'), - hl('DoubleQuotedBody', 'X'), - hl('DoubleQuote', '"'), - }) - - check_parsing('"\\u0X"', { - -- 012345 - ast = { - 'DoubleQuotedString(val="\\0X"):0:0:"\\u0X"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedEscape', '\\u0'), - hl('DoubleQuotedBody', 'X'), - hl('DoubleQuote', '"'), - }) - - check_parsing('"\\U0X"', { - -- 012345 - ast = { - 'DoubleQuotedString(val="\\0X"):0:0:"\\U0X"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedEscape', '\\U0'), - hl('DoubleQuotedBody', 'X'), - hl('DoubleQuote', '"'), - }) - - check_parsing('"\\x00X"', { - -- 0123456 - ast = { - 'DoubleQuotedString(val="\\0X"):0:0:"\\x00X"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedEscape', '\\x00'), - hl('DoubleQuotedBody', 'X'), - hl('DoubleQuote', '"'), - }) - - check_parsing('"\\X00X"', { - -- 0123456 - ast = { - 'DoubleQuotedString(val="\\0X"):0:0:"\\X00X"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedEscape', '\\X00'), - hl('DoubleQuotedBody', 'X'), - hl('DoubleQuote', '"'), - }) - - check_parsing('"\\u00X"', { - -- 0123456 - ast = { - 'DoubleQuotedString(val="\\0X"):0:0:"\\u00X"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedEscape', '\\u00'), - hl('DoubleQuotedBody', 'X'), - hl('DoubleQuote', '"'), - }) - - check_parsing('"\\U00X"', { - -- 0123456 - ast = { - 'DoubleQuotedString(val="\\0X"):0:0:"\\U00X"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedEscape', '\\U00'), - hl('DoubleQuotedBody', 'X'), - hl('DoubleQuote', '"'), - }) - - check_parsing('"\\u000X"', { - -- 01234567 - ast = { - 'DoubleQuotedString(val="\\0X"):0:0:"\\u000X"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedEscape', '\\u000'), - hl('DoubleQuotedBody', 'X'), - hl('DoubleQuote', '"'), - }) - - check_parsing('"\\U000X"', { - -- 01234567 - ast = { - 'DoubleQuotedString(val="\\0X"):0:0:"\\U000X"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedEscape', '\\U000'), - hl('DoubleQuotedBody', 'X'), - hl('DoubleQuote', '"'), - }) - - check_parsing('"\\u0000X"', { - -- 012345678 - ast = { - 'DoubleQuotedString(val="\\0X"):0:0:"\\u0000X"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedEscape', '\\u0000'), - hl('DoubleQuotedBody', 'X'), - hl('DoubleQuote', '"'), - }) - - check_parsing('"\\U0000X"', { - -- 012345678 - ast = { - 'DoubleQuotedString(val="\\0X"):0:0:"\\U0000X"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedEscape', '\\U0000'), - hl('DoubleQuotedBody', 'X'), - hl('DoubleQuote', '"'), - }) - - check_parsing('"\\U00000X"', { - -- 0123456789 - ast = { - 'DoubleQuotedString(val="\\0X"):0:0:"\\U00000X"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedEscape', '\\U00000'), - hl('DoubleQuotedBody', 'X'), - hl('DoubleQuote', '"'), - }) - - check_parsing('"\\U000000X"', { - -- 01234567890 - -- 0 1 - ast = { - 'DoubleQuotedString(val="\\0X"):0:0:"\\U000000X"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedEscape', '\\U000000'), - hl('DoubleQuotedBody', 'X'), - hl('DoubleQuote', '"'), - }) - - check_parsing('"\\U0000000X"', { - -- 012345678901 - -- 0 1 - ast = { - 'DoubleQuotedString(val="\\0X"):0:0:"\\U0000000X"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedEscape', '\\U0000000'), - hl('DoubleQuotedBody', 'X'), - hl('DoubleQuote', '"'), - }) - - check_parsing('"\\U00000000X"', { - -- 0123456789012 - -- 0 1 - ast = { - 'DoubleQuotedString(val="\\0X"):0:0:"\\U00000000X"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedEscape', '\\U00000000'), - hl('DoubleQuotedBody', 'X'), - hl('DoubleQuote', '"'), - }) - - check_parsing('"\\x000X"', { - -- 01234567 - ast = { - 'DoubleQuotedString(val="\\0000X"):0:0:"\\x000X"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedEscape', '\\x00'), - hl('DoubleQuotedBody', '0X'), - hl('DoubleQuote', '"'), - }) - - check_parsing('"\\X000X"', { - -- 01234567 - ast = { - 'DoubleQuotedString(val="\\0000X"):0:0:"\\X000X"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedEscape', '\\X00'), - hl('DoubleQuotedBody', '0X'), - hl('DoubleQuote', '"'), - }) - - check_parsing('"\\u00000X"', { - -- 0123456789 - ast = { - 'DoubleQuotedString(val="\\0000X"):0:0:"\\u00000X"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedEscape', '\\u0000'), - hl('DoubleQuotedBody', '0X'), - hl('DoubleQuote', '"'), - }) - - check_parsing('"\\U000000000X"', { - -- 01234567890123 - -- 0 1 - ast = { - 'DoubleQuotedString(val="\\0000X"):0:0:"\\U000000000X"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedEscape', '\\U00000000'), - hl('DoubleQuotedBody', '0X'), - hl('DoubleQuote', '"'), - }) - - check_parsing('"\\0"', { - -- 0123 - ast = { - 'DoubleQuotedString(val="\\0"):0:0:"\\0"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedEscape', '\\0'), - hl('DoubleQuote', '"'), - }) - - check_parsing('"\\00"', { - -- 01234 - ast = { - 'DoubleQuotedString(val="\\0"):0:0:"\\00"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedEscape', '\\00'), - hl('DoubleQuote', '"'), - }) - - check_parsing('"\\000"', { - -- 012345 - ast = { - 'DoubleQuotedString(val="\\0"):0:0:"\\000"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedEscape', '\\000'), - hl('DoubleQuote', '"'), - }) - - check_parsing('"\\0000"', { - -- 0123456 - ast = { - 'DoubleQuotedString(val="\\0000"):0:0:"\\0000"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedEscape', '\\000'), - hl('DoubleQuotedBody', '0'), - hl('DoubleQuote', '"'), - }) - - check_parsing('"\\8"', { - -- 0123 - ast = { - 'DoubleQuotedString(val="8"):0:0:"\\8"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedUnknownEscape', '\\8'), - hl('DoubleQuote', '"'), - }) - - check_parsing('"\\08"', { - -- 01234 - ast = { - 'DoubleQuotedString(val="\\0008"):0:0:"\\08"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedEscape', '\\0'), - hl('DoubleQuotedBody', '8'), - hl('DoubleQuote', '"'), - }) - - check_parsing('"\\008"', { - -- 012345 - ast = { - 'DoubleQuotedString(val="\\0008"):0:0:"\\008"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedEscape', '\\00'), - hl('DoubleQuotedBody', '8'), - hl('DoubleQuote', '"'), - }) - - check_parsing('"\\0008"', { - -- 0123456 - ast = { - 'DoubleQuotedString(val="\\0008"):0:0:"\\0008"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedEscape', '\\000'), - hl('DoubleQuotedBody', '8'), - hl('DoubleQuote', '"'), - }) - - check_parsing('"\\777"', { - -- 012345 - ast = { - 'DoubleQuotedString(val="\255"):0:0:"\\777"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedEscape', '\\777'), - hl('DoubleQuote', '"'), - }) - - check_parsing('"\\050"', { - -- 012345 - ast = { - 'DoubleQuotedString(val="\40"):0:0:"\\050"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedEscape', '\\050'), - hl('DoubleQuote', '"'), - }) - - check_parsing('"\\"', { - -- 012345 - ast = { - 'DoubleQuotedString(val="\\21"):0:0:"\\"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedEscape', '\\'), - hl('DoubleQuote', '"'), - }) - - check_parsing('"\\<', { - -- 012 - ast = { - 'DoubleQuotedString(val="<"):0:0:"\\<', - }, - err = { - arg = '"\\<', - msg = 'E114: Missing double quote: %.*s', - }, - }, { - hl('InvalidDoubleQuote', '"'), - hl('InvalidDoubleQuotedUnknownEscape', '\\<'), - }) - - check_parsing('"\\<"', { - -- 0123 - ast = { - 'DoubleQuotedString(val="<"):0:0:"\\<"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedUnknownEscape', '\\<'), - hl('DoubleQuote', '"'), - }) - - check_parsing('"\\ 10 then print(digits2) end + if zdata.ast.len then + print((' len = %u,'):format(zdata.ast.len)) + end print(' ast = ' .. format_luav(zdata.ast.ast, ' ') .. ',') if zdata.ast.err then print(' err = {') @@ -414,14 +417,21 @@ eastnodelist2lua = function(pstate, eastnode, checked_nodes) return ret end -local function east2lua(pstate, east) +local function east2lua(str, pstate, east) local checked_nodes = {} + local len = tonumber(pstate.pos.col) + if pstate.pos.line == 1 then + len = tonumber(pstate.reader.lines.items[0].size) + end + if type(str) == 'string' and len == #str then + len = nil + end return { err = east.err.msg ~= nil and { msg = ffi.string(east.err.msg), - arg = ('%s'):format( - ffi.string(east.err.arg, east.err.arg_len)), + arg = ffi.string(east.err.arg, east.err.arg_len), } or nil, + len = len, ast = eastnodelist2lua(pstate, east.root, checked_nodes), } end @@ -463,7 +473,7 @@ describe('Expressions parser', function() local pstate = new_pstate({str}) local east = lib.viml_pexpr_parse(pstate, flags) - local ast = east2lua(pstate, east) + local ast = east2lua(str, pstate, east) local hls = phl2lua(pstate) if exp_ast == nil then format_check_data[flags] = {ast=ast, hl_fs=hls_to_hl_fs(hls)} @@ -509,15 +519,6 @@ describe('Expressions parser', function() format_check(str, format_check_data, opts) end end - local function check_parsing(...) - return _check_parsing({flags={0, 1, 2, 3}, funcname='check_parsing'}, ...) - end - local function check_asgn_parsing(...) - return _check_parsing({ - flags={4, 5, 6, 7}, - funcname='check_asgn_parsing', - }, ...) - end local function hl(group, str, shift) return function(next_col) if nvim_hl_defs['NVim' .. group] == nil then @@ -531,8152 +532,9 @@ describe('Expressions parser', function() str)), (col + #str) end end - itp('works with + and @a', function() - check_parsing('@a', { - ast = { - 'Register(name=a):0:0:@a', - }, - }, { - hl('Register', '@a'), - }) - check_parsing('+@a', { - ast = { - { - 'UnaryPlus:0:0:+', - children = { - 'Register(name=a):0:1:@a', - }, - }, - }, - }, { - hl('UnaryPlus', '+'), - hl('Register', '@a'), - }) - check_parsing('@a+@b', { - ast = { - { - 'BinaryPlus:0:2:+', - children = { - 'Register(name=a):0:0:@a', - 'Register(name=b):0:3:@b', - }, - }, - }, - }, { - hl('Register', '@a'), - hl('BinaryPlus', '+'), - hl('Register', '@b'), - }) - check_parsing('@a+@b+@c', { - ast = { - { - 'BinaryPlus:0:5:+', - children = { - { - 'BinaryPlus:0:2:+', - children = { - 'Register(name=a):0:0:@a', - 'Register(name=b):0:3:@b', - }, - }, - 'Register(name=c):0:6:@c', - }, - }, - }, - }, { - hl('Register', '@a'), - hl('BinaryPlus', '+'), - hl('Register', '@b'), - hl('BinaryPlus', '+'), - hl('Register', '@c'), - }) - check_parsing('+@a+@b', { - ast = { - { - 'BinaryPlus:0:3:+', - children = { - { - 'UnaryPlus:0:0:+', - children = { - 'Register(name=a):0:1:@a', - }, - }, - 'Register(name=b):0:4:@b', - }, - }, - }, - }, { - hl('UnaryPlus', '+'), - hl('Register', '@a'), - hl('BinaryPlus', '+'), - hl('Register', '@b'), - }) - check_parsing('+@a++@b', { - ast = { - { - 'BinaryPlus:0:3:+', - children = { - { - 'UnaryPlus:0:0:+', - children = { - 'Register(name=a):0:1:@a', - }, - }, - { - 'UnaryPlus:0:4:+', - children = { - 'Register(name=b):0:5:@b', - }, - }, - }, - }, - }, - }, { - hl('UnaryPlus', '+'), - hl('Register', '@a'), - hl('BinaryPlus', '+'), - hl('UnaryPlus', '+'), - hl('Register', '@b'), - }) - check_parsing('@a@b', { - ast = { - { - 'OpMissing:0:2:', - children = { - 'Register(name=a):0:0:@a', - 'Register(name=b):0:2:@b', - }, - }, - }, - err = { - arg = '@b', - msg = 'E15: Missing operator: %.*s', - }, - }, { - hl('Register', '@a'), - hl('InvalidRegister', '@b'), - }, { - [1] = { - ast = { - err = REMOVE_THIS, - ast = { - 'Register(name=a):0:0:@a' - }, - }, - hl_fs = { - [2] = REMOVE_THIS, - }, - }, - }) - check_parsing(' @a \t @b', { - ast = { - { - 'OpMissing:0:3:', - children = { - 'Register(name=a):0:0: @a', - 'Register(name=b):0:3: \t @b', - }, - }, - }, - err = { - arg = '@b', - msg = 'E15: Missing operator: %.*s', - }, - }, { - hl('Register', '@a', 1), - hl('InvalidSpacing', ' \t '), - hl('Register', '@b'), - }, { - [1] = { - ast = { - err = REMOVE_THIS, - ast = { - 'Register(name=a):0:0: @a' - }, - }, - hl_fs = { - [2] = REMOVE_THIS, - [3] = REMOVE_THIS, - }, - }, - }) - check_parsing('+', { - ast = { - 'UnaryPlus:0:0:+', - }, - err = { - arg = '', - msg = 'E15: Expected value, got EOC: %.*s', - }, - }, { - hl('UnaryPlus', '+'), - }) - check_parsing(' +', { - ast = { - 'UnaryPlus:0:0: +', - }, - err = { - arg = '', - msg = 'E15: Expected value, got EOC: %.*s', - }, - }, { - hl('UnaryPlus', '+', 1), - }) - check_parsing('@a+ ', { - ast = { - { - 'BinaryPlus:0:2:+', - children = { - 'Register(name=a):0:0:@a', - }, - }, - }, - err = { - arg = '', - msg = 'E15: Expected value, got EOC: %.*s', - }, - }, { - hl('Register', '@a'), - hl('BinaryPlus', '+'), - }) - end) - itp('works with @a, + and parenthesis', function() - check_parsing('(@a)', { - ast = { - { - 'Nested:0:0:(', - children = { - 'Register(name=a):0:1:@a', - }, - }, - }, - }, { - hl('NestingParenthesis', '('), - hl('Register', '@a'), - hl('NestingParenthesis', ')'), - }) - check_parsing('()', { - ast = { - { - 'Nested:0:0:(', - children = { - 'Missing:0:1:', - }, - }, - }, - err = { - arg = ')', - msg = 'E15: Expected value, got parenthesis: %.*s', - }, - }, { - hl('NestingParenthesis', '('), - hl('InvalidNestingParenthesis', ')'), - }) - check_parsing(')', { - ast = { - { - 'Nested:0:0:', - children = { - 'Missing:0:0:', - }, - }, - }, - err = { - arg = ')', - msg = 'E15: Expected value, got parenthesis: %.*s', - }, - }, { - hl('InvalidNestingParenthesis', ')'), - }) - check_parsing('+)', { - ast = { - { - 'Nested:0:1:', - children = { - { - 'UnaryPlus:0:0:+', - children = { - 'Missing:0:1:', - }, - }, - }, - }, - }, - err = { - arg = ')', - msg = 'E15: Expected value, got parenthesis: %.*s', - }, - }, { - hl('UnaryPlus', '+'), - hl('InvalidNestingParenthesis', ')'), - }) - check_parsing('+@a(@b)', { - ast = { - { - 'UnaryPlus:0:0:+', - children = { - { - 'Call:0:3:(', - children = { - 'Register(name=a):0:1:@a', - 'Register(name=b):0:4:@b', - }, - }, - }, - }, - }, - }, { - hl('UnaryPlus', '+'), - hl('Register', '@a'), - hl('CallingParenthesis', '('), - hl('Register', '@b'), - hl('CallingParenthesis', ')'), - }) - check_parsing('@a+@b(@c)', { - ast = { - { - 'BinaryPlus:0:2:+', - children = { - 'Register(name=a):0:0:@a', - { - 'Call:0:5:(', - children = { - 'Register(name=b):0:3:@b', - 'Register(name=c):0:6:@c', - }, - }, - }, - }, - }, - }, { - hl('Register', '@a'), - hl('BinaryPlus', '+'), - hl('Register', '@b'), - hl('CallingParenthesis', '('), - hl('Register', '@c'), - hl('CallingParenthesis', ')'), - }) - check_parsing('@a()', { - ast = { - { - 'Call:0:2:(', - children = { - 'Register(name=a):0:0:@a', - }, - }, - }, - }, { - hl('Register', '@a'), - hl('CallingParenthesis', '('), - hl('CallingParenthesis', ')'), - }) - check_parsing('@a ()', { - ast = { - { - 'OpMissing:0:2:', - children = { - 'Register(name=a):0:0:@a', - { - 'Nested:0:2: (', - children = { - 'Missing:0:4:', - }, - }, - }, - }, - }, - err = { - arg = '()', - msg = 'E15: Missing operator: %.*s', - }, - }, { - hl('Register', '@a'), - hl('InvalidSpacing', ' '), - hl('NestingParenthesis', '('), - hl('InvalidNestingParenthesis', ')'), - }, { - [1] = { - ast = { - err = REMOVE_THIS, - ast = { - 'Register(name=a):0:0:@a', - }, - }, - hl_fs = { - [2] = REMOVE_THIS, - [3] = REMOVE_THIS, - [4] = REMOVE_THIS, - }, - }, - }) - check_parsing('@a + (@b)', { - ast = { - { - 'BinaryPlus:0:2: +', - children = { - 'Register(name=a):0:0:@a', - { - 'Nested:0:4: (', - children = { - 'Register(name=b):0:6:@b', - }, - }, - }, - }, - }, - }, { - hl('Register', '@a'), - hl('BinaryPlus', '+', 1), - hl('NestingParenthesis', '(', 1), - hl('Register', '@b'), - hl('NestingParenthesis', ')'), - }) - check_parsing('@a + (+@b)', { - ast = { - { - 'BinaryPlus:0:2: +', - children = { - 'Register(name=a):0:0:@a', - { - 'Nested:0:4: (', - children = { - { - 'UnaryPlus:0:6:+', - children = { - 'Register(name=b):0:7:@b', - }, - }, - }, - }, - }, - }, - }, - }, { - hl('Register', '@a'), - hl('BinaryPlus', '+', 1), - hl('NestingParenthesis', '(', 1), - hl('UnaryPlus', '+'), - hl('Register', '@b'), - hl('NestingParenthesis', ')'), - }) - check_parsing('@a + (@b + @c)', { - ast = { - { - 'BinaryPlus:0:2: +', - children = { - 'Register(name=a):0:0:@a', - { - 'Nested:0:4: (', - children = { - { - 'BinaryPlus:0:8: +', - children = { - 'Register(name=b):0:6:@b', - 'Register(name=c):0:10: @c', - }, - }, - }, - }, - }, - }, - }, - }, { - hl('Register', '@a'), - hl('BinaryPlus', '+', 1), - hl('NestingParenthesis', '(', 1), - hl('Register', '@b'), - hl('BinaryPlus', '+', 1), - hl('Register', '@c', 1), - hl('NestingParenthesis', ')'), - }) - check_parsing('(@a)+@b', { - ast = { - { - 'BinaryPlus:0:4:+', - children = { - { - 'Nested:0:0:(', - children = { - 'Register(name=a):0:1:@a', - }, - }, - 'Register(name=b):0:5:@b', - }, - }, - }, - }, { - hl('NestingParenthesis', '('), - hl('Register', '@a'), - hl('NestingParenthesis', ')'), - hl('BinaryPlus', '+'), - hl('Register', '@b'), - }) - check_parsing('@a+(@b)(@c)', { - -- 01234567890 - ast = { - { - 'BinaryPlus:0:2:+', - children = { - 'Register(name=a):0:0:@a', - { - 'Call:0:7:(', - children = { - { - 'Nested:0:3:(', - children = { 'Register(name=b):0:4:@b' }, - }, - 'Register(name=c):0:8:@c', - }, - }, - }, - }, - }, - }, { - hl('Register', '@a'), - hl('BinaryPlus', '+'), - hl('NestingParenthesis', '('), - hl('Register', '@b'), - hl('NestingParenthesis', ')'), - hl('CallingParenthesis', '('), - hl('Register', '@c'), - hl('CallingParenthesis', ')'), - }) - check_parsing('@a+((@b))(@c)', { - -- 01234567890123456890123456789 - -- 0 1 2 - ast = { - { - 'BinaryPlus:0:2:+', - children = { - 'Register(name=a):0:0:@a', - { - 'Call:0:9:(', - children = { - { - 'Nested:0:3:(', - children = { - { - 'Nested:0:4:(', - children = { 'Register(name=b):0:5:@b' } - }, - }, - }, - 'Register(name=c):0:10:@c', - }, - }, - }, - }, - }, - }, { - hl('Register', '@a'), - hl('BinaryPlus', '+'), - hl('NestingParenthesis', '('), - hl('NestingParenthesis', '('), - hl('Register', '@b'), - hl('NestingParenthesis', ')'), - hl('NestingParenthesis', ')'), - hl('CallingParenthesis', '('), - hl('Register', '@c'), - hl('CallingParenthesis', ')'), - }) - check_parsing('@a+((@b))+@c', { - -- 01234567890123456890123456789 - -- 0 1 2 - ast = { - { - 'BinaryPlus:0:9:+', - children = { - { - 'BinaryPlus:0:2:+', - children = { - 'Register(name=a):0:0:@a', - { - 'Nested:0:3:(', - children = { - { - 'Nested:0:4:(', - children = { 'Register(name=b):0:5:@b' } - }, - }, - }, - }, - }, - 'Register(name=c):0:10:@c', - }, - }, - }, - }, { - hl('Register', '@a'), - hl('BinaryPlus', '+'), - hl('NestingParenthesis', '('), - hl('NestingParenthesis', '('), - hl('Register', '@b'), - hl('NestingParenthesis', ')'), - hl('NestingParenthesis', ')'), - hl('BinaryPlus', '+'), - hl('Register', '@c'), - }) - check_parsing( - '@a + (@b + @c) + @d(@e) + (+@f) + ((+@g(@h))(@j)(@k))(@l)', {--[[ - | | | | | | | | || | | || | | ||| || || || || - 000000000011111111112222222222333333333344444444445555555 - 012345678901234567890123456789012345678901234567890123456 - ]] - ast = {{ - 'BinaryPlus:0:31: +', - children = { - { - 'BinaryPlus:0:23: +', - children = { - { - 'BinaryPlus:0:14: +', - children = { - { - 'BinaryPlus:0:2: +', - children = { - 'Register(name=a):0:0:@a', - { - 'Nested:0:4: (', - children = { - { - 'BinaryPlus:0:8: +', - children = { - 'Register(name=b):0:6:@b', - 'Register(name=c):0:10: @c', - }, - }, - }, - }, - }, - }, - { - 'Call:0:19:(', - children = { - 'Register(name=d):0:16: @d', - 'Register(name=e):0:20:@e', - }, - }, - }, - }, - { - 'Nested:0:25: (', - children = { - { - 'UnaryPlus:0:27:+', - children = { - 'Register(name=f):0:28:@f', - }, - }, - }, - }, - }, - }, - { - 'Call:0:53:(', - children = { - { - 'Nested:0:33: (', - children = { - { - 'Call:0:48:(', - children = { - { - 'Call:0:44:(', - children = { - { - 'Nested:0:35:(', - children = { - { - 'UnaryPlus:0:36:+', - children = { - { - 'Call:0:39:(', - children = { - 'Register(name=g):0:37:@g', - 'Register(name=h):0:40:@h', - }, - }, - }, - }, - }, - }, - 'Register(name=j):0:45:@j', - }, - }, - 'Register(name=k):0:49:@k', - }, - }, - }, - }, - 'Register(name=l):0:54:@l', - }, - }, - }, - }}, - }, { - hl('Register', '@a'), - hl('BinaryPlus', '+', 1), - hl('NestingParenthesis', '(', 1), - hl('Register', '@b'), - hl('BinaryPlus', '+', 1), - hl('Register', '@c', 1), - hl('NestingParenthesis', ')'), - hl('BinaryPlus', '+', 1), - hl('Register', '@d', 1), - hl('CallingParenthesis', '('), - hl('Register', '@e'), - hl('CallingParenthesis', ')'), - hl('BinaryPlus', '+', 1), - hl('NestingParenthesis', '(', 1), - hl('UnaryPlus', '+'), - hl('Register', '@f'), - hl('NestingParenthesis', ')'), - hl('BinaryPlus', '+', 1), - hl('NestingParenthesis', '(', 1), - hl('NestingParenthesis', '('), - hl('UnaryPlus', '+'), - hl('Register', '@g'), - hl('CallingParenthesis', '('), - hl('Register', '@h'), - hl('CallingParenthesis', ')'), - hl('NestingParenthesis', ')'), - hl('CallingParenthesis', '('), - hl('Register', '@j'), - hl('CallingParenthesis', ')'), - hl('CallingParenthesis', '('), - hl('Register', '@k'), - hl('CallingParenthesis', ')'), - hl('NestingParenthesis', ')'), - hl('CallingParenthesis', '('), - hl('Register', '@l'), - hl('CallingParenthesis', ')'), - }) - check_parsing('@a)', { - -- 012 - ast = { - { - 'Nested:0:2:', - children = { - 'Register(name=a):0:0:@a', - }, - }, - }, - err = { - arg = ')', - msg = 'E15: Unexpected closing parenthesis: %.*s', - }, - }, { - hl('Register', '@a'), - hl('InvalidNestingParenthesis', ')'), - }) - check_parsing('(@a', { - -- 012 - ast = { - { - 'Nested:0:0:(', - children = { - 'Register(name=a):0:1:@a', - }, - }, - }, - err = { - arg = '(@a', - msg = 'E110: Missing closing parenthesis for nested expression: %.*s', - }, - }, { - hl('NestingParenthesis', '('), - hl('Register', '@a'), - }) - check_parsing('@a(@b', { - -- 01234 - ast = { - { - 'Call:0:2:(', - children = { - 'Register(name=a):0:0:@a', - 'Register(name=b):0:3:@b', - }, - }, - }, - err = { - arg = '(@b', - msg = 'E116: Missing closing parenthesis for function call: %.*s', - }, - }, { - hl('Register', '@a'), - hl('CallingParenthesis', '('), - hl('Register', '@b'), - }) - check_parsing('@a(@b, @c, @d, @e)', { - -- 012345678901234567 - -- 0 1 - ast = { - { - 'Call:0:2:(', - children = { - 'Register(name=a):0:0:@a', - { - 'Comma:0:5:,', - children = { - 'Register(name=b):0:3:@b', - { - 'Comma:0:9:,', - children = { - 'Register(name=c):0:6: @c', - { - 'Comma:0:13:,', - children = { - 'Register(name=d):0:10: @d', - 'Register(name=e):0:14: @e', - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, { - hl('Register', '@a'), - hl('CallingParenthesis', '('), - hl('Register', '@b'), - hl('Comma', ','), - hl('Register', '@c', 1), - hl('Comma', ','), - hl('Register', '@d', 1), - hl('Comma', ','), - hl('Register', '@e', 1), - hl('CallingParenthesis', ')'), - }) - check_parsing('@a(@b(@c))', { - -- 01234567890123456789012345678901234567 - -- 0 1 2 3 - ast = { - { - 'Call:0:2:(', - children = { - 'Register(name=a):0:0:@a', - { - 'Call:0:5:(', - children = { - 'Register(name=b):0:3:@b', - 'Register(name=c):0:6:@c', - }, - }, - }, - }, - }, - }, { - hl('Register', '@a'), - hl('CallingParenthesis', '('), - hl('Register', '@b'), - hl('CallingParenthesis', '('), - hl('Register', '@c'), - hl('CallingParenthesis', ')'), - hl('CallingParenthesis', ')'), - }) - check_parsing('@a(@b(@c(@d(@e), @f(@g(@h), @i(@j)))))', { - -- 01234567890123456789012345678901234567 - -- 0 1 2 3 - ast = { - { - 'Call:0:2:(', - children = { - 'Register(name=a):0:0:@a', - { - 'Call:0:5:(', - children = { - 'Register(name=b):0:3:@b', - { - 'Call:0:8:(', - children = { - 'Register(name=c):0:6:@c', - { - 'Comma:0:15:,', - children = { - { - 'Call:0:11:(', - children = { - 'Register(name=d):0:9:@d', - 'Register(name=e):0:12:@e', - }, - }, - { - 'Call:0:19:(', - children = { - 'Register(name=f):0:16: @f', - { - 'Comma:0:26:,', - children = { - { - 'Call:0:22:(', - children = { - 'Register(name=g):0:20:@g', - 'Register(name=h):0:23:@h', - }, - }, - { - 'Call:0:30:(', - children = { - 'Register(name=i):0:27: @i', - 'Register(name=j):0:31:@j', - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, { - hl('Register', '@a'), - hl('CallingParenthesis', '('), - hl('Register', '@b'), - hl('CallingParenthesis', '('), - hl('Register', '@c'), - hl('CallingParenthesis', '('), - hl('Register', '@d'), - hl('CallingParenthesis', '('), - hl('Register', '@e'), - hl('CallingParenthesis', ')'), - hl('Comma', ','), - hl('Register', '@f', 1), - hl('CallingParenthesis', '('), - hl('Register', '@g'), - hl('CallingParenthesis', '('), - hl('Register', '@h'), - hl('CallingParenthesis', ')'), - hl('Comma', ','), - hl('Register', '@i', 1), - hl('CallingParenthesis', '('), - hl('Register', '@j'), - hl('CallingParenthesis', ')'), - hl('CallingParenthesis', ')'), - hl('CallingParenthesis', ')'), - hl('CallingParenthesis', ')'), - hl('CallingParenthesis', ')'), - }) - check_parsing('()()', { - -- 0123 - ast = { - { - 'Call:0:2:(', - children = { - { - 'Nested:0:0:(', - children = { - 'Missing:0:1:', - }, - }, - }, - }, - }, - err = { - arg = ')()', - msg = 'E15: Expected value, got parenthesis: %.*s', - }, - }, { - hl('NestingParenthesis', '('), - hl('InvalidNestingParenthesis', ')'), - hl('CallingParenthesis', '('), - hl('CallingParenthesis', ')'), - }) - check_parsing('(@a)()', { - -- 012345 - ast = { - { - 'Call:0:4:(', - children = { - { - 'Nested:0:0:(', - children = { - 'Register(name=a):0:1:@a', - }, - }, - }, - }, - }, - }, { - hl('NestingParenthesis', '('), - hl('Register', '@a'), - hl('NestingParenthesis', ')'), - hl('CallingParenthesis', '('), - hl('CallingParenthesis', ')'), - }) - check_parsing('(@a)(@b)', { - -- 01234567 - ast = { - { - 'Call:0:4:(', - children = { - { - 'Nested:0:0:(', - children = { - 'Register(name=a):0:1:@a', - }, - }, - 'Register(name=b):0:5:@b', - }, - }, - }, - }, { - hl('NestingParenthesis', '('), - hl('Register', '@a'), - hl('NestingParenthesis', ')'), - hl('CallingParenthesis', '('), - hl('Register', '@b'), - hl('CallingParenthesis', ')'), - }) - check_parsing('(@a) (@b)', { - -- 012345678 - ast = { - { - 'OpMissing:0:4:', - children = { - { - 'Nested:0:0:(', - children = { - 'Register(name=a):0:1:@a', - }, - }, - { - 'Nested:0:4: (', - children = { - 'Register(name=b):0:6:@b', - }, - }, - }, - }, - }, - err = { - arg = '(@b)', - msg = 'E15: Missing operator: %.*s', - }, - }, { - hl('NestingParenthesis', '('), - hl('Register', '@a'), - hl('NestingParenthesis', ')'), - hl('InvalidSpacing', ' '), - hl('NestingParenthesis', '('), - hl('Register', '@b'), - hl('NestingParenthesis', ')'), - }, { - [1] = { - ast = { - ast = { - { - 'Nested:0:0:(', - children = { - 'Register(name=a):0:1:@a', - REMOVE_THIS, - }, - }, - }, - err = REMOVE_THIS, - }, - hl_fs = { - [4] = REMOVE_THIS, - [5] = REMOVE_THIS, - [6] = REMOVE_THIS, - [7] = REMOVE_THIS, - }, - }, - }) - end) - itp('works with variable names, including curly braces ones', function() - check_parsing('var', { - ast = { - 'PlainIdentifier(scope=0,ident=var):0:0:var', - }, - }, { - hl('IdentifierName', 'var'), - }) - check_parsing('g:var', { - ast = { - 'PlainIdentifier(scope=g,ident=var):0:0:g:var', - }, - }, { - hl('IdentifierScope', 'g'), - hl('IdentifierScopeDelimiter', ':'), - hl('IdentifierName', 'var'), - }) - check_parsing('g:', { - ast = { - 'PlainIdentifier(scope=g,ident=):0:0:g:', - }, - }, { - hl('IdentifierScope', 'g'), - hl('IdentifierScopeDelimiter', ':'), - }) - check_parsing('{a}', { - -- 012 - ast = { - { - 'CurlyBracesIdentifier(-di):0:0:{', - children = { - 'PlainIdentifier(scope=0,ident=a):0:1:a', - }, - }, - }, - }, { - hl('Curly', '{'), - hl('IdentifierName', 'a'), - hl('Curly', '}'), - }) - check_parsing('{a:b}', { - -- 012 - ast = { - { - 'CurlyBracesIdentifier(-di):0:0:{', - children = { - 'PlainIdentifier(scope=a,ident=b):0:1:a:b', - }, - }, - }, - }, { - hl('Curly', '{'), - hl('IdentifierScope', 'a'), - hl('IdentifierScopeDelimiter', ':'), - hl('IdentifierName', 'b'), - hl('Curly', '}'), - }) - check_parsing('{a:@b}', { - -- 012345 - ast = { - { - 'CurlyBracesIdentifier(-di):0:0:{', - children = { - { - 'OpMissing:0:3:', - children={ - 'PlainIdentifier(scope=a,ident=):0:1:a:', - 'Register(name=b):0:3:@b', - }, - }, - }, - }, - }, - err = { - arg = '@b}', - msg = 'E15: Missing operator: %.*s', - }, - }, { - hl('Curly', '{'), - hl('IdentifierScope', 'a'), - hl('IdentifierScopeDelimiter', ':'), - hl('InvalidRegister', '@b'), - hl('Curly', '}'), - }) - check_parsing('{@a}', { - ast = { - { - 'CurlyBracesIdentifier(-di):0:0:{', - children = { - 'Register(name=a):0:1:@a', - }, - }, - }, - }, { - hl('Curly', '{'), - hl('Register', '@a'), - hl('Curly', '}'), - }) - check_parsing('{@a}{@b}', { - -- 01234567 - ast = { - { - 'ComplexIdentifier:0:4:', - children = { - { - 'CurlyBracesIdentifier(-di):0:0:{', - children = { - 'Register(name=a):0:1:@a', - }, - }, - { - 'CurlyBracesIdentifier(--i):0:4:{', - children = { - 'Register(name=b):0:5:@b', - }, - }, - }, - }, - }, - }, { - hl('Curly', '{'), - hl('Register', '@a'), - hl('Curly', '}'), - hl('Curly', '{'), - hl('Register', '@b'), - hl('Curly', '}'), - }) - check_parsing('g:{@a}', { - -- 01234567 - ast = { - { - 'ComplexIdentifier:0:2:', - children = { - 'PlainIdentifier(scope=g,ident=):0:0:g:', - { - 'CurlyBracesIdentifier(--i):0:2:{', - children = { - 'Register(name=a):0:3:@a', - }, - }, - }, - }, - }, - }, { - hl('IdentifierScope', 'g'), - hl('IdentifierScopeDelimiter', ':'), - hl('Curly', '{'), - hl('Register', '@a'), - hl('Curly', '}'), - }) - check_parsing('{@a}_test', { - -- 012345678 - ast = { - { - 'ComplexIdentifier:0:4:', - children = { - { - 'CurlyBracesIdentifier(-di):0:0:{', - children = { - 'Register(name=a):0:1:@a', - }, - }, - 'PlainIdentifier(scope=0,ident=_test):0:4:_test', - }, - }, - }, - }, { - hl('Curly', '{'), - hl('Register', '@a'), - hl('Curly', '}'), - hl('IdentifierName', '_test'), - }) - check_parsing('g:{@a}_test', { - -- 01234567890 - ast = { - { - 'ComplexIdentifier:0:2:', - children = { - 'PlainIdentifier(scope=g,ident=):0:0:g:', - { - 'ComplexIdentifier:0:6:', - children = { - { - 'CurlyBracesIdentifier(--i):0:2:{', - children = { - 'Register(name=a):0:3:@a', - }, - }, - 'PlainIdentifier(scope=0,ident=_test):0:6:_test', - }, - }, - }, - }, - }, - }, { - hl('IdentifierScope', 'g'), - hl('IdentifierScopeDelimiter', ':'), - hl('Curly', '{'), - hl('Register', '@a'), - hl('Curly', '}'), - hl('IdentifierName', '_test'), - }) - check_parsing('g:{@a}_test()', { - -- 0123456789012 - ast = { - { - 'Call:0:11:(', - children = { - { - 'ComplexIdentifier:0:2:', - children = { - 'PlainIdentifier(scope=g,ident=):0:0:g:', - { - 'ComplexIdentifier:0:6:', - children = { - { - 'CurlyBracesIdentifier(--i):0:2:{', - children = { - 'Register(name=a):0:3:@a', - }, - }, - 'PlainIdentifier(scope=0,ident=_test):0:6:_test', - }, - }, - }, - }, - }, - }, - }, - }, { - hl('IdentifierScope', 'g'), - hl('IdentifierScopeDelimiter', ':'), - hl('Curly', '{'), - hl('Register', '@a'), - hl('Curly', '}'), - hl('IdentifierName', '_test'), - hl('CallingParenthesis', '('), - hl('CallingParenthesis', ')'), - }) - check_parsing('{@a} ()', { - -- 0123456789012 - ast = { - { - 'Call:0:4: (', - children = { - { - 'CurlyBracesIdentifier(-di):0:0:{', - children = { - 'Register(name=a):0:1:@a', - }, - }, - }, - }, - }, - }, { - hl('Curly', '{'), - hl('Register', '@a'), - hl('Curly', '}'), - hl('CallingParenthesis', '(', 1), - hl('CallingParenthesis', ')'), - }) - check_parsing('g:{@a} ()', { - -- 0123456789012 - ast = { - { - 'Call:0:6: (', - children = { - { - 'ComplexIdentifier:0:2:', - children = { - 'PlainIdentifier(scope=g,ident=):0:0:g:', - { - 'CurlyBracesIdentifier(--i):0:2:{', - children = { - 'Register(name=a):0:3:@a', - }, - }, - }, - }, - }, - }, - }, - }, { - hl('IdentifierScope', 'g'), - hl('IdentifierScopeDelimiter', ':'), - hl('Curly', '{'), - hl('Register', '@a'), - hl('Curly', '}'), - hl('CallingParenthesis', '(', 1), - hl('CallingParenthesis', ')'), - }) - check_parsing('{@a', { - -- 012 - ast = { - { - 'UnknownFigure(-di):0:0:{', - children = { - 'Register(name=a):0:1:@a', - }, - }, - }, - err = { - arg = '{@a', - msg = 'E15: Missing closing figure brace: %.*s', - }, - }, { - hl('FigureBrace', '{'), - hl('Register', '@a'), - }) - check_parsing('a ()', { - -- 0123 - ast = { - { - 'Call:0:1: (', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - }, - }, - }, - }, { - hl('IdentifierName', 'a'), - hl('CallingParenthesis', '(', 1), - hl('CallingParenthesis', ')'), - }) - end) - itp('works with lambdas and dictionaries', function() - check_parsing('{}', { - ast = { - 'DictLiteral(-di):0:0:{', - }, - }, { - hl('Dict', '{'), - hl('Dict', '}'), - }) - check_parsing('{->@a}', { - ast = { - { - 'Lambda(\\di):0:0:{', - children = { - { - 'Arrow:0:1:->', - children = { - 'Register(name=a):0:3:@a', - }, - }, - }, - }, - }, - }, { - hl('Lambda', '{'), - hl('Arrow', '->'), - hl('Register', '@a'), - hl('Lambda', '}'), - }) - check_parsing('{->@a+@b}', { - -- 012345678 - ast = { - { - 'Lambda(\\di):0:0:{', - children = { - { - 'Arrow:0:1:->', - children = { - { - 'BinaryPlus:0:5:+', - children = { - 'Register(name=a):0:3:@a', - 'Register(name=b):0:6:@b', - }, - }, - }, - }, - }, - }, - }, - }, { - hl('Lambda', '{'), - hl('Arrow', '->'), - hl('Register', '@a'), - hl('BinaryPlus', '+'), - hl('Register', '@b'), - hl('Lambda', '}'), - }) - check_parsing('{a->@a}', { - -- 012345678 - ast = { - { - 'Lambda(\\di):0:0:{', - children = { - 'PlainIdentifier(scope=0,ident=a):0:1:a', - { - 'Arrow:0:2:->', - children = { - 'Register(name=a):0:4:@a', - }, - }, - }, - }, - }, - }, { - hl('Lambda', '{'), - hl('IdentifierName', 'a'), - hl('Arrow', '->'), - hl('Register', '@a'), - hl('Lambda', '}'), - }) - check_parsing('{a,b->@a}', { - -- 012345678 - ast = { - { - 'Lambda(\\di):0:0:{', - children = { - { - 'Comma:0:2:,', - children = { - 'PlainIdentifier(scope=0,ident=a):0:1:a', - 'PlainIdentifier(scope=0,ident=b):0:3:b', - }, - }, - { - 'Arrow:0:4:->', - children = { - 'Register(name=a):0:6:@a', - }, - }, - }, - }, - }, - }, { - hl('Lambda', '{'), - hl('IdentifierName', 'a'), - hl('Comma', ','), - hl('IdentifierName', 'b'), - hl('Arrow', '->'), - hl('Register', '@a'), - hl('Lambda', '}'), - }) - check_parsing('{a,b,c->@a}', { - -- 01234567890 - ast = { - { - 'Lambda(\\di):0:0:{', - children = { - { - 'Comma:0:2:,', - children = { - 'PlainIdentifier(scope=0,ident=a):0:1:a', - { - 'Comma:0:4:,', - children = { - 'PlainIdentifier(scope=0,ident=b):0:3:b', - 'PlainIdentifier(scope=0,ident=c):0:5:c', - }, - }, - }, - }, - { - 'Arrow:0:6:->', - children = { - 'Register(name=a):0:8:@a', - }, - }, - }, - }, - }, - }, { - hl('Lambda', '{'), - hl('IdentifierName', 'a'), - hl('Comma', ','), - hl('IdentifierName', 'b'), - hl('Comma', ','), - hl('IdentifierName', 'c'), - hl('Arrow', '->'), - hl('Register', '@a'), - hl('Lambda', '}'), - }) - check_parsing('{a,b,c,d->@a}', { - -- 0123456789012 - ast = { - { - 'Lambda(\\di):0:0:{', - children = { - { - 'Comma:0:2:,', - children = { - 'PlainIdentifier(scope=0,ident=a):0:1:a', - { - 'Comma:0:4:,', - children = { - 'PlainIdentifier(scope=0,ident=b):0:3:b', - { - 'Comma:0:6:,', - children = { - 'PlainIdentifier(scope=0,ident=c):0:5:c', - 'PlainIdentifier(scope=0,ident=d):0:7:d', - }, - }, - }, - }, - }, - }, - { - 'Arrow:0:8:->', - children = { - 'Register(name=a):0:10:@a', - }, - }, - }, - }, - }, - }, { - hl('Lambda', '{'), - hl('IdentifierName', 'a'), - hl('Comma', ','), - hl('IdentifierName', 'b'), - hl('Comma', ','), - hl('IdentifierName', 'c'), - hl('Comma', ','), - hl('IdentifierName', 'd'), - hl('Arrow', '->'), - hl('Register', '@a'), - hl('Lambda', '}'), - }) - check_parsing('{a,b,c,d,->@a}', { - -- 01234567890123 - ast = { - { - 'Lambda(\\di):0:0:{', - children = { - { - 'Comma:0:2:,', - children = { - 'PlainIdentifier(scope=0,ident=a):0:1:a', - { - 'Comma:0:4:,', - children = { - 'PlainIdentifier(scope=0,ident=b):0:3:b', - { - 'Comma:0:6:,', - children = { - 'PlainIdentifier(scope=0,ident=c):0:5:c', - { - 'Comma:0:8:,', - children = { - 'PlainIdentifier(scope=0,ident=d):0:7:d', - }, - }, - }, - }, - }, - }, - }, - }, - { - 'Arrow:0:9:->', - children = { - 'Register(name=a):0:11:@a', - }, - }, - }, - }, - }, - }, { - hl('Lambda', '{'), - hl('IdentifierName', 'a'), - hl('Comma', ','), - hl('IdentifierName', 'b'), - hl('Comma', ','), - hl('IdentifierName', 'c'), - hl('Comma', ','), - hl('IdentifierName', 'd'), - hl('Comma', ','), - hl('Arrow', '->'), - hl('Register', '@a'), - hl('Lambda', '}'), - }) - check_parsing('{a,b->{c,d->{e,f->@a}}}', { - -- 01234567890123456789012 - -- 0 1 2 - ast = { - { - 'Lambda(\\di):0:0:{', - children = { - { - 'Comma:0:2:,', - children = { - 'PlainIdentifier(scope=0,ident=a):0:1:a', - 'PlainIdentifier(scope=0,ident=b):0:3:b', - }, - }, - { - 'Arrow:0:4:->', - children = { - { - 'Lambda(\\di):0:6:{', - children = { - { - 'Comma:0:8:,', - children = { - 'PlainIdentifier(scope=0,ident=c):0:7:c', - 'PlainIdentifier(scope=0,ident=d):0:9:d', - }, - }, - { - 'Arrow:0:10:->', - children = { - { - 'Lambda(\\di):0:12:{', - children = { - { - 'Comma:0:14:,', - children = { - 'PlainIdentifier(scope=0,ident=e):0:13:e', - 'PlainIdentifier(scope=0,ident=f):0:15:f', - }, - }, - { - 'Arrow:0:16:->', - children = { - 'Register(name=a):0:18:@a', - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, { - hl('Lambda', '{'), - hl('IdentifierName', 'a'), - hl('Comma', ','), - hl('IdentifierName', 'b'), - hl('Arrow', '->'), - hl('Lambda', '{'), - hl('IdentifierName', 'c'), - hl('Comma', ','), - hl('IdentifierName', 'd'), - hl('Arrow', '->'), - hl('Lambda', '{'), - hl('IdentifierName', 'e'), - hl('Comma', ','), - hl('IdentifierName', 'f'), - hl('Arrow', '->'), - hl('Register', '@a'), - hl('Lambda', '}'), - hl('Lambda', '}'), - hl('Lambda', '}'), - }) - check_parsing('{a,b->c,d}', { - -- 0123456789 - ast = { - { - 'Lambda(\\di):0:0:{', - children = { - { - 'Comma:0:2:,', - children = { - 'PlainIdentifier(scope=0,ident=a):0:1:a', - 'PlainIdentifier(scope=0,ident=b):0:3:b', - }, - }, - { - 'Arrow:0:4:->', - children = { - { - 'Comma:0:7:,', - children = { - 'PlainIdentifier(scope=0,ident=c):0:6:c', - 'PlainIdentifier(scope=0,ident=d):0:8:d', - }, - }, - }, - }, - }, - }, - }, - err = { - arg = ',d}', - msg = 'E15: Comma outside of call, lambda or literal: %.*s', - }, - }, { - hl('Lambda', '{'), - hl('IdentifierName', 'a'), - hl('Comma', ','), - hl('IdentifierName', 'b'), - hl('Arrow', '->'), - hl('IdentifierName', 'c'), - hl('InvalidComma', ','), - hl('IdentifierName', 'd'), - hl('Lambda', '}'), - }) - check_parsing('a,b,c,d', { - -- 0123456789 - ast = { - { - 'Comma:0:1:,', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - { - 'Comma:0:3:,', - children = { - 'PlainIdentifier(scope=0,ident=b):0:2:b', - { - 'Comma:0:5:,', - children = { - 'PlainIdentifier(scope=0,ident=c):0:4:c', - 'PlainIdentifier(scope=0,ident=d):0:6:d', - }, - }, - }, - }, - }, - }, - }, - err = { - arg = ',b,c,d', - msg = 'E15: Comma outside of call, lambda or literal: %.*s', - }, - }, { - hl('IdentifierName', 'a'), - hl('InvalidComma', ','), - hl('IdentifierName', 'b'), - hl('InvalidComma', ','), - hl('IdentifierName', 'c'), - hl('InvalidComma', ','), - hl('IdentifierName', 'd'), - }) - check_parsing('a,b,c,d,', { - -- 0123456789 - ast = { - { - 'Comma:0:1:,', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - { - 'Comma:0:3:,', - children = { - 'PlainIdentifier(scope=0,ident=b):0:2:b', - { - 'Comma:0:5:,', - children = { - 'PlainIdentifier(scope=0,ident=c):0:4:c', - { - 'Comma:0:7:,', - children = { - 'PlainIdentifier(scope=0,ident=d):0:6:d', - }, - }, - }, - }, - }, - }, - }, - }, - }, - err = { - arg = ',b,c,d,', - msg = 'E15: Comma outside of call, lambda or literal: %.*s', - }, - }, { - hl('IdentifierName', 'a'), - hl('InvalidComma', ','), - hl('IdentifierName', 'b'), - hl('InvalidComma', ','), - hl('IdentifierName', 'c'), - hl('InvalidComma', ','), - hl('IdentifierName', 'd'), - hl('InvalidComma', ','), - }) - check_parsing(',', { - -- 0123456789 - ast = { - { - 'Comma:0:0:,', - children = { - 'Missing:0:0:', - }, - }, - }, - err = { - arg = ',', - msg = 'E15: Expected value, got comma: %.*s', - }, - }, { - hl('InvalidComma', ','), - }) - check_parsing('{,a->@a}', { - -- 0123456789 - ast = { - { - 'CurlyBracesIdentifier(-di):0:0:{', - children = { - { - 'Arrow:0:3:->', - children = { - { - 'Comma:0:1:,', - children = { - 'Missing:0:1:', - 'PlainIdentifier(scope=0,ident=a):0:2:a', - }, - }, - 'Register(name=a):0:5:@a', - }, - }, - }, - }, - }, - err = { - arg = ',a->@a}', - msg = 'E15: Expected value, got comma: %.*s', - }, - }, { - hl('Curly', '{'), - hl('InvalidComma', ','), - hl('IdentifierName', 'a'), - hl('InvalidArrow', '->'), - hl('Register', '@a'), - hl('Curly', '}'), - }) - check_parsing('}', { - -- 0123456789 - ast = { - 'UnknownFigure(---):0:0:', - }, - err = { - arg = '}', - msg = 'E15: Unexpected closing figure brace: %.*s', - }, - }, { - hl('InvalidFigureBrace', '}'), - }) - check_parsing('{->}', { - -- 0123456789 - ast = { - { - 'Lambda(\\di):0:0:{', - children = { - 'Arrow:0:1:->', - }, - }, - }, - err = { - arg = '}', - msg = 'E15: Expected value, got closing figure brace: %.*s', - }, - }, { - hl('Lambda', '{'), - hl('Arrow', '->'), - hl('InvalidLambda', '}'), - }) - check_parsing('{a,b}', { - -- 0123456789 - ast = { - { - 'Lambda(-di):0:0:{', - children = { - { - 'Comma:0:2:,', - children = { - 'PlainIdentifier(scope=0,ident=a):0:1:a', - 'PlainIdentifier(scope=0,ident=b):0:3:b', - }, - }, - }, - }, - }, - err = { - arg = '}', - msg = 'E15: Expected lambda arguments list or arrow: %.*s', - }, - }, { - hl('Lambda', '{'), - hl('IdentifierName', 'a'), - hl('Comma', ','), - hl('IdentifierName', 'b'), - hl('InvalidLambda', '}'), - }) - check_parsing('{a,}', { - -- 0123456789 - ast = { - { - 'Lambda(-di):0:0:{', - children = { - { - 'Comma:0:2:,', - children = { - 'PlainIdentifier(scope=0,ident=a):0:1:a', - }, - }, - }, - }, - }, - err = { - arg = '}', - msg = 'E15: Expected lambda arguments list or arrow: %.*s', - }, - }, { - hl('Lambda', '{'), - hl('IdentifierName', 'a'), - hl('Comma', ','), - hl('InvalidLambda', '}'), - }) - check_parsing('{@a:@b}', { - -- 0123456789 - ast = { - { - 'DictLiteral(-di):0:0:{', - children = { - { - 'Colon:0:3::', - children = { - 'Register(name=a):0:1:@a', - 'Register(name=b):0:4:@b', - }, - }, - }, - }, - }, - }, { - hl('Dict', '{'), - hl('Register', '@a'), - hl('Colon', ':'), - hl('Register', '@b'), - hl('Dict', '}'), - }) - check_parsing('{@a:@b,@c:@d}', { - -- 0123456789012 - -- 0 1 - ast = { - { - 'DictLiteral(-di):0:0:{', - children = { - { - 'Comma:0:6:,', - children = { - { - 'Colon:0:3::', - children = { - 'Register(name=a):0:1:@a', - 'Register(name=b):0:4:@b', - }, - }, - { - 'Colon:0:9::', - children = { - 'Register(name=c):0:7:@c', - 'Register(name=d):0:10:@d', - }, - }, - }, - }, - }, - }, - }, - }, { - hl('Dict', '{'), - hl('Register', '@a'), - hl('Colon', ':'), - hl('Register', '@b'), - hl('Comma', ','), - hl('Register', '@c'), - hl('Colon', ':'), - hl('Register', '@d'), - hl('Dict', '}'), - }) - check_parsing('{@a:@b,@c:@d,@e:@f,}', { - -- 01234567890123456789 - -- 0 1 - ast = { - { - 'DictLiteral(-di):0:0:{', - children = { - { - 'Comma:0:6:,', - children = { - { - 'Colon:0:3::', - children = { - 'Register(name=a):0:1:@a', - 'Register(name=b):0:4:@b', - }, - }, - { - 'Comma:0:12:,', - children = { - { - 'Colon:0:9::', - children = { - 'Register(name=c):0:7:@c', - 'Register(name=d):0:10:@d', - }, - }, - { - 'Comma:0:18:,', - children = { - { - 'Colon:0:15::', - children = { - 'Register(name=e):0:13:@e', - 'Register(name=f):0:16:@f', - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, { - hl('Dict', '{'), - hl('Register', '@a'), - hl('Colon', ':'), - hl('Register', '@b'), - hl('Comma', ','), - hl('Register', '@c'), - hl('Colon', ':'), - hl('Register', '@d'), - hl('Comma', ','), - hl('Register', '@e'), - hl('Colon', ':'), - hl('Register', '@f'), - hl('Comma', ','), - hl('Dict', '}'), - }) - check_parsing('{@a:@b,@c:@d,@e:@f,@g:}', { - -- 01234567890123456789012 - -- 0 1 2 - ast = { - { - 'DictLiteral(-di):0:0:{', - children = { - { - 'Comma:0:6:,', - children = { - { - 'Colon:0:3::', - children = { - 'Register(name=a):0:1:@a', - 'Register(name=b):0:4:@b', - }, - }, - { - 'Comma:0:12:,', - children = { - { - 'Colon:0:9::', - children = { - 'Register(name=c):0:7:@c', - 'Register(name=d):0:10:@d', - }, - }, - { - 'Comma:0:18:,', - children = { - { - 'Colon:0:15::', - children = { - 'Register(name=e):0:13:@e', - 'Register(name=f):0:16:@f', - }, - }, - { - 'Colon:0:21::', - children = { - 'Register(name=g):0:19:@g', - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - err = { - arg = '}', - msg = 'E15: Expected value, got closing figure brace: %.*s', - }, - }, { - hl('Dict', '{'), - hl('Register', '@a'), - hl('Colon', ':'), - hl('Register', '@b'), - hl('Comma', ','), - hl('Register', '@c'), - hl('Colon', ':'), - hl('Register', '@d'), - hl('Comma', ','), - hl('Register', '@e'), - hl('Colon', ':'), - hl('Register', '@f'), - hl('Comma', ','), - hl('Register', '@g'), - hl('Colon', ':'), - hl('InvalidDict', '}'), - }) - check_parsing('{@a:@b,}', { - -- 01234567890123 - -- 0 1 - ast = { - { - 'DictLiteral(-di):0:0:{', - children = { - { - 'Comma:0:6:,', - children = { - { - 'Colon:0:3::', - children = { - 'Register(name=a):0:1:@a', - 'Register(name=b):0:4:@b', - }, - }, - }, - }, - }, - }, - }, - }, { - hl('Dict', '{'), - hl('Register', '@a'), - hl('Colon', ':'), - hl('Register', '@b'), - hl('Comma', ','), - hl('Dict', '}'), - }) - check_parsing('{({f -> g})(@h)(@i)}', { - -- 01234567890123456789 - -- 0 1 - ast = { - { - 'CurlyBracesIdentifier(-di):0:0:{', - children = { - { - 'Call:0:15:(', - children = { - { - 'Call:0:11:(', - children = { - { - 'Nested:0:1:(', - children = { - { - 'Lambda(\\di):0:2:{', - children = { - 'PlainIdentifier(scope=0,ident=f):0:3:f', - { - 'Arrow:0:4: ->', - children = { - 'PlainIdentifier(scope=0,ident=g):0:7: g', - }, - }, - }, - }, - }, - }, - 'Register(name=h):0:12:@h', - }, - }, - 'Register(name=i):0:16:@i', - }, - }, - }, - }, - }, - }, { - hl('Curly', '{'), - hl('NestingParenthesis', '('), - hl('Lambda', '{'), - hl('IdentifierName', 'f'), - hl('Arrow', '->', 1), - hl('IdentifierName', 'g', 1), - hl('Lambda', '}'), - hl('NestingParenthesis', ')'), - hl('CallingParenthesis', '('), - hl('Register', '@h'), - hl('CallingParenthesis', ')'), - hl('CallingParenthesis', '('), - hl('Register', '@i'), - hl('CallingParenthesis', ')'), - hl('Curly', '}'), - }) - check_parsing('a:{b()}c', { - -- 01234567 - ast = { - { - 'ComplexIdentifier:0:2:', - children = { - 'PlainIdentifier(scope=a,ident=):0:0:a:', - { - 'ComplexIdentifier:0:7:', - children = { - { - 'CurlyBracesIdentifier(--i):0:2:{', - children = { - { - 'Call:0:4:(', - children = { - 'PlainIdentifier(scope=0,ident=b):0:3:b', - }, - }, - }, - }, - 'PlainIdentifier(scope=0,ident=c):0:7:c', - }, - }, - }, - }, - }, - }, { - hl('IdentifierScope', 'a'), - hl('IdentifierScopeDelimiter', ':'), - hl('Curly', '{'), - hl('IdentifierName', 'b'), - hl('CallingParenthesis', '('), - hl('CallingParenthesis', ')'), - hl('Curly', '}'), - hl('IdentifierName', 'c'), - }) - check_parsing('a:{{b, c -> @d + @e + ({f -> g})(@h)}(@i)}j', { - -- 01234567890123456789012345678901234567890123456 - -- 0 1 2 3 4 - ast = { - { - 'ComplexIdentifier:0:2:', - children = { - 'PlainIdentifier(scope=a,ident=):0:0:a:', - { - 'ComplexIdentifier:0:42:', - children = { - { - 'CurlyBracesIdentifier(--i):0:2:{', - children = { - { - 'Call:0:37:(', - children = { - { - 'Lambda(\\di):0:3:{', - children = { - { - 'Comma:0:5:,', - children = { - 'PlainIdentifier(scope=0,ident=b):0:4:b', - 'PlainIdentifier(scope=0,ident=c):0:6: c', - }, - }, - { - 'Arrow:0:8: ->', - children = { - { - 'BinaryPlus:0:19: +', - children = { - { - 'BinaryPlus:0:14: +', - children = { - 'Register(name=d):0:11: @d', - 'Register(name=e):0:16: @e', - }, - }, - { - 'Call:0:32:(', - children = { - { - 'Nested:0:21: (', - children = { - { - 'Lambda(\\di):0:23:{', - children = { - 'PlainIdentifier(scope=0,ident=f):0:24:f', - { - 'Arrow:0:25: ->', - children = { - 'PlainIdentifier(scope=0,ident=g):0:28: g', - }, - }, - }, - }, - }, - }, - 'Register(name=h):0:33:@h', - }, - }, - }, - }, - }, - }, - }, - }, - 'Register(name=i):0:38:@i', - }, - }, - }, - }, - 'PlainIdentifier(scope=0,ident=j):0:42:j', - }, - }, - }, - }, - }, - }, { - hl('IdentifierScope', 'a'), - hl('IdentifierScopeDelimiter', ':'), - hl('Curly', '{'), - hl('Lambda', '{'), - hl('IdentifierName', 'b'), - hl('Comma', ','), - hl('IdentifierName', 'c', 1), - hl('Arrow', '->', 1), - hl('Register', '@d', 1), - hl('BinaryPlus', '+', 1), - hl('Register', '@e', 1), - hl('BinaryPlus', '+', 1), - hl('NestingParenthesis', '(', 1), - hl('Lambda', '{'), - hl('IdentifierName', 'f'), - hl('Arrow', '->', 1), - hl('IdentifierName', 'g', 1), - hl('Lambda', '}'), - hl('NestingParenthesis', ')'), - hl('CallingParenthesis', '('), - hl('Register', '@h'), - hl('CallingParenthesis', ')'), - hl('Lambda', '}'), - hl('CallingParenthesis', '('), - hl('Register', '@i'), - hl('CallingParenthesis', ')'), - hl('Curly', '}'), - hl('IdentifierName', 'j'), - }) - check_parsing('{@a + @b : @c + @d, @e + @f : @g + @i}', { - -- 01234567890123456789012345678901234567 - -- 0 1 2 3 - ast = { - { - 'DictLiteral(-di):0:0:{', - children = { - { - 'Comma:0:18:,', - children = { - { - 'Colon:0:8: :', - children = { - { - 'BinaryPlus:0:3: +', - children = { - 'Register(name=a):0:1:@a', - 'Register(name=b):0:5: @b', - }, - }, - { - 'BinaryPlus:0:13: +', - children = { - 'Register(name=c):0:10: @c', - 'Register(name=d):0:15: @d', - }, - }, - }, - }, - { - 'Colon:0:27: :', - children = { - { - 'BinaryPlus:0:22: +', - children = { - 'Register(name=e):0:19: @e', - 'Register(name=f):0:24: @f', - }, - }, - { - 'BinaryPlus:0:32: +', - children = { - 'Register(name=g):0:29: @g', - 'Register(name=i):0:34: @i', - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, { - hl('Dict', '{'), - hl('Register', '@a'), - hl('BinaryPlus', '+', 1), - hl('Register', '@b', 1), - hl('Colon', ':', 1), - hl('Register', '@c', 1), - hl('BinaryPlus', '+', 1), - hl('Register', '@d', 1), - hl('Comma', ','), - hl('Register', '@e', 1), - hl('BinaryPlus', '+', 1), - hl('Register', '@f', 1), - hl('Colon', ':', 1), - hl('Register', '@g', 1), - hl('BinaryPlus', '+', 1), - hl('Register', '@i', 1), - hl('Dict', '}'), - }) - check_parsing('-> -> ->', { - -- 01234567 - ast = { - { - 'Arrow:0:0:->', - children = { - 'Missing:0:0:', - { - 'Arrow:0:2: ->', - children = { - 'Missing:0:2:', - { - 'Arrow:0:5: ->', - children = { - 'Missing:0:5:', - }, - }, - }, - }, - }, - }, - }, - err = { - arg = '-> -> ->', - msg = 'E15: Unexpected arrow: %.*s', - }, - }, { - hl('InvalidArrow', '->'), - hl('InvalidArrow', '->', 1), - hl('InvalidArrow', '->', 1), - }) - check_parsing('a -> b -> c -> d', { - -- 0123456789012345 - -- 0 1 - ast = { - { - 'Arrow:0:1: ->', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - { - 'Arrow:0:6: ->', - children = { - 'PlainIdentifier(scope=0,ident=b):0:4: b', - { - 'Arrow:0:11: ->', - children = { - 'PlainIdentifier(scope=0,ident=c):0:9: c', - 'PlainIdentifier(scope=0,ident=d):0:14: d', - }, - }, - }, - }, - }, - }, - }, - err = { - arg = '-> b -> c -> d', - msg = 'E15: Arrow outside of lambda: %.*s', - }, - }, { - hl('IdentifierName', 'a'), - hl('InvalidArrow', '->', 1), - hl('IdentifierName', 'b', 1), - hl('InvalidArrow', '->', 1), - hl('IdentifierName', 'c', 1), - hl('InvalidArrow', '->', 1), - hl('IdentifierName', 'd', 1), - }) - check_parsing('{a -> b -> c}', { - -- 0123456789012 - -- 0 1 - ast = { - { - 'Lambda(\\di):0:0:{', - children = { - 'PlainIdentifier(scope=0,ident=a):0:1:a', - { - 'Arrow:0:2: ->', - children = { - { - 'Arrow:0:7: ->', - children = { - 'PlainIdentifier(scope=0,ident=b):0:5: b', - 'PlainIdentifier(scope=0,ident=c):0:10: c', - }, - }, - }, - }, - }, - }, - }, - err = { - arg = '-> c}', - msg = 'E15: Arrow outside of lambda: %.*s', - }, - }, { - hl('Lambda', '{'), - hl('IdentifierName', 'a'), - hl('Arrow', '->', 1), - hl('IdentifierName', 'b', 1), - hl('InvalidArrow', '->', 1), - hl('IdentifierName', 'c', 1), - hl('Lambda', '}'), - }) - check_parsing('{a: -> b}', { - -- 012345678 - ast = { - { - 'CurlyBracesIdentifier(-di):0:0:{', - children = { - { - 'Arrow:0:3: ->', - children = { - 'PlainIdentifier(scope=a,ident=):0:1:a:', - 'PlainIdentifier(scope=0,ident=b):0:6: b', - }, - }, - }, - }, - }, - err = { - arg = '-> b}', - msg = 'E15: Arrow outside of lambda: %.*s', - }, - }, { - hl('Curly', '{'), - hl('IdentifierScope', 'a'), - hl('IdentifierScopeDelimiter', ':'), - hl('InvalidArrow', '->', 1), - hl('IdentifierName', 'b', 1), - hl('Curly', '}'), - }) - - check_parsing('{a:b -> b}', { - -- 0123456789 - ast = { - { - 'CurlyBracesIdentifier(-di):0:0:{', - children = { - { - 'Arrow:0:4: ->', - children = { - 'PlainIdentifier(scope=a,ident=b):0:1:a:b', - 'PlainIdentifier(scope=0,ident=b):0:7: b', - }, - }, - }, - }, - }, - err = { - arg = '-> b}', - msg = 'E15: Arrow outside of lambda: %.*s', - }, - }, { - hl('Curly', '{'), - hl('IdentifierScope', 'a'), - hl('IdentifierScopeDelimiter', ':'), - hl('IdentifierName', 'b'), - hl('InvalidArrow', '->', 1), - hl('IdentifierName', 'b', 1), - hl('Curly', '}'), - }) - - check_parsing('{a#b -> b}', { - -- 0123456789 - ast = { - { - 'CurlyBracesIdentifier(-di):0:0:{', - children = { - { - 'Arrow:0:4: ->', - children = { - 'PlainIdentifier(scope=0,ident=a#b):0:1:a#b', - 'PlainIdentifier(scope=0,ident=b):0:7: b', - }, - }, - }, - }, - }, - err = { - arg = '-> b}', - msg = 'E15: Arrow outside of lambda: %.*s', - }, - }, { - hl('Curly', '{'), - hl('IdentifierName', 'a#b'), - hl('InvalidArrow', '->', 1), - hl('IdentifierName', 'b', 1), - hl('Curly', '}'), - }) - check_parsing('{a : b : c}', { - -- 01234567890 - -- 0 1 - ast = { - { - 'DictLiteral(-di):0:0:{', - children = { - { - 'Colon:0:2: :', - children = { - 'PlainIdentifier(scope=0,ident=a):0:1:a', - { - 'Colon:0:6: :', - children = { - 'PlainIdentifier(scope=0,ident=b):0:4: b', - 'PlainIdentifier(scope=0,ident=c):0:8: c', - }, - }, - }, - }, - }, - }, - }, - err = { - arg = ': c}', - msg = 'E15: Colon outside of dictionary or ternary operator: %.*s', - }, - }, { - hl('Dict', '{'), - hl('IdentifierName', 'a'), - hl('Colon', ':', 1), - hl('IdentifierName', 'b', 1), - hl('InvalidColon', ':', 1), - hl('IdentifierName', 'c', 1), - hl('Dict', '}'), - }) - check_parsing('{', { - -- 0 - ast = { - 'UnknownFigure(\\di):0:0:{', - }, - err = { - arg = '{', - msg = 'E15: Missing closing figure brace: %.*s', - }, - }, { - hl('FigureBrace', '{'), - }) - check_parsing('{a', { - -- 01 - ast = { - { - 'UnknownFigure(\\di):0:0:{', - children = { - 'PlainIdentifier(scope=0,ident=a):0:1:a', - }, - }, - }, - err = { - arg = '{a', - msg = 'E15: Missing closing figure brace: %.*s', - }, - }, { - hl('FigureBrace', '{'), - hl('IdentifierName', 'a'), - }) - check_parsing('{a,b', { - -- 0123 - ast = { - { - 'Lambda(\\di):0:0:{', - children = { - { - 'Comma:0:2:,', - children = { - 'PlainIdentifier(scope=0,ident=a):0:1:a', - 'PlainIdentifier(scope=0,ident=b):0:3:b', - }, - }, - }, - }, - }, - err = { - arg = '{a,b', - msg = 'E15: Missing closing figure brace for lambda: %.*s', - }, - }, { - hl('Lambda', '{'), - hl('IdentifierName', 'a'), - hl('Comma', ','), - hl('IdentifierName', 'b'), - }) - check_parsing('{a,b->', { - -- 012345 - ast = { - { - 'Lambda(\\di):0:0:{', - children = { - { - 'Comma:0:2:,', - children = { - 'PlainIdentifier(scope=0,ident=a):0:1:a', - 'PlainIdentifier(scope=0,ident=b):0:3:b', - }, - }, - 'Arrow:0:4:->', - }, - }, - }, - err = { - arg = '', - msg = 'E15: Expected value, got EOC: %.*s', - }, - }, { - hl('Lambda', '{'), - hl('IdentifierName', 'a'), - hl('Comma', ','), - hl('IdentifierName', 'b'), - hl('Arrow', '->'), - }) - check_parsing('{a,b->c', { - -- 0123456 - ast = { - { - 'Lambda(\\di):0:0:{', - children = { - { - 'Comma:0:2:,', - children = { - 'PlainIdentifier(scope=0,ident=a):0:1:a', - 'PlainIdentifier(scope=0,ident=b):0:3:b', - }, - }, - { - 'Arrow:0:4:->', - children = { - 'PlainIdentifier(scope=0,ident=c):0:6:c', - }, - }, - }, - }, - }, - err = { - arg = '{a,b->c', - msg = 'E15: Missing closing figure brace for lambda: %.*s', - }, - }, { - hl('Lambda', '{'), - hl('IdentifierName', 'a'), - hl('Comma', ','), - hl('IdentifierName', 'b'), - hl('Arrow', '->'), - hl('IdentifierName', 'c'), - }) - check_parsing('{a : b', { - -- 012345 - ast = { - { - 'DictLiteral(-di):0:0:{', - children = { - { - 'Colon:0:2: :', - children = { - 'PlainIdentifier(scope=0,ident=a):0:1:a', - 'PlainIdentifier(scope=0,ident=b):0:4: b', - }, - }, - }, - }, - }, - err = { - arg = '{a : b', - msg = 'E723: Missing end of Dictionary \'}\': %.*s', - }, - }, { - hl('Dict', '{'), - hl('IdentifierName', 'a'), - hl('Colon', ':', 1), - hl('IdentifierName', 'b', 1), - }) - check_parsing('{a : b,', { - -- 0123456 - ast = { - { - 'DictLiteral(-di):0:0:{', - children = { - { - 'Comma:0:6:,', - children = { - { - 'Colon:0:2: :', - children = { - 'PlainIdentifier(scope=0,ident=a):0:1:a', - 'PlainIdentifier(scope=0,ident=b):0:4: b', - }, - }, - }, - }, - }, - }, - }, - err = { - arg = '', - msg = 'E15: Expected value, got EOC: %.*s', - }, - }, { - hl('Dict', '{'), - hl('IdentifierName', 'a'), - hl('Colon', ':', 1), - hl('IdentifierName', 'b', 1), - hl('Comma', ','), - }) - end) - itp('works with ternary operator', function() - check_parsing('a ? b : c', { - -- 012345678 - ast = { - { - 'Ternary:0:1: ?', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - { - 'TernaryValue:0:5: :', - children = { - 'PlainIdentifier(scope=0,ident=b):0:3: b', - 'PlainIdentifier(scope=0,ident=c):0:7: c', - }, - }, - }, - }, - }, - }, { - hl('IdentifierName', 'a'), - hl('Ternary', '?', 1), - hl('IdentifierName', 'b', 1), - hl('TernaryColon', ':', 1), - hl('IdentifierName', 'c', 1), - }) - check_parsing('@a?@b?@c:@d:@e', { - -- 01234567890123 - -- 0 1 - ast = { - { - 'Ternary:0:2:?', - children = { - 'Register(name=a):0:0:@a', - { - 'TernaryValue:0:11::', - children = { - { - 'Ternary:0:5:?', - children = { - 'Register(name=b):0:3:@b', - { - 'TernaryValue:0:8::', - children = { - 'Register(name=c):0:6:@c', - 'Register(name=d):0:9:@d', - }, - }, - }, - }, - 'Register(name=e):0:12:@e', - }, - }, - }, - }, - }, - }, { - hl('Register', '@a'), - hl('Ternary', '?'), - hl('Register', '@b'), - hl('Ternary', '?'), - hl('Register', '@c'), - hl('TernaryColon', ':'), - hl('Register', '@d'), - hl('TernaryColon', ':'), - hl('Register', '@e'), - }) - check_parsing('@a?@b:@c?@d:@e', { - -- 01234567890123 - -- 0 1 - ast = { - { - 'Ternary:0:2:?', - children = { - 'Register(name=a):0:0:@a', - { - 'TernaryValue:0:5::', - children = { - 'Register(name=b):0:3:@b', - { - 'Ternary:0:8:?', - children = { - 'Register(name=c):0:6:@c', - { - 'TernaryValue:0:11::', - children = { - 'Register(name=d):0:9:@d', - 'Register(name=e):0:12:@e', - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, { - hl('Register', '@a'), - hl('Ternary', '?'), - hl('Register', '@b'), - hl('TernaryColon', ':'), - hl('Register', '@c'), - hl('Ternary', '?'), - hl('Register', '@d'), - hl('TernaryColon', ':'), - hl('Register', '@e'), - }) - check_parsing('@a?@b?@c?@d:@e?@f:@g:@h?@i:@j:@k', { - -- 01234567890123456789012345678901 - -- 0 1 2 3 - ast = { - { - 'Ternary:0:2:?', - children = { - 'Register(name=a):0:0:@a', - { - 'TernaryValue:0:29::', - children = { - { - 'Ternary:0:5:?', - children = { - 'Register(name=b):0:3:@b', - { - 'TernaryValue:0:20::', - children = { - { - 'Ternary:0:8:?', - children = { - 'Register(name=c):0:6:@c', - { - 'TernaryValue:0:11::', - children = { - 'Register(name=d):0:9:@d', - { - 'Ternary:0:14:?', - children = { - 'Register(name=e):0:12:@e', - { - 'TernaryValue:0:17::', - children = { - 'Register(name=f):0:15:@f', - 'Register(name=g):0:18:@g', - }, - }, - }, - }, - }, - }, - }, - }, - { - 'Ternary:0:23:?', - children = { - 'Register(name=h):0:21:@h', - { - 'TernaryValue:0:26::', - children = { - 'Register(name=i):0:24:@i', - 'Register(name=j):0:27:@j', - }, - }, - }, - }, - }, - }, - }, - }, - 'Register(name=k):0:30:@k', - }, - }, - }, - }, - }, - }, { - hl('Register', '@a'), - hl('Ternary', '?'), - hl('Register', '@b'), - hl('Ternary', '?'), - hl('Register', '@c'), - hl('Ternary', '?'), - hl('Register', '@d'), - hl('TernaryColon', ':'), - hl('Register', '@e'), - hl('Ternary', '?'), - hl('Register', '@f'), - hl('TernaryColon', ':'), - hl('Register', '@g'), - hl('TernaryColon', ':'), - hl('Register', '@h'), - hl('Ternary', '?'), - hl('Register', '@i'), - hl('TernaryColon', ':'), - hl('Register', '@j'), - hl('TernaryColon', ':'), - hl('Register', '@k'), - }) - check_parsing('?', { - -- 0 - ast = { - { - 'Ternary:0:0:?', - children = { - 'Missing:0:0:', - 'TernaryValue:0:0:?', - }, - }, - }, - err = { - arg = '?', - msg = 'E15: Expected value, got question mark: %.*s', - }, - }, { - hl('InvalidTernary', '?'), - }) - - check_parsing('?:', { - -- 01 - ast = { - { - 'Ternary:0:0:?', - children = { - 'Missing:0:0:', - { - 'TernaryValue:0:1::', - children = { - 'Missing:0:1:', - }, - }, - }, - }, - }, - err = { - arg = '?:', - msg = 'E15: Expected value, got question mark: %.*s', - }, - }, { - hl('InvalidTernary', '?'), - hl('InvalidTernaryColon', ':'), - }) - - check_parsing('?::', { - -- 012 - ast = { - { - 'Colon:0:2::', - children = { - { - 'Ternary:0:0:?', - children = { - 'Missing:0:0:', - { - 'TernaryValue:0:1::', - children = { - 'Missing:0:1:', - 'Missing:0:2:', - }, - }, - }, - }, - }, - }, - }, - err = { - arg = '?::', - msg = 'E15: Expected value, got question mark: %.*s', - }, - }, { - hl('InvalidTernary', '?'), - hl('InvalidTernaryColon', ':'), - hl('InvalidColon', ':'), - }) - - check_parsing('a?b', { - -- 012 - ast = { - { - 'Ternary:0:1:?', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - { - 'TernaryValue:0:1:?', - children = { - 'PlainIdentifier(scope=0,ident=b):0:2:b', - }, - }, - }, - }, - }, - err = { - arg = '?b', - msg = 'E109: Missing \':\' after \'?\': %.*s', - }, - }, { - hl('IdentifierName', 'a'), - hl('Ternary', '?'), - hl('IdentifierName', 'b'), - }) - check_parsing('a?b:', { - -- 0123 - ast = { - { - 'Ternary:0:1:?', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - { - 'TernaryValue:0:1:?', - children = { - 'PlainIdentifier(scope=b,ident=):0:2:b:', - }, - }, - }, - }, - }, - err = { - arg = '?b:', - msg = 'E109: Missing \':\' after \'?\': %.*s', - }, - }, { - hl('IdentifierName', 'a'), - hl('Ternary', '?'), - hl('IdentifierScope', 'b'), - hl('IdentifierScopeDelimiter', ':'), - }) - - check_parsing('a?b::c', { - -- 012345 - ast = { - { - 'Ternary:0:1:?', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - { - 'TernaryValue:0:4::', - children = { - 'PlainIdentifier(scope=b,ident=):0:2:b:', - 'PlainIdentifier(scope=0,ident=c):0:5:c', - }, - }, - }, - }, - }, - }, { - hl('IdentifierName', 'a'), - hl('Ternary', '?'), - hl('IdentifierScope', 'b'), - hl('IdentifierScopeDelimiter', ':'), - hl('TernaryColon', ':'), - hl('IdentifierName', 'c'), - }) - - check_parsing('a?b :', { - -- 01234 - ast = { - { - 'Ternary:0:1:?', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - { - 'TernaryValue:0:3: :', - children = { - 'PlainIdentifier(scope=0,ident=b):0:2:b', - }, - }, - }, - }, - }, - err = { - arg = '', - msg = 'E15: Expected value, got EOC: %.*s', - }, - }, { - hl('IdentifierName', 'a'), - hl('Ternary', '?'), - hl('IdentifierName', 'b'), - hl('TernaryColon', ':', 1), - }) - - check_parsing('(@a?@b:@c)?@d:@e', { - -- 0123456789012345 - -- 0 1 - ast = { - { - 'Ternary:0:10:?', - children = { - { - 'Nested:0:0:(', - children = { - { - 'Ternary:0:3:?', - children = { - 'Register(name=a):0:1:@a', - { - 'TernaryValue:0:6::', - children = { - 'Register(name=b):0:4:@b', - 'Register(name=c):0:7:@c', - }, - }, - }, - }, - }, - }, - { - 'TernaryValue:0:13::', - children = { - 'Register(name=d):0:11:@d', - 'Register(name=e):0:14:@e', - }, - }, - }, - }, - }, - }, { - hl('NestingParenthesis', '('), - hl('Register', '@a'), - hl('Ternary', '?'), - hl('Register', '@b'), - hl('TernaryColon', ':'), - hl('Register', '@c'), - hl('NestingParenthesis', ')'), - hl('Ternary', '?'), - hl('Register', '@d'), - hl('TernaryColon', ':'), - hl('Register', '@e'), - }) - - check_parsing('(@a?@b:@c)?(@d?@e:@f):(@g?@h:@i)', { - -- 01234567890123456789012345678901 - -- 0 1 2 3 - ast = { - { - 'Ternary:0:10:?', - children = { - { - 'Nested:0:0:(', - children = { - { - 'Ternary:0:3:?', - children = { - 'Register(name=a):0:1:@a', - { - 'TernaryValue:0:6::', - children = { - 'Register(name=b):0:4:@b', - 'Register(name=c):0:7:@c', - }, - }, - }, - }, - }, - }, - { - 'TernaryValue:0:21::', - children = { - { - 'Nested:0:11:(', - children = { - { - 'Ternary:0:14:?', - children = { - 'Register(name=d):0:12:@d', - { - 'TernaryValue:0:17::', - children = { - 'Register(name=e):0:15:@e', - 'Register(name=f):0:18:@f', - }, - }, - }, - }, - }, - }, - { - 'Nested:0:22:(', - children = { - { - 'Ternary:0:25:?', - children = { - 'Register(name=g):0:23:@g', - { - 'TernaryValue:0:28::', - children = { - 'Register(name=h):0:26:@h', - 'Register(name=i):0:29:@i', - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, { - hl('NestingParenthesis', '('), - hl('Register', '@a'), - hl('Ternary', '?'), - hl('Register', '@b'), - hl('TernaryColon', ':'), - hl('Register', '@c'), - hl('NestingParenthesis', ')'), - hl('Ternary', '?'), - hl('NestingParenthesis', '('), - hl('Register', '@d'), - hl('Ternary', '?'), - hl('Register', '@e'), - hl('TernaryColon', ':'), - hl('Register', '@f'), - hl('NestingParenthesis', ')'), - hl('TernaryColon', ':'), - hl('NestingParenthesis', '('), - hl('Register', '@g'), - hl('Ternary', '?'), - hl('Register', '@h'), - hl('TernaryColon', ':'), - hl('Register', '@i'), - hl('NestingParenthesis', ')'), - }) - - check_parsing('(@a?@b:@c)?@d?@e:@f:@g?@h:@i', { - -- 0123456789012345678901234567 - -- 0 1 2 - ast = { - { - 'Ternary:0:10:?', - children = { - { - 'Nested:0:0:(', - children = { - { - 'Ternary:0:3:?', - children = { - 'Register(name=a):0:1:@a', - { - 'TernaryValue:0:6::', - children = { - 'Register(name=b):0:4:@b', - 'Register(name=c):0:7:@c', - }, - }, - }, - }, - }, - }, - { - 'TernaryValue:0:19::', - children = { - { - 'Ternary:0:13:?', - children = { - 'Register(name=d):0:11:@d', - { - 'TernaryValue:0:16::', - children = { - 'Register(name=e):0:14:@e', - 'Register(name=f):0:17:@f', - }, - }, - }, - }, - { - 'Ternary:0:22:?', - children = { - 'Register(name=g):0:20:@g', - { - 'TernaryValue:0:25::', - children = { - 'Register(name=h):0:23:@h', - 'Register(name=i):0:26:@i', - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, { - hl('NestingParenthesis', '('), - hl('Register', '@a'), - hl('Ternary', '?'), - hl('Register', '@b'), - hl('TernaryColon', ':'), - hl('Register', '@c'), - hl('NestingParenthesis', ')'), - hl('Ternary', '?'), - hl('Register', '@d'), - hl('Ternary', '?'), - hl('Register', '@e'), - hl('TernaryColon', ':'), - hl('Register', '@f'), - hl('TernaryColon', ':'), - hl('Register', '@g'), - hl('Ternary', '?'), - hl('Register', '@h'), - hl('TernaryColon', ':'), - hl('Register', '@i'), - }) - check_parsing('a?b{cdef}g:h', { - -- 012345678901 - -- 0 1 - ast = { - { - 'Ternary:0:1:?', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - { - 'TernaryValue:0:10::', - children = { - { - 'ComplexIdentifier:0:3:', - children = { - 'PlainIdentifier(scope=0,ident=b):0:2:b', - { - 'ComplexIdentifier:0:9:', - children = { - { - 'CurlyBracesIdentifier(--i):0:3:{', - children = { - 'PlainIdentifier(scope=0,ident=cdef):0:4:cdef', - }, - }, - 'PlainIdentifier(scope=0,ident=g):0:9:g', - }, - }, - }, - }, - 'PlainIdentifier(scope=0,ident=h):0:11:h', - }, - }, - }, - }, - }, - }, { - hl('IdentifierName', 'a'), - hl('Ternary', '?'), - hl('IdentifierName', 'b'), - hl('Curly', '{'), - hl('IdentifierName', 'cdef'), - hl('Curly', '}'), - hl('IdentifierName', 'g'), - hl('TernaryColon', ':'), - hl('IdentifierName', 'h'), - }) - check_parsing('a ? b : c : d', { - -- 0123456789012 - -- 0 1 - ast = { - { - 'Colon:0:9: :', - children = { - { - 'Ternary:0:1: ?', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - { - 'TernaryValue:0:5: :', - children = { - 'PlainIdentifier(scope=0,ident=b):0:3: b', - 'PlainIdentifier(scope=0,ident=c):0:7: c', - }, - }, - }, - }, - 'PlainIdentifier(scope=0,ident=d):0:11: d', - }, - }, - }, - err = { - arg = ': d', - msg = 'E15: Colon outside of dictionary or ternary operator: %.*s', - }, - }, { - hl('IdentifierName', 'a'), - hl('Ternary', '?', 1), - hl('IdentifierName', 'b', 1), - hl('TernaryColon', ':', 1), - hl('IdentifierName', 'c', 1), - hl('InvalidColon', ':', 1), - hl('IdentifierName', 'd', 1), - }) - end) - itp('works with comparison operators', function() - check_parsing('a == b', { - -- 012345 - ast = { - { - 'Comparison(type=Equal,inv=0,ccs=UseOption):0:1: ==', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - 'PlainIdentifier(scope=0,ident=b):0:4: b', - }, - }, - }, - }, { - hl('IdentifierName', 'a'), - hl('Comparison', '==', 1), - hl('IdentifierName', 'b', 1), - }) - - check_parsing('a ==? b', { - -- 0123456 - ast = { - { - 'Comparison(type=Equal,inv=0,ccs=IgnoreCase):0:1: ==?', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - 'PlainIdentifier(scope=0,ident=b):0:5: b', - }, - }, - }, - }, { - hl('IdentifierName', 'a'), - hl('Comparison', '==', 1), - hl('ComparisonModifier', '?'), - hl('IdentifierName', 'b', 1), - }) - - check_parsing('a ==# b', { - -- 0123456 - ast = { - { - 'Comparison(type=Equal,inv=0,ccs=MatchCase):0:1: ==#', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - 'PlainIdentifier(scope=0,ident=b):0:5: b', - }, - }, - }, - }, { - hl('IdentifierName', 'a'), - hl('Comparison', '==', 1), - hl('ComparisonModifier', '#'), - hl('IdentifierName', 'b', 1), - }) - - check_parsing('a !=# b', { - -- 0123456 - ast = { - { - 'Comparison(type=Equal,inv=1,ccs=MatchCase):0:1: !=#', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - 'PlainIdentifier(scope=0,ident=b):0:5: b', - }, - }, - }, - }, { - hl('IdentifierName', 'a'), - hl('Comparison', '!=', 1), - hl('ComparisonModifier', '#'), - hl('IdentifierName', 'b', 1), - }) - - check_parsing('a <=# b', { - -- 0123456 - ast = { - { - 'Comparison(type=Greater,inv=1,ccs=MatchCase):0:1: <=#', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - 'PlainIdentifier(scope=0,ident=b):0:5: b', - }, - }, - }, - }, { - hl('IdentifierName', 'a'), - hl('Comparison', '<=', 1), - hl('ComparisonModifier', '#'), - hl('IdentifierName', 'b', 1), - }) - - check_parsing('a >=# b', { - -- 0123456 - ast = { - { - 'Comparison(type=GreaterOrEqual,inv=0,ccs=MatchCase):0:1: >=#', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - 'PlainIdentifier(scope=0,ident=b):0:5: b', - }, - }, - }, - }, { - hl('IdentifierName', 'a'), - hl('Comparison', '>=', 1), - hl('ComparisonModifier', '#'), - hl('IdentifierName', 'b', 1), - }) - - check_parsing('a ># b', { - -- 012345 - ast = { - { - 'Comparison(type=Greater,inv=0,ccs=MatchCase):0:1: >#', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - 'PlainIdentifier(scope=0,ident=b):0:4: b', - }, - }, - }, - }, { - hl('IdentifierName', 'a'), - hl('Comparison', '>', 1), - hl('ComparisonModifier', '#'), - hl('IdentifierName', 'b', 1), - }) - - check_parsing('a <# b', { - -- 012345 - ast = { - { - 'Comparison(type=GreaterOrEqual,inv=1,ccs=MatchCase):0:1: <#', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - 'PlainIdentifier(scope=0,ident=b):0:4: b', - }, - }, - }, - }, { - hl('IdentifierName', 'a'), - hl('Comparison', '<', 1), - hl('ComparisonModifier', '#'), - hl('IdentifierName', 'b', 1), - }) - - check_parsing('a is#b', { - -- 012345 - ast = { - { - 'Comparison(type=Identical,inv=0,ccs=MatchCase):0:1: is#', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - 'PlainIdentifier(scope=0,ident=b):0:5:b', - }, - }, - }, - }, { - hl('IdentifierName', 'a'), - hl('Comparison', 'is', 1), - hl('ComparisonModifier', '#'), - hl('IdentifierName', 'b'), - }) - - check_parsing('a is?b', { - -- 012345 - ast = { - { - 'Comparison(type=Identical,inv=0,ccs=IgnoreCase):0:1: is?', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - 'PlainIdentifier(scope=0,ident=b):0:5:b', - }, - }, - }, - }, { - hl('IdentifierName', 'a'), - hl('Comparison', 'is', 1), - hl('ComparisonModifier', '?'), - hl('IdentifierName', 'b'), - }) - - check_parsing('a isnot b', { - -- 012345678 - ast = { - { - 'Comparison(type=Identical,inv=1,ccs=UseOption):0:1: isnot', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - 'PlainIdentifier(scope=0,ident=b):0:7: b', - }, - }, - }, - }, { - hl('IdentifierName', 'a'), - hl('Comparison', 'isnot', 1), - hl('IdentifierName', 'b', 1), - }) - - check_parsing('a < b < c', { - -- 012345678 - ast = { - { - 'Comparison(type=GreaterOrEqual,inv=1,ccs=UseOption):0:1: <', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - { - 'Comparison(type=GreaterOrEqual,inv=1,ccs=UseOption):0:5: <', - children = { - 'PlainIdentifier(scope=0,ident=b):0:3: b', - 'PlainIdentifier(scope=0,ident=c):0:7: c', - }, - }, - }, - }, - }, - err = { - arg = ' < c', - msg = 'E15: Operator is not associative: %.*s', - }, - }, { - hl('IdentifierName', 'a'), - hl('Comparison', '<', 1), - hl('IdentifierName', 'b', 1), - hl('InvalidComparison', '<', 1), - hl('IdentifierName', 'c', 1), - }) - - check_parsing('a < b <# c', { - -- 012345678 - ast = { - { - 'Comparison(type=GreaterOrEqual,inv=1,ccs=UseOption):0:1: <', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - { - 'Comparison(type=GreaterOrEqual,inv=1,ccs=MatchCase):0:5: <#', - children = { - 'PlainIdentifier(scope=0,ident=b):0:3: b', - 'PlainIdentifier(scope=0,ident=c):0:8: c', - }, - }, - }, - }, - }, - err = { - arg = ' <# c', - msg = 'E15: Operator is not associative: %.*s', - }, - }, { - hl('IdentifierName', 'a'), - hl('Comparison', '<', 1), - hl('IdentifierName', 'b', 1), - hl('InvalidComparison', '<', 1), - hl('InvalidComparisonModifier', '#'), - hl('IdentifierName', 'c', 1), - }) - - check_parsing('a += b', { - -- 012345 - ast = { - { - 'Assignment(Add):0:1: +=', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - 'PlainIdentifier(scope=0,ident=b):0:4: b', - }, - }, - }, - err = { - arg = '+= b', - msg = 'E15: Misplaced assignment: %.*s', - }, - }, { - hl('IdentifierName', 'a'), - hl('InvalidAssignmentWithAddition', '+=', 1), - hl('IdentifierName', 'b', 1), - }) - check_parsing('a + b == c + d', { - -- 01234567890123 - -- 0 1 - ast = { - { - 'Comparison(type=Equal,inv=0,ccs=UseOption):0:5: ==', - children = { - { - 'BinaryPlus:0:1: +', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - 'PlainIdentifier(scope=0,ident=b):0:3: b', - }, - }, - { - 'BinaryPlus:0:10: +', - children = { - 'PlainIdentifier(scope=0,ident=c):0:8: c', - 'PlainIdentifier(scope=0,ident=d):0:12: d', - }, - }, - }, - }, - }, - }, { - hl('IdentifierName', 'a'), - hl('BinaryPlus', '+', 1), - hl('IdentifierName', 'b', 1), - hl('Comparison', '==', 1), - hl('IdentifierName', 'c', 1), - hl('BinaryPlus', '+', 1), - hl('IdentifierName', 'd', 1), - }) - check_parsing('+ a == + b', { - -- 0123456789 - ast = { - { - 'Comparison(type=Equal,inv=0,ccs=UseOption):0:3: ==', - children = { - { - 'UnaryPlus:0:0:+', - children = { - 'PlainIdentifier(scope=0,ident=a):0:1: a', - }, - }, - { - 'UnaryPlus:0:6: +', - children = { - 'PlainIdentifier(scope=0,ident=b):0:8: b', - }, - }, - }, - }, - }, - }, { - hl('UnaryPlus', '+'), - hl('IdentifierName', 'a', 1), - hl('Comparison', '==', 1), - hl('UnaryPlus', '+', 1), - hl('IdentifierName', 'b', 1), - }) - end) - itp('works with concat/subscript', function() - check_parsing('.', { - -- 0 - ast = { - { - 'ConcatOrSubscript:0:0:.', - children = { - 'Missing:0:0:', - }, - }, - }, - err = { - arg = '.', - msg = 'E15: Unexpected dot: %.*s', - }, - }, { - hl('InvalidConcatOrSubscript', '.'), - }) - - check_parsing('a.', { - -- 01 - ast = { - { - 'ConcatOrSubscript:0:1:.', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - }, - }, - }, - err = { - arg = '', - msg = 'E15: Expected value, got EOC: %.*s', - }, - }, { - hl('IdentifierName', 'a'), - hl('ConcatOrSubscript', '.'), - }) - - check_parsing('a.b', { - -- 012 - ast = { - { - 'ConcatOrSubscript:0:1:.', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - 'PlainKey(key=b):0:2:b', - }, - }, - }, - }, { - hl('IdentifierName', 'a'), - hl('ConcatOrSubscript', '.'), - hl('IdentifierKey', 'b'), - }) - - check_parsing('1.2', { - -- 012 - ast = { - 'Float(val=1.200000e+00):0:0:1.2', - }, - }, { - hl('Float', '1.2'), - }) - - check_parsing('1.2 + 1.3e-5', { - -- 012345678901 - -- 0 1 - ast = { - { - 'BinaryPlus:0:3: +', - children = { - 'Float(val=1.200000e+00):0:0:1.2', - 'Float(val=1.300000e-05):0:5: 1.3e-5', - }, - }, - }, - }, { - hl('Float', '1.2'), - hl('BinaryPlus', '+', 1), - hl('Float', '1.3e-5', 1), - }) - - check_parsing('a . 1.2 + 1.3e-5', { - -- 0123456789012345 - -- 0 1 - ast = { - { - 'BinaryPlus:0:7: +', - children = { - { - 'Concat:0:1: .', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - { - 'ConcatOrSubscript:0:5:.', - children = { - 'Integer(val=1):0:3: 1', - 'PlainKey(key=2):0:6:2', - }, - }, - }, - }, - 'Float(val=1.300000e-05):0:9: 1.3e-5', - }, - }, - }, - }, { - hl('IdentifierName', 'a'), - hl('Concat', '.', 1), - hl('Number', '1', 1), - hl('ConcatOrSubscript', '.'), - hl('IdentifierKey', '2'), - hl('BinaryPlus', '+', 1), - hl('Float', '1.3e-5', 1), - }) - - check_parsing('1.3e-5 + 1.2 . a', { - -- 0123456789012345 - -- 0 1 - ast = { - { - 'Concat:0:12: .', - children = { - { - 'BinaryPlus:0:6: +', - children = { - 'Float(val=1.300000e-05):0:0:1.3e-5', - 'Float(val=1.200000e+00):0:8: 1.2', - }, - }, - 'PlainIdentifier(scope=0,ident=a):0:14: a', - }, - }, - }, - }, { - hl('Float', '1.3e-5'), - hl('BinaryPlus', '+', 1), - hl('Float', '1.2', 1), - hl('Concat', '.', 1), - hl('IdentifierName', 'a', 1), - }) - - check_parsing('1.3e-5 + a . 1.2', { - -- 0123456789012345 - -- 0 1 - ast = { - { - 'Concat:0:10: .', - children = { - { - 'BinaryPlus:0:6: +', - children = { - 'Float(val=1.300000e-05):0:0:1.3e-5', - 'PlainIdentifier(scope=0,ident=a):0:8: a', - }, - }, - { - 'ConcatOrSubscript:0:14:.', - children = { - 'Integer(val=1):0:12: 1', - 'PlainKey(key=2):0:15:2', - }, - }, - }, - }, - }, - }, { - hl('Float', '1.3e-5'), - hl('BinaryPlus', '+', 1), - hl('IdentifierName', 'a', 1), - hl('Concat', '.', 1), - hl('Number', '1', 1), - hl('ConcatOrSubscript', '.'), - hl('IdentifierKey', '2'), - }) - - check_parsing('1.2.3', { - -- 01234 - ast = { - { - 'ConcatOrSubscript:0:3:.', - children = { - { - 'ConcatOrSubscript:0:1:.', - children = { - 'Integer(val=1):0:0:1', - 'PlainKey(key=2):0:2:2', - }, - }, - 'PlainKey(key=3):0:4:3', - }, - }, - }, - }, { - hl('Number', '1'), - hl('ConcatOrSubscript', '.'), - hl('IdentifierKey', '2'), - hl('ConcatOrSubscript', '.'), - hl('IdentifierKey', '3'), - }) - - check_parsing('a.1.2', { - -- 01234 - ast = { - { - 'ConcatOrSubscript:0:3:.', - children = { - { - 'ConcatOrSubscript:0:1:.', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - 'PlainKey(key=1):0:2:1', - }, - }, - 'PlainKey(key=2):0:4:2', - }, - }, - }, - }, { - hl('IdentifierName', 'a'), - hl('ConcatOrSubscript', '.'), - hl('IdentifierKey', '1'), - hl('ConcatOrSubscript', '.'), - hl('IdentifierKey', '2'), - }) - - check_parsing('a . 1.2', { - -- 0123456 - ast = { - { - 'Concat:0:1: .', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - { - 'ConcatOrSubscript:0:5:.', - children = { - 'Integer(val=1):0:3: 1', - 'PlainKey(key=2):0:6:2', - }, - }, - }, - }, - }, - }, { - hl('IdentifierName', 'a'), - hl('Concat', '.', 1), - hl('Number', '1', 1), - hl('ConcatOrSubscript', '.'), - hl('IdentifierKey', '2'), - }) - - check_parsing('+a . +b', { - -- 0123456 - ast = { - { - 'Concat:0:2: .', - children = { - { - 'UnaryPlus:0:0:+', - children = { - 'PlainIdentifier(scope=0,ident=a):0:1:a', - }, - }, - { - 'UnaryPlus:0:4: +', - children = { - 'PlainIdentifier(scope=0,ident=b):0:6:b', - }, - }, - }, - }, - }, - }, { - hl('UnaryPlus', '+'), - hl('IdentifierName', 'a'), - hl('Concat', '.', 1), - hl('UnaryPlus', '+', 1), - hl('IdentifierName', 'b'), - }) - - check_parsing('a. b', { - -- 0123 - ast = { - { - 'Concat:0:1:.', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - 'PlainIdentifier(scope=0,ident=b):0:2: b', - }, - }, - }, - }, { - hl('IdentifierName', 'a'), - hl('ConcatOrSubscript', '.'), - hl('IdentifierName', 'b', 1), - }) - - check_parsing('a. 1', { - -- 0123 - ast = { - { - 'Concat:0:1:.', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - 'Integer(val=1):0:2: 1', - }, - }, - }, - }, { - hl('IdentifierName', 'a'), - hl('ConcatOrSubscript', '.'), - hl('Number', '1', 1), - }) - end) - itp('works with bracket subscripts', function() - check_parsing(':', { - -- 0 - ast = { - { - 'Colon:0:0::', - children = { - 'Missing:0:0:', - }, - }, - }, - err = { - arg = ':', - msg = 'E15: Colon outside of dictionary or ternary operator: %.*s', - }, - }, { - hl('InvalidColon', ':'), - }) - check_parsing('a[]', { - -- 012 - ast = { - { - 'Subscript:0:1:[', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - }, - }, - }, - err = { - arg = ']', - msg = 'E15: Expected value, got closing bracket: %.*s', - }, - }, { - hl('IdentifierName', 'a'), - hl('SubscriptBracket', '['), - hl('InvalidSubscriptBracket', ']'), - }) - check_parsing('a[b:]', { - -- 01234 - ast = { - { - 'Subscript:0:1:[', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - 'PlainIdentifier(scope=b,ident=):0:2:b:', - }, - }, - }, - }, { - hl('IdentifierName', 'a'), - hl('SubscriptBracket', '['), - hl('IdentifierScope', 'b'), - hl('IdentifierScopeDelimiter', ':'), - hl('SubscriptBracket', ']'), - }) - - check_parsing('a[b:c]', { - -- 012345 - ast = { - { - 'Subscript:0:1:[', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - 'PlainIdentifier(scope=b,ident=c):0:2:b:c', - }, - }, - }, - }, { - hl('IdentifierName', 'a'), - hl('SubscriptBracket', '['), - hl('IdentifierScope', 'b'), - hl('IdentifierScopeDelimiter', ':'), - hl('IdentifierName', 'c'), - hl('SubscriptBracket', ']'), - }) - check_parsing('a[b : c]', { - -- 01234567 - ast = { - { - 'Subscript:0:1:[', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - { - 'Colon:0:3: :', - children = { - 'PlainIdentifier(scope=0,ident=b):0:2:b', - 'PlainIdentifier(scope=0,ident=c):0:5: c', - }, - }, - }, - }, - }, - }, { - hl('IdentifierName', 'a'), - hl('SubscriptBracket', '['), - hl('IdentifierName', 'b'), - hl('SubscriptColon', ':', 1), - hl('IdentifierName', 'c', 1), - hl('SubscriptBracket', ']'), - }) - - check_parsing('a[: b]', { - -- 012345 - ast = { - { - 'Subscript:0:1:[', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - { - 'Colon:0:2::', - children = { - 'Missing:0:2:', - 'PlainIdentifier(scope=0,ident=b):0:3: b', - }, - }, - }, - }, - }, - }, { - hl('IdentifierName', 'a'), - hl('SubscriptBracket', '['), - hl('SubscriptColon', ':'), - hl('IdentifierName', 'b', 1), - hl('SubscriptBracket', ']'), - }) - - check_parsing('a[b :]', { - -- 012345 - ast = { - { - 'Subscript:0:1:[', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - { - 'Colon:0:3: :', - children = { - 'PlainIdentifier(scope=0,ident=b):0:2:b', - }, - }, - }, - }, - }, - }, { - hl('IdentifierName', 'a'), - hl('SubscriptBracket', '['), - hl('IdentifierName', 'b'), - hl('SubscriptColon', ':', 1), - hl('SubscriptBracket', ']'), - }) - check_parsing('a[b][c][d](e)(f)(g)', { - -- 0123456789012345678 - -- 0 1 - ast = { - { - 'Call:0:16:(', - children = { - { - 'Call:0:13:(', - children = { - { - 'Call:0:10:(', - children = { - { - 'Subscript:0:7:[', - children = { - { - 'Subscript:0:4:[', - children = { - { - 'Subscript:0:1:[', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - 'PlainIdentifier(scope=0,ident=b):0:2:b', - }, - }, - 'PlainIdentifier(scope=0,ident=c):0:5:c', - }, - }, - 'PlainIdentifier(scope=0,ident=d):0:8:d', - }, - }, - 'PlainIdentifier(scope=0,ident=e):0:11:e', - }, - }, - 'PlainIdentifier(scope=0,ident=f):0:14:f', - }, - }, - 'PlainIdentifier(scope=0,ident=g):0:17:g', - }, - }, - }, - }, { - hl('IdentifierName', 'a'), - hl('SubscriptBracket', '['), - hl('IdentifierName', 'b'), - hl('SubscriptBracket', ']'), - hl('SubscriptBracket', '['), - hl('IdentifierName', 'c'), - hl('SubscriptBracket', ']'), - hl('SubscriptBracket', '['), - hl('IdentifierName', 'd'), - hl('SubscriptBracket', ']'), - hl('CallingParenthesis', '('), - hl('IdentifierName', 'e'), - hl('CallingParenthesis', ')'), - hl('CallingParenthesis', '('), - hl('IdentifierName', 'f'), - hl('CallingParenthesis', ')'), - hl('CallingParenthesis', '('), - hl('IdentifierName', 'g'), - hl('CallingParenthesis', ')'), - }) - check_parsing('{a}{b}{c}[d][e][f]', { - -- 012345678901234567 - -- 0 1 - ast = { - { - 'Subscript:0:15:[', - children = { - { - 'Subscript:0:12:[', - children = { - { - 'Subscript:0:9:[', - children = { - { - 'ComplexIdentifier:0:3:', - children = { - { - 'CurlyBracesIdentifier(-di):0:0:{', - children = { - 'PlainIdentifier(scope=0,ident=a):0:1:a', - }, - }, - { - 'ComplexIdentifier:0:6:', - children = { - { - 'CurlyBracesIdentifier(--i):0:3:{', - children = { - 'PlainIdentifier(scope=0,ident=b):0:4:b', - }, - }, - { - 'CurlyBracesIdentifier(--i):0:6:{', - children = { - 'PlainIdentifier(scope=0,ident=c):0:7:c', - }, - }, - }, - }, - }, - }, - 'PlainIdentifier(scope=0,ident=d):0:10:d', - }, - }, - 'PlainIdentifier(scope=0,ident=e):0:13:e', - }, - }, - 'PlainIdentifier(scope=0,ident=f):0:16:f', - }, - }, - }, - }, { - hl('Curly', '{'), - hl('IdentifierName', 'a'), - hl('Curly', '}'), - hl('Curly', '{'), - hl('IdentifierName', 'b'), - hl('Curly', '}'), - hl('Curly', '{'), - hl('IdentifierName', 'c'), - hl('Curly', '}'), - hl('SubscriptBracket', '['), - hl('IdentifierName', 'd'), - hl('SubscriptBracket', ']'), - hl('SubscriptBracket', '['), - hl('IdentifierName', 'e'), - hl('SubscriptBracket', ']'), - hl('SubscriptBracket', '['), - hl('IdentifierName', 'f'), - hl('SubscriptBracket', ']'), - }) - end) - itp('supports list literals', function() - check_parsing('[]', { - -- 01 - ast = { - 'ListLiteral:0:0:[', - }, - }, { - hl('List', '['), - hl('List', ']'), - }) - - check_parsing('[a]', { - -- 012 - ast = { - { - 'ListLiteral:0:0:[', - children = { - 'PlainIdentifier(scope=0,ident=a):0:1:a', - }, - }, - }, - }, { - hl('List', '['), - hl('IdentifierName', 'a'), - hl('List', ']'), - }) - - check_parsing('[a, b]', { - -- 012345 - ast = { - { - 'ListLiteral:0:0:[', - children = { - { - 'Comma:0:2:,', - children = { - 'PlainIdentifier(scope=0,ident=a):0:1:a', - 'PlainIdentifier(scope=0,ident=b):0:3: b', - }, - }, - }, - }, - }, - }, { - hl('List', '['), - hl('IdentifierName', 'a'), - hl('Comma', ','), - hl('IdentifierName', 'b', 1), - hl('List', ']'), - }) - - check_parsing('[a, b, c]', { - -- 012345678 - ast = { - { - 'ListLiteral:0:0:[', - children = { - { - 'Comma:0:2:,', - children = { - 'PlainIdentifier(scope=0,ident=a):0:1:a', - { - 'Comma:0:5:,', - children = { - 'PlainIdentifier(scope=0,ident=b):0:3: b', - 'PlainIdentifier(scope=0,ident=c):0:6: c', - }, - }, - }, - }, - }, - }, - }, - }, { - hl('List', '['), - hl('IdentifierName', 'a'), - hl('Comma', ','), - hl('IdentifierName', 'b', 1), - hl('Comma', ','), - hl('IdentifierName', 'c', 1), - hl('List', ']'), - }) - - check_parsing('[a, b, c, ]', { - -- 01234567890 - -- 0 1 - ast = { - { - 'ListLiteral:0:0:[', - children = { - { - 'Comma:0:2:,', - children = { - 'PlainIdentifier(scope=0,ident=a):0:1:a', - { - 'Comma:0:5:,', - children = { - 'PlainIdentifier(scope=0,ident=b):0:3: b', - { - 'Comma:0:8:,', - children = { - 'PlainIdentifier(scope=0,ident=c):0:6: c', - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, { - hl('List', '['), - hl('IdentifierName', 'a'), - hl('Comma', ','), - hl('IdentifierName', 'b', 1), - hl('Comma', ','), - hl('IdentifierName', 'c', 1), - hl('Comma', ','), - hl('List', ']', 1), - }) - - check_parsing('[a : b, c : d]', { - -- 01234567890123 - -- 0 1 - ast = { - { - 'ListLiteral:0:0:[', - children = { - { - 'Comma:0:6:,', - children = { - { - 'Colon:0:2: :', - children = { - 'PlainIdentifier(scope=0,ident=a):0:1:a', - 'PlainIdentifier(scope=0,ident=b):0:4: b', - }, - }, - { - 'Colon:0:9: :', - children = { - 'PlainIdentifier(scope=0,ident=c):0:7: c', - 'PlainIdentifier(scope=0,ident=d):0:11: d', - }, - }, - }, - }, - }, - }, - }, - err = { - arg = ': b, c : d]', - msg = 'E15: Colon outside of dictionary or ternary operator: %.*s', - }, - }, { - hl('List', '['), - hl('IdentifierName', 'a'), - hl('InvalidColon', ':', 1), - hl('IdentifierName', 'b', 1), - hl('Comma', ','), - hl('IdentifierName', 'c', 1), - hl('InvalidColon', ':', 1), - hl('IdentifierName', 'd', 1), - hl('List', ']'), - }) - - check_parsing(']', { - -- 0 - ast = { - 'ListLiteral:0:0:', - }, - err = { - arg = ']', - msg = 'E15: Unexpected closing figure brace: %.*s', - }, - }, { - hl('InvalidList', ']'), - }) - - check_parsing('a]', { - -- 01 - ast = { - { - 'ListLiteral:0:1:', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - }, - }, - }, - err = { - arg = ']', - msg = 'E15: Unexpected closing figure brace: %.*s', - }, - }, { - hl('IdentifierName', 'a'), - hl('InvalidList', ']'), - }) - - check_parsing('[] []', { - -- 01234 - ast = { - { - 'OpMissing:0:2:', - children = { - 'ListLiteral:0:0:[', - 'ListLiteral:0:2: [', - }, - }, - }, - err = { - arg = '[]', - msg = 'E15: Missing operator: %.*s', - }, - }, { - hl('List', '['), - hl('List', ']'), - hl('InvalidSpacing', ' '), - hl('List', '['), - hl('List', ']'), - }, { - [1] = { - ast = { - err = REMOVE_THIS, - ast = { - 'ListLiteral:0:0:[', - }, - }, - hl_fs = { - [3] = REMOVE_THIS, - [4] = REMOVE_THIS, - [5] = REMOVE_THIS, - }, - }, - }) - - check_parsing('[][]', { - -- 0123 - ast = { - { - 'Subscript:0:2:[', - children = { - 'ListLiteral:0:0:[', - }, - }, - }, - err = { - arg = ']', - msg = 'E15: Expected value, got closing bracket: %.*s', - }, - }, { - hl('List', '['), - hl('List', ']'), - hl('SubscriptBracket', '['), - hl('InvalidSubscriptBracket', ']'), - }) - - check_parsing('[', { - -- 0 - ast = { - 'ListLiteral:0:0:[', - }, - err = { - arg = '', - msg = 'E15: Expected value, got EOC: %.*s', - }, - }, { - hl('List', '['), - }) - - check_parsing('[1', { - -- 01 - ast = { - { - 'ListLiteral:0:0:[', - children = { - 'Integer(val=1):0:1:1', - }, - }, - }, - err = { - arg = '[1', - msg = 'E697: Missing end of List \']\': %.*s', - }, - }, { - hl('List', '['), - hl('Number', '1'), - }) - end) - itp('works with strings', function() - check_parsing('\'abc\'', { - -- 01234 - ast = { - 'SingleQuotedString(val="abc"):0:0:\'abc\'', - }, - }, { - hl('SingleQuote', '\''), - hl('SingleQuotedBody', 'abc'), - hl('SingleQuote', '\''), - }) - check_parsing('"abc"', { - -- 01234 - ast = { - 'DoubleQuotedString(val="abc"):0:0:"abc"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedBody', 'abc'), - hl('DoubleQuote', '"'), - }) - check_parsing('\'\'', { - -- 01 - ast = { - 'SingleQuotedString(val=NULL):0:0:\'\'', - }, - }, { - hl('SingleQuote', '\''), - hl('SingleQuote', '\''), - }) - check_parsing('""', { - -- 01 - ast = { - 'DoubleQuotedString(val=NULL):0:0:""', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuote', '"'), - }) - check_parsing('"', { - -- 0 - ast = { - 'DoubleQuotedString(val=NULL):0:0:"', - }, - err = { - arg = '"', - msg = 'E114: Missing double quote: %.*s', - }, - }, { - hl('InvalidDoubleQuote', '"'), - }) - check_parsing('\'', { - -- 0 - ast = { - 'SingleQuotedString(val=NULL):0:0:\'', - }, - err = { - arg = '\'', - msg = 'E115: Missing single quote: %.*s', - }, - }, { - hl('InvalidSingleQuote', '\''), - }) - check_parsing('"a', { - -- 01 - ast = { - 'DoubleQuotedString(val="a"):0:0:"a', - }, - err = { - arg = '"a', - msg = 'E114: Missing double quote: %.*s', - }, - }, { - hl('InvalidDoubleQuote', '"'), - hl('InvalidDoubleQuotedBody', 'a'), - }) - check_parsing('\'a', { - -- 01 - ast = { - 'SingleQuotedString(val="a"):0:0:\'a', - }, - err = { - arg = '\'a', - msg = 'E115: Missing single quote: %.*s', - }, - }, { - hl('InvalidSingleQuote', '\''), - hl('InvalidSingleQuotedBody', 'a'), - }) - check_parsing('\'abc\'\'def\'', { - -- 0123456789 - ast = { - 'SingleQuotedString(val="abc\'def"):0:0:\'abc\'\'def\'', - }, - }, { - hl('SingleQuote', '\''), - hl('SingleQuotedBody', 'abc'), - hl('SingleQuotedQuote', '\'\''), - hl('SingleQuotedBody', 'def'), - hl('SingleQuote', '\''), - }) - check_parsing('\'abc\'\'', { - -- 012345 - ast = { - 'SingleQuotedString(val="abc\'"):0:0:\'abc\'\'', - }, - err = { - arg = '\'abc\'\'', - msg = 'E115: Missing single quote: %.*s', - }, - }, { - hl('InvalidSingleQuote', '\''), - hl('InvalidSingleQuotedBody', 'abc'), - hl('InvalidSingleQuotedQuote', '\'\''), - }) - check_parsing('\'\'\'\'\'\'\'\'', { - -- 01234567 - ast = { - 'SingleQuotedString(val="\'\'\'"):0:0:\'\'\'\'\'\'\'\'', - }, - }, { - hl('SingleQuote', '\''), - hl('SingleQuotedQuote', '\'\''), - hl('SingleQuotedQuote', '\'\''), - hl('SingleQuotedQuote', '\'\''), - hl('SingleQuote', '\''), - }) - check_parsing('\'\'\'a\'\'\'\'bc\'', { - -- 01234567890 - -- 0 1 - ast = { - 'SingleQuotedString(val="\'a\'\'bc"):0:0:\'\'\'a\'\'\'\'bc\'', - }, - }, { - hl('SingleQuote', '\''), - hl('SingleQuotedQuote', '\'\''), - hl('SingleQuotedBody', 'a'), - hl('SingleQuotedQuote', '\'\''), - hl('SingleQuotedQuote', '\'\''), - hl('SingleQuotedBody', 'bc'), - hl('SingleQuote', '\''), - }) - check_parsing('"\\"\\"\\"\\""', { - -- 0123456789 - ast = { - 'DoubleQuotedString(val="\\"\\"\\"\\""):0:0:"\\"\\"\\"\\""', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedEscape', '\\"'), - hl('DoubleQuotedEscape', '\\"'), - hl('DoubleQuotedEscape', '\\"'), - hl('DoubleQuotedEscape', '\\"'), - hl('DoubleQuote', '"'), - }) - check_parsing('"abc\\"def\\"ghi\\"jkl\\"mno"', { - -- 0123456789012345678901234 - -- 0 1 2 - ast = { - 'DoubleQuotedString(val="abc\\"def\\"ghi\\"jkl\\"mno"):0:0:"abc\\"def\\"ghi\\"jkl\\"mno"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedBody', 'abc'), - hl('DoubleQuotedEscape', '\\"'), - hl('DoubleQuotedBody', 'def'), - hl('DoubleQuotedEscape', '\\"'), - hl('DoubleQuotedBody', 'ghi'), - hl('DoubleQuotedEscape', '\\"'), - hl('DoubleQuotedBody', 'jkl'), - hl('DoubleQuotedEscape', '\\"'), - hl('DoubleQuotedBody', 'mno'), - hl('DoubleQuote', '"'), - }) - check_parsing('"\\b\\e\\f\\r\\t\\\\"', { - -- 0123456789012345 - -- 0 1 - ast = { - [[DoubleQuotedString(val="\8\27\12\13\9\\"):0:0:"\b\e\f\r\t\\"]], - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedEscape', '\\b'), - hl('DoubleQuotedEscape', '\\e'), - hl('DoubleQuotedEscape', '\\f'), - hl('DoubleQuotedEscape', '\\r'), - hl('DoubleQuotedEscape', '\\t'), - hl('DoubleQuotedEscape', '\\\\'), - hl('DoubleQuote', '"'), - }) - check_parsing('"\\n\n"', { - -- 01234 - ast = { - 'DoubleQuotedString(val="\\\n\\\n"):0:0:"\\n\n"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedEscape', '\\n'), - hl('DoubleQuotedBody', '\n'), - hl('DoubleQuote', '"'), - }) - check_parsing('"\\x00"', { - -- 012345 - ast = { - 'DoubleQuotedString(val="\\0"):0:0:"\\x00"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedEscape', '\\x00'), - hl('DoubleQuote', '"'), - }) - check_parsing('"\\xFF"', { - -- 012345 - ast = { - 'DoubleQuotedString(val="\255"):0:0:"\\xFF"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedEscape', '\\xFF'), - hl('DoubleQuote', '"'), - }) - check_parsing('"\\xF"', { - -- 012345 - ast = { - 'DoubleQuotedString(val="\\15"):0:0:"\\xF"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedEscape', '\\xF'), - hl('DoubleQuote', '"'), - }) - check_parsing('"\\u00AB"', { - -- 01234567 - ast = { - 'DoubleQuotedString(val="«"):0:0:"\\u00AB"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedEscape', '\\u00AB'), - hl('DoubleQuote', '"'), - }) - check_parsing('"\\U000000AB"', { - -- 01234567 - ast = { - 'DoubleQuotedString(val="«"):0:0:"\\U000000AB"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedEscape', '\\U000000AB'), - hl('DoubleQuote', '"'), - }) - check_parsing('"\\x"', { - -- 0123 - ast = { - 'DoubleQuotedString(val="x"):0:0:"\\x"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedUnknownEscape', '\\x'), - hl('DoubleQuote', '"'), - }) - - check_parsing('"\\x', { - -- 012 - ast = { - 'DoubleQuotedString(val="x"):0:0:"\\x', - }, - err = { - arg = '"\\x', - msg = 'E114: Missing double quote: %.*s', - }, - }, { - hl('InvalidDoubleQuote', '"'), - hl('InvalidDoubleQuotedUnknownEscape', '\\x'), - }) - - check_parsing('"\\xF', { - -- 0123 - ast = { - 'DoubleQuotedString(val="\\15"):0:0:"\\xF', - }, - err = { - arg = '"\\xF', - msg = 'E114: Missing double quote: %.*s', - }, - }, { - hl('InvalidDoubleQuote', '"'), - hl('InvalidDoubleQuotedEscape', '\\xF'), - }) - - check_parsing('"\\u"', { - -- 0123 - ast = { - 'DoubleQuotedString(val="u"):0:0:"\\u"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedUnknownEscape', '\\u'), - hl('DoubleQuote', '"'), - }) - - check_parsing('"\\u', { - -- 012 - ast = { - 'DoubleQuotedString(val="u"):0:0:"\\u', - }, - err = { - arg = '"\\u', - msg = 'E114: Missing double quote: %.*s', - }, - }, { - hl('InvalidDoubleQuote', '"'), - hl('InvalidDoubleQuotedUnknownEscape', '\\u'), - }) - - check_parsing('"\\U', { - -- 012 - ast = { - 'DoubleQuotedString(val="U"):0:0:"\\U', - }, - err = { - arg = '"\\U', - msg = 'E114: Missing double quote: %.*s', - }, - }, { - hl('InvalidDoubleQuote', '"'), - hl('InvalidDoubleQuotedUnknownEscape', '\\U'), - }) - - check_parsing('"\\U"', { - -- 0123 - ast = { - 'DoubleQuotedString(val="U"):0:0:"\\U"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedUnknownEscape', '\\U'), - hl('DoubleQuote', '"'), - }) - - check_parsing('"\\xFX"', { - -- 012345 - ast = { - 'DoubleQuotedString(val="\\15X"):0:0:"\\xFX"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedEscape', '\\xF'), - hl('DoubleQuotedBody', 'X'), - hl('DoubleQuote', '"'), - }) - - check_parsing('"\\XFX"', { - -- 012345 - ast = { - 'DoubleQuotedString(val="\\15X"):0:0:"\\XFX"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedEscape', '\\XF'), - hl('DoubleQuotedBody', 'X'), - hl('DoubleQuote', '"'), - }) - - check_parsing('"\\xX"', { - -- 01234 - ast = { - 'DoubleQuotedString(val="xX"):0:0:"\\xX"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedUnknownEscape', '\\x'), - hl('DoubleQuotedBody', 'X'), - hl('DoubleQuote', '"'), - }) - - check_parsing('"\\XX"', { - -- 01234 - ast = { - 'DoubleQuotedString(val="XX"):0:0:"\\XX"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedUnknownEscape', '\\X'), - hl('DoubleQuotedBody', 'X'), - hl('DoubleQuote', '"'), - }) - - check_parsing('"\\uX"', { - -- 01234 - ast = { - 'DoubleQuotedString(val="uX"):0:0:"\\uX"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedUnknownEscape', '\\u'), - hl('DoubleQuotedBody', 'X'), - hl('DoubleQuote', '"'), - }) - - check_parsing('"\\UX"', { - -- 01234 - ast = { - 'DoubleQuotedString(val="UX"):0:0:"\\UX"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedUnknownEscape', '\\U'), - hl('DoubleQuotedBody', 'X'), - hl('DoubleQuote', '"'), - }) - - check_parsing('"\\x0X"', { - -- 012345 - ast = { - 'DoubleQuotedString(val="\\0X"):0:0:"\\x0X"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedEscape', '\\x0'), - hl('DoubleQuotedBody', 'X'), - hl('DoubleQuote', '"'), - }) - - check_parsing('"\\X0X"', { - -- 012345 - ast = { - 'DoubleQuotedString(val="\\0X"):0:0:"\\X0X"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedEscape', '\\X0'), - hl('DoubleQuotedBody', 'X'), - hl('DoubleQuote', '"'), - }) - - check_parsing('"\\u0X"', { - -- 012345 - ast = { - 'DoubleQuotedString(val="\\0X"):0:0:"\\u0X"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedEscape', '\\u0'), - hl('DoubleQuotedBody', 'X'), - hl('DoubleQuote', '"'), - }) - - check_parsing('"\\U0X"', { - -- 012345 - ast = { - 'DoubleQuotedString(val="\\0X"):0:0:"\\U0X"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedEscape', '\\U0'), - hl('DoubleQuotedBody', 'X'), - hl('DoubleQuote', '"'), - }) - - check_parsing('"\\x00X"', { - -- 0123456 - ast = { - 'DoubleQuotedString(val="\\0X"):0:0:"\\x00X"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedEscape', '\\x00'), - hl('DoubleQuotedBody', 'X'), - hl('DoubleQuote', '"'), - }) - - check_parsing('"\\X00X"', { - -- 0123456 - ast = { - 'DoubleQuotedString(val="\\0X"):0:0:"\\X00X"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedEscape', '\\X00'), - hl('DoubleQuotedBody', 'X'), - hl('DoubleQuote', '"'), - }) - - check_parsing('"\\u00X"', { - -- 0123456 - ast = { - 'DoubleQuotedString(val="\\0X"):0:0:"\\u00X"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedEscape', '\\u00'), - hl('DoubleQuotedBody', 'X'), - hl('DoubleQuote', '"'), - }) - - check_parsing('"\\U00X"', { - -- 0123456 - ast = { - 'DoubleQuotedString(val="\\0X"):0:0:"\\U00X"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedEscape', '\\U00'), - hl('DoubleQuotedBody', 'X'), - hl('DoubleQuote', '"'), - }) - - check_parsing('"\\u000X"', { - -- 01234567 - ast = { - 'DoubleQuotedString(val="\\0X"):0:0:"\\u000X"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedEscape', '\\u000'), - hl('DoubleQuotedBody', 'X'), - hl('DoubleQuote', '"'), - }) - - check_parsing('"\\U000X"', { - -- 01234567 - ast = { - 'DoubleQuotedString(val="\\0X"):0:0:"\\U000X"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedEscape', '\\U000'), - hl('DoubleQuotedBody', 'X'), - hl('DoubleQuote', '"'), - }) - - check_parsing('"\\u0000X"', { - -- 012345678 - ast = { - 'DoubleQuotedString(val="\\0X"):0:0:"\\u0000X"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedEscape', '\\u0000'), - hl('DoubleQuotedBody', 'X'), - hl('DoubleQuote', '"'), - }) - - check_parsing('"\\U0000X"', { - -- 012345678 - ast = { - 'DoubleQuotedString(val="\\0X"):0:0:"\\U0000X"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedEscape', '\\U0000'), - hl('DoubleQuotedBody', 'X'), - hl('DoubleQuote', '"'), - }) - - check_parsing('"\\U00000X"', { - -- 0123456789 - ast = { - 'DoubleQuotedString(val="\\0X"):0:0:"\\U00000X"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedEscape', '\\U00000'), - hl('DoubleQuotedBody', 'X'), - hl('DoubleQuote', '"'), - }) - - check_parsing('"\\U000000X"', { - -- 01234567890 - -- 0 1 - ast = { - 'DoubleQuotedString(val="\\0X"):0:0:"\\U000000X"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedEscape', '\\U000000'), - hl('DoubleQuotedBody', 'X'), - hl('DoubleQuote', '"'), - }) - - check_parsing('"\\U0000000X"', { - -- 012345678901 - -- 0 1 - ast = { - 'DoubleQuotedString(val="\\0X"):0:0:"\\U0000000X"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedEscape', '\\U0000000'), - hl('DoubleQuotedBody', 'X'), - hl('DoubleQuote', '"'), - }) - - check_parsing('"\\U00000000X"', { - -- 0123456789012 - -- 0 1 - ast = { - 'DoubleQuotedString(val="\\0X"):0:0:"\\U00000000X"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedEscape', '\\U00000000'), - hl('DoubleQuotedBody', 'X'), - hl('DoubleQuote', '"'), - }) - - check_parsing('"\\x000X"', { - -- 01234567 - ast = { - 'DoubleQuotedString(val="\\0000X"):0:0:"\\x000X"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedEscape', '\\x00'), - hl('DoubleQuotedBody', '0X'), - hl('DoubleQuote', '"'), - }) - - check_parsing('"\\X000X"', { - -- 01234567 - ast = { - 'DoubleQuotedString(val="\\0000X"):0:0:"\\X000X"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedEscape', '\\X00'), - hl('DoubleQuotedBody', '0X'), - hl('DoubleQuote', '"'), - }) - - check_parsing('"\\u00000X"', { - -- 0123456789 - ast = { - 'DoubleQuotedString(val="\\0000X"):0:0:"\\u00000X"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedEscape', '\\u0000'), - hl('DoubleQuotedBody', '0X'), - hl('DoubleQuote', '"'), - }) - - check_parsing('"\\U000000000X"', { - -- 01234567890123 - -- 0 1 - ast = { - 'DoubleQuotedString(val="\\0000X"):0:0:"\\U000000000X"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedEscape', '\\U00000000'), - hl('DoubleQuotedBody', '0X'), - hl('DoubleQuote', '"'), - }) - - check_parsing('"\\0"', { - -- 0123 - ast = { - 'DoubleQuotedString(val="\\0"):0:0:"\\0"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedEscape', '\\0'), - hl('DoubleQuote', '"'), - }) - - check_parsing('"\\00"', { - -- 01234 - ast = { - 'DoubleQuotedString(val="\\0"):0:0:"\\00"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedEscape', '\\00'), - hl('DoubleQuote', '"'), - }) - - check_parsing('"\\000"', { - -- 012345 - ast = { - 'DoubleQuotedString(val="\\0"):0:0:"\\000"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedEscape', '\\000'), - hl('DoubleQuote', '"'), - }) - - check_parsing('"\\0000"', { - -- 0123456 - ast = { - 'DoubleQuotedString(val="\\0000"):0:0:"\\0000"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedEscape', '\\000'), - hl('DoubleQuotedBody', '0'), - hl('DoubleQuote', '"'), - }) - - check_parsing('"\\8"', { - -- 0123 - ast = { - 'DoubleQuotedString(val="8"):0:0:"\\8"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedUnknownEscape', '\\8'), - hl('DoubleQuote', '"'), - }) - - check_parsing('"\\08"', { - -- 01234 - ast = { - 'DoubleQuotedString(val="\\0008"):0:0:"\\08"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedEscape', '\\0'), - hl('DoubleQuotedBody', '8'), - hl('DoubleQuote', '"'), - }) - - check_parsing('"\\008"', { - -- 012345 - ast = { - 'DoubleQuotedString(val="\\0008"):0:0:"\\008"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedEscape', '\\00'), - hl('DoubleQuotedBody', '8'), - hl('DoubleQuote', '"'), - }) - - check_parsing('"\\0008"', { - -- 0123456 - ast = { - 'DoubleQuotedString(val="\\0008"):0:0:"\\0008"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedEscape', '\\000'), - hl('DoubleQuotedBody', '8'), - hl('DoubleQuote', '"'), - }) - - check_parsing('"\\777"', { - -- 012345 - ast = { - 'DoubleQuotedString(val="\255"):0:0:"\\777"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedEscape', '\\777'), - hl('DoubleQuote', '"'), - }) - - check_parsing('"\\050"', { - -- 012345 - ast = { - 'DoubleQuotedString(val="\40"):0:0:"\\050"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedEscape', '\\050'), - hl('DoubleQuote', '"'), - }) - - check_parsing('"\\"', { - -- 012345 - ast = { - 'DoubleQuotedString(val="\\21"):0:0:"\\"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedEscape', '\\'), - hl('DoubleQuote', '"'), - }) - - check_parsing('"\\<', { - -- 012 - ast = { - 'DoubleQuotedString(val="<"):0:0:"\\<', - }, - err = { - arg = '"\\<', - msg = 'E114: Missing double quote: %.*s', - }, - }, { - hl('InvalidDoubleQuote', '"'), - hl('InvalidDoubleQuotedUnknownEscape', '\\<'), - }) - - check_parsing('"\\<"', { - -- 0123 - ast = { - 'DoubleQuotedString(val="<"):0:0:"\\<"', - }, - }, { - hl('DoubleQuote', '"'), - hl('DoubleQuotedUnknownEscape', '\\<'), - hl('DoubleQuote', '"'), - }) - - check_parsing('"\\ {b{3}: 4}[5]}()] += 6', { - -- 012345678901234567890123456 - -- 0 1 2 - ast = { - { - 'Assignment(Add):0:22: +=', - children = { - { - 'Subscript:0:1:[', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - { - 'Call:0:19:(', - children = { - { - 'Lambda(\\di):0:2:{', - children = { - { - 'Arrow:0:3:->', - children = { - { - 'Subscript:0:15:[', - children = { - { - 'DictLiteral(-di):0:5: {', - children = { - { - 'Colon:0:11::', - children = { - { - 'ComplexIdentifier:0:8:', - children = { - 'PlainIdentifier(scope=0,ident=b):0:7:b', - { - 'CurlyBracesIdentifier(--i):0:8:{', - children = { - 'Integer(val=3):0:9:3', - }, - }, - }, - }, - 'Integer(val=4):0:12: 4', - }, - }, - }, - }, - 'Integer(val=5):0:16:5', - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - 'Integer(val=6):0:25: 6', - }, - }, - }, - }, { - hl('IdentifierName', 'a'), - hl('SubscriptBracket', '['), - hl('Lambda', '{'), - hl('Arrow', '->'), - hl('Dict', '{', 1), - hl('IdentifierName', 'b'), - hl('Curly', '{'), - hl('Number', '3'), - hl('Curly', '}'), - hl('Colon', ':'), - hl('Number', '4', 1), - hl('Dict', '}'), - hl('SubscriptBracket', '['), - hl('Number', '5'), - hl('SubscriptBracket', ']'), - hl('Lambda', '}'), - hl('CallingParenthesis', '('), - hl('CallingParenthesis', ')'), - hl('SubscriptBracket', ']'), - hl('AssignmentWithAddition', '+=', 1), - hl('Number', '6', 1), - }) - - check_asgn_parsing('a{1}.2[{-> {b{3}: 4}[5]}()]', { - -- 012345678901234567890123456 - -- 0 1 2 - ast = { - { - 'Subscript:0:6:[', - children = { - { - 'ConcatOrSubscript:0:4:.', - children = { - { - 'ComplexIdentifier:0:1:', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - { - 'CurlyBracesIdentifier(--i):0:1:{', - children = { - 'Integer(val=1):0:2:1', - }, - }, - }, - }, - 'PlainKey(key=2):0:5:2', - }, - }, - { - 'Call:0:24:(', - children = { - { - 'Lambda(\\di):0:7:{', - children = { - { - 'Arrow:0:8:->', - children = { - { - 'Subscript:0:20:[', - children = { - { - 'DictLiteral(-di):0:10: {', - children = { - { - 'Colon:0:16::', - children = { - { - 'ComplexIdentifier:0:13:', - children = { - 'PlainIdentifier(scope=0,ident=b):0:12:b', - { - 'CurlyBracesIdentifier(--i):0:13:{', - children = { - 'Integer(val=3):0:14:3', - }, - }, - }, - }, - 'Integer(val=4):0:17: 4', - }, - }, - }, - }, - 'Integer(val=5):0:21:5', - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, { - hl('IdentifierName', 'a'), - hl('Curly', '{'), - hl('Number', '1'), - hl('Curly', '}'), - hl('ConcatOrSubscript', '.'), - hl('IdentifierKey', '2'), - hl('SubscriptBracket', '['), - hl('Lambda', '{'), - hl('Arrow', '->'), - hl('Dict', '{', 1), - hl('IdentifierName', 'b'), - hl('Curly', '{'), - hl('Number', '3'), - hl('Curly', '}'), - hl('Colon', ':'), - hl('Number', '4', 1), - hl('Dict', '}'), - hl('SubscriptBracket', '['), - hl('Number', '5'), - hl('SubscriptBracket', ']'), - hl('Lambda', '}'), - hl('CallingParenthesis', '('), - hl('CallingParenthesis', ')'), - hl('SubscriptBracket', ']'), - }) - - check_asgn_parsing('a', { - -- 0 - ast = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - }, - }, { - hl('IdentifierName', 'a'), - }) - - check_asgn_parsing('{a}', { - -- 012 - ast = { - { - 'CurlyBracesIdentifier(--i):0:0:{', - children = { - 'PlainIdentifier(scope=0,ident=a):0:1:a', - }, - }, - }, - }, { - hl('FigureBrace', '{'), - hl('IdentifierName', 'a'), - hl('Curly', '}'), - }) - - check_asgn_parsing('{a}b', { - -- 0123 - ast = { - { - 'ComplexIdentifier:0:3:', - children = { - { - 'CurlyBracesIdentifier(--i):0:0:{', - children = { - 'PlainIdentifier(scope=0,ident=a):0:1:a', - }, - }, - 'PlainIdentifier(scope=0,ident=b):0:3:b', - }, - }, - }, - }, { - hl('FigureBrace', '{'), - hl('IdentifierName', 'a'), - hl('Curly', '}'), - hl('IdentifierName', 'b'), - }) - - check_asgn_parsing('a{b}c', { - -- 01234 - ast = { - { - 'ComplexIdentifier:0:1:', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - { - 'ComplexIdentifier:0:4:', - children = { - { - 'CurlyBracesIdentifier(--i):0:1:{', - children = { - 'PlainIdentifier(scope=0,ident=b):0:2:b', - }, - }, - 'PlainIdentifier(scope=0,ident=c):0:4:c', - }, - }, - }, - }, - }, - }, { - hl('IdentifierName', 'a'), - hl('Curly', '{'), - hl('IdentifierName', 'b'), - hl('Curly', '}'), - hl('IdentifierName', 'c'), - }) - - check_asgn_parsing('a{b}c[0]', { - -- 01234567 - ast = { - { - 'Subscript:0:5:[', - children = { - { - 'ComplexIdentifier:0:1:', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - { - 'ComplexIdentifier:0:4:', - children = { - { - 'CurlyBracesIdentifier(--i):0:1:{', - children = { - 'PlainIdentifier(scope=0,ident=b):0:2:b', - }, - }, - 'PlainIdentifier(scope=0,ident=c):0:4:c', - }, - }, - }, - }, - 'Integer(val=0):0:6:0', - }, - }, - }, - }, { - hl('IdentifierName', 'a'), - hl('Curly', '{'), - hl('IdentifierName', 'b'), - hl('Curly', '}'), - hl('IdentifierName', 'c'), - hl('SubscriptBracket', '['), - hl('Number', '0'), - hl('SubscriptBracket', ']'), - }) - - check_asgn_parsing('a{b}c.0', { - -- 0123456 - ast = { - { - 'ConcatOrSubscript:0:5:.', - children = { - { - 'ComplexIdentifier:0:1:', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - { - 'ComplexIdentifier:0:4:', - children = { - { - 'CurlyBracesIdentifier(--i):0:1:{', - children = { - 'PlainIdentifier(scope=0,ident=b):0:2:b', - }, - }, - 'PlainIdentifier(scope=0,ident=c):0:4:c', - }, - }, - }, - }, - 'PlainKey(key=0):0:6:0', - }, - }, - }, - }, { - hl('IdentifierName', 'a'), - hl('Curly', '{'), - hl('IdentifierName', 'b'), - hl('Curly', '}'), - hl('IdentifierName', 'c'), - hl('ConcatOrSubscript', '.'), - hl('IdentifierKey', '0'), - }) - - check_asgn_parsing('[a{b}c[0].0]', { - -- 012345678901 - -- 0 1 - ast = { - { - 'ListLiteral:0:0:[', - children = { - { - 'ConcatOrSubscript:0:9:.', - children = { - { - 'Subscript:0:6:[', - children = { - { - 'ComplexIdentifier:0:2:', - children = { - 'PlainIdentifier(scope=0,ident=a):0:1:a', - { - 'ComplexIdentifier:0:5:', - children = { - { - 'CurlyBracesIdentifier(--i):0:2:{', - children = { - 'PlainIdentifier(scope=0,ident=b):0:3:b', - }, - }, - 'PlainIdentifier(scope=0,ident=c):0:5:c', - }, - }, - }, - }, - 'Integer(val=0):0:7:0', - }, - }, - 'PlainKey(key=0):0:10:0', - }, - }, - }, - }, - }, - }, { - hl('List', '['), - hl('IdentifierName', 'a'), - hl('Curly', '{'), - hl('IdentifierName', 'b'), - hl('Curly', '}'), - hl('IdentifierName', 'c'), - hl('SubscriptBracket', '['), - hl('Number', '0'), - hl('SubscriptBracket', ']'), - hl('ConcatOrSubscript', '.'), - hl('IdentifierKey', '0'), - hl('List', ']'), - }) - - check_asgn_parsing('{a}{b}', { - -- 012345 - ast = { - { - 'ComplexIdentifier:0:3:', - children = { - { - 'CurlyBracesIdentifier(--i):0:0:{', - children = { - 'PlainIdentifier(scope=0,ident=a):0:1:a', - }, - }, - { - 'CurlyBracesIdentifier(--i):0:3:{', - children = { - 'PlainIdentifier(scope=0,ident=b):0:4:b', - }, - }, - }, - }, - }, - }, { - hl('FigureBrace', '{'), - hl('IdentifierName', 'a'), - hl('Curly', '}'), - hl('Curly', '{'), - hl('IdentifierName', 'b'), - hl('Curly', '}'), - }) - - check_asgn_parsing('a.b{c}{d}', { - -- 012345678 - ast = { - { - 'OpMissing:0:3:', - children = { - { - 'ConcatOrSubscript:0:1:.', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - 'PlainKey(key=b):0:2:b', - }, - }, - { - 'ComplexIdentifier:0:6:', - children = { - { - 'CurlyBracesIdentifier(--i):0:3:{', - children = { - 'PlainIdentifier(scope=0,ident=c):0:4:c', - }, - }, - { - 'CurlyBracesIdentifier(--i):0:6:{', - children = { - 'PlainIdentifier(scope=0,ident=d):0:7:d', - }, - }, - }, - }, - }, - }, - }, - err = { - arg = '{c}{d}', - msg = 'E15: Missing operator: %.*s', - }, - }, { - hl('IdentifierName', 'a'), - hl('ConcatOrSubscript', '.'), - hl('IdentifierKey', 'b'), - hl('InvalidFigureBrace', '{'), - hl('IdentifierName', 'c'), - hl('Curly', '}'), - hl('Curly', '{'), - hl('IdentifierName', 'd'), - hl('Curly', '}'), - }) - - check_asgn_parsing('[a] = 1', { - -- 0123456 - ast = { - { - 'Assignment(Plain):0:3: =', - children = { - { - 'ListLiteral:0:0:[', - children = { - 'PlainIdentifier(scope=0,ident=a):0:1:a', - }, - }, - 'Integer(val=1):0:5: 1', - }, - }, - }, - }, { - hl('List', '['), - hl('IdentifierName', 'a'), - hl('List', ']'), - hl('PlainAssignment', '=', 1), - hl('Number', '1', 1), - }) - - check_asgn_parsing('[a[b], [c, [d, [e]]]] = 1', { - -- 0123456789012345678901234 - -- 0 1 2 - ast = { - { - 'Assignment(Plain):0:21: =', - children = { - { - 'ListLiteral:0:0:[', - children = { - { - 'Comma:0:5:,', - children = { - { - 'Subscript:0:2:[', - children = { - 'PlainIdentifier(scope=0,ident=a):0:1:a', - 'PlainIdentifier(scope=0,ident=b):0:3:b', - }, - }, - { - 'ListLiteral:0:6: [', - children = { - { - 'Comma:0:9:,', - children = { - 'PlainIdentifier(scope=0,ident=c):0:8:c', - { - 'ListLiteral:0:10: [', - children = { - { - 'Comma:0:13:,', - children = { - 'PlainIdentifier(scope=0,ident=d):0:12:d', - { - 'ListLiteral:0:14: [', - children = { - 'PlainIdentifier(scope=0,ident=e):0:16:e', - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - 'Integer(val=1):0:23: 1', - }, - }, - }, - err = { - arg = '[c, [d, [e]]]] = 1', - msg = 'E475: Nested lists not allowed when assigning: %.*s', - }, - }, { - hl('List', '['), - hl('IdentifierName', 'a'), - hl('SubscriptBracket', '['), - hl('IdentifierName', 'b'), - hl('SubscriptBracket', ']'), - hl('Comma', ','), - hl('InvalidList', '[', 1), - hl('IdentifierName', 'c'), - hl('Comma', ','), - hl('InvalidList', '[', 1), - hl('IdentifierName', 'd'), - hl('Comma', ','), - hl('InvalidList', '[', 1), - hl('IdentifierName', 'e'), - hl('List', ']'), - hl('List', ']'), - hl('List', ']'), - hl('List', ']'), - hl('PlainAssignment', '=', 1), - hl('Number', '1', 1), - }) - - check_asgn_parsing('$X += 1', { - -- 0123456 - ast = { - { - 'Assignment(Add):0:2: +=', - children = { - 'Environment(ident=X):0:0:$X', - 'Integer(val=1):0:5: 1', - }, - }, - }, - }, { - hl('EnvironmentSigil', '$'), - hl('EnvironmentName', 'X'), - hl('AssignmentWithAddition', '+=', 1), - hl('Number', '1', 1), - }) - - check_asgn_parsing('@a .= 1', { - -- 0123456 - ast = { - { - 'Assignment(Concat):0:2: .=', - children = { - 'Register(name=a):0:0:@a', - 'Integer(val=1):0:5: 1', - }, - }, - }, - }, { - hl('Register', '@a'), - hl('AssignmentWithConcatenation', '.=', 1), - hl('Number', '1', 1), - }) - - check_asgn_parsing('&option -= 1', { - -- 012345678901 - -- 0 1 - ast = { - { - 'Assignment(Subtract):0:7: -=', - children = { - 'Option(scope=0,ident=option):0:0:&option', - 'Integer(val=1):0:10: 1', - }, - }, - }, - }, { - hl('OptionSigil', '&'), - hl('OptionName', 'option'), - hl('AssignmentWithSubtraction', '-=', 1), - hl('Number', '1', 1), - }) - - check_asgn_parsing('[$X, @a, &l:option] = [1, 2, 3]', { - -- 0123456789012345678901234567890 - -- 0 1 2 3 - ast = { - { - 'Assignment(Plain):0:19: =', - children = { - { - 'ListLiteral:0:0:[', - children = { - { - 'Comma:0:3:,', - children = { - 'Environment(ident=X):0:1:$X', - { - 'Comma:0:7:,', - children = { - 'Register(name=a):0:4: @a', - 'Option(scope=l,ident=option):0:8: &l:option', - }, - }, - }, - }, - }, - }, - { - 'ListLiteral:0:21: [', - children = { - { - 'Comma:0:24:,', - children = { - 'Integer(val=1):0:23:1', - { - 'Comma:0:27:,', - children = { - 'Integer(val=2):0:25: 2', - 'Integer(val=3):0:28: 3', - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, { - hl('List', '['), - hl('EnvironmentSigil', '$'), - hl('EnvironmentName', 'X'), - hl('Comma', ','), - hl('Register', '@a', 1), - hl('Comma', ','), - hl('OptionSigil', '&', 1), - hl('OptionScope', 'l'), - hl('OptionScopeDelimiter', ':'), - hl('OptionName', 'option'), - hl('List', ']'), - hl('PlainAssignment', '=', 1), - hl('List', '[', 1), - hl('Number', '1'), - hl('Comma', ','), - hl('Number', '2', 1), - hl('Comma', ','), - hl('Number', '3', 1), - hl('List', ']'), - }) - end) - -- FIXME: Somehow make functional tests use the same code. Or, at least, - -- create an automated script which will do the import. + local function fmtn(typ, args, rest) + return ('%s(%s)%s'):format(typ, args, rest) + end + require('test.unit.viml.expressions.parser_tests')( + itp, _check_parsing, hl, fmtn) end) diff --git a/test/unit/viml/expressions/parser_tests.lua b/test/unit/viml/expressions/parser_tests.lua new file mode 100644 index 0000000000..f0c2723a38 --- /dev/null +++ b/test/unit/viml/expressions/parser_tests.lua @@ -0,0 +1,8185 @@ +local global_helpers = require('test.helpers') + +local REMOVE_THIS = global_helpers.REMOVE_THIS + +return function(itp, _check_parsing, hl, fmtn) + local function check_parsing(...) + return _check_parsing({flags={0, 1, 2, 3}, funcname='check_parsing'}, ...) + end + local function check_asgn_parsing(...) + return _check_parsing({ + flags={4, 5, 6, 7}, + funcname='check_asgn_parsing', + }, ...) + end + itp('works with + and @a', function() + check_parsing('@a', { + ast = { + 'Register(name=a):0:0:@a', + }, + }, { + hl('Register', '@a'), + }) + check_parsing('+@a', { + ast = { + { + 'UnaryPlus:0:0:+', + children = { + 'Register(name=a):0:1:@a', + }, + }, + }, + }, { + hl('UnaryPlus', '+'), + hl('Register', '@a'), + }) + check_parsing('@a+@b', { + ast = { + { + 'BinaryPlus:0:2:+', + children = { + 'Register(name=a):0:0:@a', + 'Register(name=b):0:3:@b', + }, + }, + }, + }, { + hl('Register', '@a'), + hl('BinaryPlus', '+'), + hl('Register', '@b'), + }) + check_parsing('@a+@b+@c', { + ast = { + { + 'BinaryPlus:0:5:+', + children = { + { + 'BinaryPlus:0:2:+', + children = { + 'Register(name=a):0:0:@a', + 'Register(name=b):0:3:@b', + }, + }, + 'Register(name=c):0:6:@c', + }, + }, + }, + }, { + hl('Register', '@a'), + hl('BinaryPlus', '+'), + hl('Register', '@b'), + hl('BinaryPlus', '+'), + hl('Register', '@c'), + }) + check_parsing('+@a+@b', { + ast = { + { + 'BinaryPlus:0:3:+', + children = { + { + 'UnaryPlus:0:0:+', + children = { + 'Register(name=a):0:1:@a', + }, + }, + 'Register(name=b):0:4:@b', + }, + }, + }, + }, { + hl('UnaryPlus', '+'), + hl('Register', '@a'), + hl('BinaryPlus', '+'), + hl('Register', '@b'), + }) + check_parsing('+@a++@b', { + ast = { + { + 'BinaryPlus:0:3:+', + children = { + { + 'UnaryPlus:0:0:+', + children = { + 'Register(name=a):0:1:@a', + }, + }, + { + 'UnaryPlus:0:4:+', + children = { + 'Register(name=b):0:5:@b', + }, + }, + }, + }, + }, + }, { + hl('UnaryPlus', '+'), + hl('Register', '@a'), + hl('BinaryPlus', '+'), + hl('UnaryPlus', '+'), + hl('Register', '@b'), + }) + check_parsing('@a@b', { + ast = { + { + 'OpMissing:0:2:', + children = { + 'Register(name=a):0:0:@a', + 'Register(name=b):0:2:@b', + }, + }, + }, + err = { + arg = '@b', + msg = 'E15: Missing operator: %.*s', + }, + }, { + hl('Register', '@a'), + hl('InvalidRegister', '@b'), + }, { + [1] = { + ast = { + len = 2, + err = REMOVE_THIS, + ast = { + 'Register(name=a):0:0:@a' + }, + }, + hl_fs = { + [2] = REMOVE_THIS, + }, + }, + }) + check_parsing(' @a \t @b', { + ast = { + { + 'OpMissing:0:3:', + children = { + 'Register(name=a):0:0: @a', + 'Register(name=b):0:3: \t @b', + }, + }, + }, + err = { + arg = '@b', + msg = 'E15: Missing operator: %.*s', + }, + }, { + hl('Register', '@a', 1), + hl('InvalidSpacing', ' \t '), + hl('Register', '@b'), + }, { + [1] = { + ast = { + len = 6, + err = REMOVE_THIS, + ast = { + 'Register(name=a):0:0: @a' + }, + }, + hl_fs = { + [2] = REMOVE_THIS, + [3] = REMOVE_THIS, + }, + }, + }) + check_parsing('+', { + ast = { + 'UnaryPlus:0:0:+', + }, + err = { + arg = '', + msg = 'E15: Expected value, got EOC: %.*s', + }, + }, { + hl('UnaryPlus', '+'), + }) + check_parsing(' +', { + ast = { + 'UnaryPlus:0:0: +', + }, + err = { + arg = '', + msg = 'E15: Expected value, got EOC: %.*s', + }, + }, { + hl('UnaryPlus', '+', 1), + }) + check_parsing('@a+ ', { + ast = { + { + 'BinaryPlus:0:2:+', + children = { + 'Register(name=a):0:0:@a', + }, + }, + }, + err = { + arg = '', + msg = 'E15: Expected value, got EOC: %.*s', + }, + }, { + hl('Register', '@a'), + hl('BinaryPlus', '+'), + }) + end) + itp('works with @a, + and parenthesis', function() + check_parsing('(@a)', { + ast = { + { + 'Nested:0:0:(', + children = { + 'Register(name=a):0:1:@a', + }, + }, + }, + }, { + hl('NestingParenthesis', '('), + hl('Register', '@a'), + hl('NestingParenthesis', ')'), + }) + check_parsing('()', { + ast = { + { + 'Nested:0:0:(', + children = { + 'Missing:0:1:', + }, + }, + }, + err = { + arg = ')', + msg = 'E15: Expected value, got parenthesis: %.*s', + }, + }, { + hl('NestingParenthesis', '('), + hl('InvalidNestingParenthesis', ')'), + }) + check_parsing(')', { + ast = { + { + 'Nested:0:0:', + children = { + 'Missing:0:0:', + }, + }, + }, + err = { + arg = ')', + msg = 'E15: Expected value, got parenthesis: %.*s', + }, + }, { + hl('InvalidNestingParenthesis', ')'), + }) + check_parsing('+)', { + ast = { + { + 'Nested:0:1:', + children = { + { + 'UnaryPlus:0:0:+', + children = { + 'Missing:0:1:', + }, + }, + }, + }, + }, + err = { + arg = ')', + msg = 'E15: Expected value, got parenthesis: %.*s', + }, + }, { + hl('UnaryPlus', '+'), + hl('InvalidNestingParenthesis', ')'), + }) + check_parsing('+@a(@b)', { + ast = { + { + 'UnaryPlus:0:0:+', + children = { + { + 'Call:0:3:(', + children = { + 'Register(name=a):0:1:@a', + 'Register(name=b):0:4:@b', + }, + }, + }, + }, + }, + }, { + hl('UnaryPlus', '+'), + hl('Register', '@a'), + hl('CallingParenthesis', '('), + hl('Register', '@b'), + hl('CallingParenthesis', ')'), + }) + check_parsing('@a+@b(@c)', { + ast = { + { + 'BinaryPlus:0:2:+', + children = { + 'Register(name=a):0:0:@a', + { + 'Call:0:5:(', + children = { + 'Register(name=b):0:3:@b', + 'Register(name=c):0:6:@c', + }, + }, + }, + }, + }, + }, { + hl('Register', '@a'), + hl('BinaryPlus', '+'), + hl('Register', '@b'), + hl('CallingParenthesis', '('), + hl('Register', '@c'), + hl('CallingParenthesis', ')'), + }) + check_parsing('@a()', { + ast = { + { + 'Call:0:2:(', + children = { + 'Register(name=a):0:0:@a', + }, + }, + }, + }, { + hl('Register', '@a'), + hl('CallingParenthesis', '('), + hl('CallingParenthesis', ')'), + }) + check_parsing('@a ()', { + ast = { + { + 'OpMissing:0:2:', + children = { + 'Register(name=a):0:0:@a', + { + 'Nested:0:2: (', + children = { + 'Missing:0:4:', + }, + }, + }, + }, + }, + err = { + arg = '()', + msg = 'E15: Missing operator: %.*s', + }, + }, { + hl('Register', '@a'), + hl('InvalidSpacing', ' '), + hl('NestingParenthesis', '('), + hl('InvalidNestingParenthesis', ')'), + }, { + [1] = { + ast = { + len = 3, + err = REMOVE_THIS, + ast = { + 'Register(name=a):0:0:@a', + }, + }, + hl_fs = { + [2] = REMOVE_THIS, + [3] = REMOVE_THIS, + [4] = REMOVE_THIS, + }, + }, + }) + check_parsing('@a + (@b)', { + ast = { + { + 'BinaryPlus:0:2: +', + children = { + 'Register(name=a):0:0:@a', + { + 'Nested:0:4: (', + children = { + 'Register(name=b):0:6:@b', + }, + }, + }, + }, + }, + }, { + hl('Register', '@a'), + hl('BinaryPlus', '+', 1), + hl('NestingParenthesis', '(', 1), + hl('Register', '@b'), + hl('NestingParenthesis', ')'), + }) + check_parsing('@a + (+@b)', { + ast = { + { + 'BinaryPlus:0:2: +', + children = { + 'Register(name=a):0:0:@a', + { + 'Nested:0:4: (', + children = { + { + 'UnaryPlus:0:6:+', + children = { + 'Register(name=b):0:7:@b', + }, + }, + }, + }, + }, + }, + }, + }, { + hl('Register', '@a'), + hl('BinaryPlus', '+', 1), + hl('NestingParenthesis', '(', 1), + hl('UnaryPlus', '+'), + hl('Register', '@b'), + hl('NestingParenthesis', ')'), + }) + check_parsing('@a + (@b + @c)', { + ast = { + { + 'BinaryPlus:0:2: +', + children = { + 'Register(name=a):0:0:@a', + { + 'Nested:0:4: (', + children = { + { + 'BinaryPlus:0:8: +', + children = { + 'Register(name=b):0:6:@b', + 'Register(name=c):0:10: @c', + }, + }, + }, + }, + }, + }, + }, + }, { + hl('Register', '@a'), + hl('BinaryPlus', '+', 1), + hl('NestingParenthesis', '(', 1), + hl('Register', '@b'), + hl('BinaryPlus', '+', 1), + hl('Register', '@c', 1), + hl('NestingParenthesis', ')'), + }) + check_parsing('(@a)+@b', { + ast = { + { + 'BinaryPlus:0:4:+', + children = { + { + 'Nested:0:0:(', + children = { + 'Register(name=a):0:1:@a', + }, + }, + 'Register(name=b):0:5:@b', + }, + }, + }, + }, { + hl('NestingParenthesis', '('), + hl('Register', '@a'), + hl('NestingParenthesis', ')'), + hl('BinaryPlus', '+'), + hl('Register', '@b'), + }) + check_parsing('@a+(@b)(@c)', { + -- 01234567890 + ast = { + { + 'BinaryPlus:0:2:+', + children = { + 'Register(name=a):0:0:@a', + { + 'Call:0:7:(', + children = { + { + 'Nested:0:3:(', + children = { 'Register(name=b):0:4:@b' }, + }, + 'Register(name=c):0:8:@c', + }, + }, + }, + }, + }, + }, { + hl('Register', '@a'), + hl('BinaryPlus', '+'), + hl('NestingParenthesis', '('), + hl('Register', '@b'), + hl('NestingParenthesis', ')'), + hl('CallingParenthesis', '('), + hl('Register', '@c'), + hl('CallingParenthesis', ')'), + }) + check_parsing('@a+((@b))(@c)', { + -- 01234567890123456890123456789 + -- 0 1 2 + ast = { + { + 'BinaryPlus:0:2:+', + children = { + 'Register(name=a):0:0:@a', + { + 'Call:0:9:(', + children = { + { + 'Nested:0:3:(', + children = { + { + 'Nested:0:4:(', + children = { 'Register(name=b):0:5:@b' } + }, + }, + }, + 'Register(name=c):0:10:@c', + }, + }, + }, + }, + }, + }, { + hl('Register', '@a'), + hl('BinaryPlus', '+'), + hl('NestingParenthesis', '('), + hl('NestingParenthesis', '('), + hl('Register', '@b'), + hl('NestingParenthesis', ')'), + hl('NestingParenthesis', ')'), + hl('CallingParenthesis', '('), + hl('Register', '@c'), + hl('CallingParenthesis', ')'), + }) + check_parsing('@a+((@b))+@c', { + -- 01234567890123456890123456789 + -- 0 1 2 + ast = { + { + 'BinaryPlus:0:9:+', + children = { + { + 'BinaryPlus:0:2:+', + children = { + 'Register(name=a):0:0:@a', + { + 'Nested:0:3:(', + children = { + { + 'Nested:0:4:(', + children = { 'Register(name=b):0:5:@b' } + }, + }, + }, + }, + }, + 'Register(name=c):0:10:@c', + }, + }, + }, + }, { + hl('Register', '@a'), + hl('BinaryPlus', '+'), + hl('NestingParenthesis', '('), + hl('NestingParenthesis', '('), + hl('Register', '@b'), + hl('NestingParenthesis', ')'), + hl('NestingParenthesis', ')'), + hl('BinaryPlus', '+'), + hl('Register', '@c'), + }) + check_parsing( + '@a + (@b + @c) + @d(@e) + (+@f) + ((+@g(@h))(@j)(@k))(@l)', {--[[ + | | | | | | | | || | | || | | ||| || || || || + 000000000011111111112222222222333333333344444444445555555 + 012345678901234567890123456789012345678901234567890123456 + ]] + ast = {{ + 'BinaryPlus:0:31: +', + children = { + { + 'BinaryPlus:0:23: +', + children = { + { + 'BinaryPlus:0:14: +', + children = { + { + 'BinaryPlus:0:2: +', + children = { + 'Register(name=a):0:0:@a', + { + 'Nested:0:4: (', + children = { + { + 'BinaryPlus:0:8: +', + children = { + 'Register(name=b):0:6:@b', + 'Register(name=c):0:10: @c', + }, + }, + }, + }, + }, + }, + { + 'Call:0:19:(', + children = { + 'Register(name=d):0:16: @d', + 'Register(name=e):0:20:@e', + }, + }, + }, + }, + { + 'Nested:0:25: (', + children = { + { + 'UnaryPlus:0:27:+', + children = { + 'Register(name=f):0:28:@f', + }, + }, + }, + }, + }, + }, + { + 'Call:0:53:(', + children = { + { + 'Nested:0:33: (', + children = { + { + 'Call:0:48:(', + children = { + { + 'Call:0:44:(', + children = { + { + 'Nested:0:35:(', + children = { + { + 'UnaryPlus:0:36:+', + children = { + { + 'Call:0:39:(', + children = { + 'Register(name=g):0:37:@g', + 'Register(name=h):0:40:@h', + }, + }, + }, + }, + }, + }, + 'Register(name=j):0:45:@j', + }, + }, + 'Register(name=k):0:49:@k', + }, + }, + }, + }, + 'Register(name=l):0:54:@l', + }, + }, + }, + }}, + }, { + hl('Register', '@a'), + hl('BinaryPlus', '+', 1), + hl('NestingParenthesis', '(', 1), + hl('Register', '@b'), + hl('BinaryPlus', '+', 1), + hl('Register', '@c', 1), + hl('NestingParenthesis', ')'), + hl('BinaryPlus', '+', 1), + hl('Register', '@d', 1), + hl('CallingParenthesis', '('), + hl('Register', '@e'), + hl('CallingParenthesis', ')'), + hl('BinaryPlus', '+', 1), + hl('NestingParenthesis', '(', 1), + hl('UnaryPlus', '+'), + hl('Register', '@f'), + hl('NestingParenthesis', ')'), + hl('BinaryPlus', '+', 1), + hl('NestingParenthesis', '(', 1), + hl('NestingParenthesis', '('), + hl('UnaryPlus', '+'), + hl('Register', '@g'), + hl('CallingParenthesis', '('), + hl('Register', '@h'), + hl('CallingParenthesis', ')'), + hl('NestingParenthesis', ')'), + hl('CallingParenthesis', '('), + hl('Register', '@j'), + hl('CallingParenthesis', ')'), + hl('CallingParenthesis', '('), + hl('Register', '@k'), + hl('CallingParenthesis', ')'), + hl('NestingParenthesis', ')'), + hl('CallingParenthesis', '('), + hl('Register', '@l'), + hl('CallingParenthesis', ')'), + }) + check_parsing('@a)', { + -- 012 + ast = { + { + 'Nested:0:2:', + children = { + 'Register(name=a):0:0:@a', + }, + }, + }, + err = { + arg = ')', + msg = 'E15: Unexpected closing parenthesis: %.*s', + }, + }, { + hl('Register', '@a'), + hl('InvalidNestingParenthesis', ')'), + }) + check_parsing('(@a', { + -- 012 + ast = { + { + 'Nested:0:0:(', + children = { + 'Register(name=a):0:1:@a', + }, + }, + }, + err = { + arg = '(@a', + msg = 'E110: Missing closing parenthesis for nested expression: %.*s', + }, + }, { + hl('NestingParenthesis', '('), + hl('Register', '@a'), + }) + check_parsing('@a(@b', { + -- 01234 + ast = { + { + 'Call:0:2:(', + children = { + 'Register(name=a):0:0:@a', + 'Register(name=b):0:3:@b', + }, + }, + }, + err = { + arg = '(@b', + msg = 'E116: Missing closing parenthesis for function call: %.*s', + }, + }, { + hl('Register', '@a'), + hl('CallingParenthesis', '('), + hl('Register', '@b'), + }) + check_parsing('@a(@b, @c, @d, @e)', { + -- 012345678901234567 + -- 0 1 + ast = { + { + 'Call:0:2:(', + children = { + 'Register(name=a):0:0:@a', + { + 'Comma:0:5:,', + children = { + 'Register(name=b):0:3:@b', + { + 'Comma:0:9:,', + children = { + 'Register(name=c):0:6: @c', + { + 'Comma:0:13:,', + children = { + 'Register(name=d):0:10: @d', + 'Register(name=e):0:14: @e', + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, { + hl('Register', '@a'), + hl('CallingParenthesis', '('), + hl('Register', '@b'), + hl('Comma', ','), + hl('Register', '@c', 1), + hl('Comma', ','), + hl('Register', '@d', 1), + hl('Comma', ','), + hl('Register', '@e', 1), + hl('CallingParenthesis', ')'), + }) + check_parsing('@a(@b(@c))', { + -- 01234567890123456789012345678901234567 + -- 0 1 2 3 + ast = { + { + 'Call:0:2:(', + children = { + 'Register(name=a):0:0:@a', + { + 'Call:0:5:(', + children = { + 'Register(name=b):0:3:@b', + 'Register(name=c):0:6:@c', + }, + }, + }, + }, + }, + }, { + hl('Register', '@a'), + hl('CallingParenthesis', '('), + hl('Register', '@b'), + hl('CallingParenthesis', '('), + hl('Register', '@c'), + hl('CallingParenthesis', ')'), + hl('CallingParenthesis', ')'), + }) + check_parsing('@a(@b(@c(@d(@e), @f(@g(@h), @i(@j)))))', { + -- 01234567890123456789012345678901234567 + -- 0 1 2 3 + ast = { + { + 'Call:0:2:(', + children = { + 'Register(name=a):0:0:@a', + { + 'Call:0:5:(', + children = { + 'Register(name=b):0:3:@b', + { + 'Call:0:8:(', + children = { + 'Register(name=c):0:6:@c', + { + 'Comma:0:15:,', + children = { + { + 'Call:0:11:(', + children = { + 'Register(name=d):0:9:@d', + 'Register(name=e):0:12:@e', + }, + }, + { + 'Call:0:19:(', + children = { + 'Register(name=f):0:16: @f', + { + 'Comma:0:26:,', + children = { + { + 'Call:0:22:(', + children = { + 'Register(name=g):0:20:@g', + 'Register(name=h):0:23:@h', + }, + }, + { + 'Call:0:30:(', + children = { + 'Register(name=i):0:27: @i', + 'Register(name=j):0:31:@j', + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, { + hl('Register', '@a'), + hl('CallingParenthesis', '('), + hl('Register', '@b'), + hl('CallingParenthesis', '('), + hl('Register', '@c'), + hl('CallingParenthesis', '('), + hl('Register', '@d'), + hl('CallingParenthesis', '('), + hl('Register', '@e'), + hl('CallingParenthesis', ')'), + hl('Comma', ','), + hl('Register', '@f', 1), + hl('CallingParenthesis', '('), + hl('Register', '@g'), + hl('CallingParenthesis', '('), + hl('Register', '@h'), + hl('CallingParenthesis', ')'), + hl('Comma', ','), + hl('Register', '@i', 1), + hl('CallingParenthesis', '('), + hl('Register', '@j'), + hl('CallingParenthesis', ')'), + hl('CallingParenthesis', ')'), + hl('CallingParenthesis', ')'), + hl('CallingParenthesis', ')'), + hl('CallingParenthesis', ')'), + }) + check_parsing('()()', { + -- 0123 + ast = { + { + 'Call:0:2:(', + children = { + { + 'Nested:0:0:(', + children = { + 'Missing:0:1:', + }, + }, + }, + }, + }, + err = { + arg = ')()', + msg = 'E15: Expected value, got parenthesis: %.*s', + }, + }, { + hl('NestingParenthesis', '('), + hl('InvalidNestingParenthesis', ')'), + hl('CallingParenthesis', '('), + hl('CallingParenthesis', ')'), + }) + check_parsing('(@a)()', { + -- 012345 + ast = { + { + 'Call:0:4:(', + children = { + { + 'Nested:0:0:(', + children = { + 'Register(name=a):0:1:@a', + }, + }, + }, + }, + }, + }, { + hl('NestingParenthesis', '('), + hl('Register', '@a'), + hl('NestingParenthesis', ')'), + hl('CallingParenthesis', '('), + hl('CallingParenthesis', ')'), + }) + check_parsing('(@a)(@b)', { + -- 01234567 + ast = { + { + 'Call:0:4:(', + children = { + { + 'Nested:0:0:(', + children = { + 'Register(name=a):0:1:@a', + }, + }, + 'Register(name=b):0:5:@b', + }, + }, + }, + }, { + hl('NestingParenthesis', '('), + hl('Register', '@a'), + hl('NestingParenthesis', ')'), + hl('CallingParenthesis', '('), + hl('Register', '@b'), + hl('CallingParenthesis', ')'), + }) + check_parsing('(@a) (@b)', { + -- 012345678 + ast = { + { + 'OpMissing:0:4:', + children = { + { + 'Nested:0:0:(', + children = { + 'Register(name=a):0:1:@a', + }, + }, + { + 'Nested:0:4: (', + children = { + 'Register(name=b):0:6:@b', + }, + }, + }, + }, + }, + err = { + arg = '(@b)', + msg = 'E15: Missing operator: %.*s', + }, + }, { + hl('NestingParenthesis', '('), + hl('Register', '@a'), + hl('NestingParenthesis', ')'), + hl('InvalidSpacing', ' '), + hl('NestingParenthesis', '('), + hl('Register', '@b'), + hl('NestingParenthesis', ')'), + }, { + [1] = { + ast = { + len = 5, + ast = { + { + 'Nested:0:0:(', + children = { + 'Register(name=a):0:1:@a', + REMOVE_THIS, + }, + }, + }, + err = REMOVE_THIS, + }, + hl_fs = { + [4] = REMOVE_THIS, + [5] = REMOVE_THIS, + [6] = REMOVE_THIS, + [7] = REMOVE_THIS, + }, + }, + }) + end) + itp('works with variable names, including curly braces ones', function() + check_parsing('var', { + ast = { + 'PlainIdentifier(scope=0,ident=var):0:0:var', + }, + }, { + hl('IdentifierName', 'var'), + }) + check_parsing('g:var', { + ast = { + 'PlainIdentifier(scope=g,ident=var):0:0:g:var', + }, + }, { + hl('IdentifierScope', 'g'), + hl('IdentifierScopeDelimiter', ':'), + hl('IdentifierName', 'var'), + }) + check_parsing('g:', { + ast = { + 'PlainIdentifier(scope=g,ident=):0:0:g:', + }, + }, { + hl('IdentifierScope', 'g'), + hl('IdentifierScopeDelimiter', ':'), + }) + check_parsing('{a}', { + -- 012 + ast = { + { + fmtn('CurlyBracesIdentifier', '-di', ':0:0:{'), + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + }, + }, + }, + }, { + hl('Curly', '{'), + hl('IdentifierName', 'a'), + hl('Curly', '}'), + }) + check_parsing('{a:b}', { + -- 012 + ast = { + { + fmtn('CurlyBracesIdentifier', '-di', ':0:0:{'), + children = { + 'PlainIdentifier(scope=a,ident=b):0:1:a:b', + }, + }, + }, + }, { + hl('Curly', '{'), + hl('IdentifierScope', 'a'), + hl('IdentifierScopeDelimiter', ':'), + hl('IdentifierName', 'b'), + hl('Curly', '}'), + }) + check_parsing('{a:@b}', { + -- 012345 + ast = { + { + fmtn('CurlyBracesIdentifier', '-di', ':0:0:{'), + children = { + { + 'OpMissing:0:3:', + children={ + 'PlainIdentifier(scope=a,ident=):0:1:a:', + 'Register(name=b):0:3:@b', + }, + }, + }, + }, + }, + err = { + arg = '@b}', + msg = 'E15: Missing operator: %.*s', + }, + }, { + hl('Curly', '{'), + hl('IdentifierScope', 'a'), + hl('IdentifierScopeDelimiter', ':'), + hl('InvalidRegister', '@b'), + hl('Curly', '}'), + }) + check_parsing('{@a}', { + ast = { + { + fmtn('CurlyBracesIdentifier', '-di', ':0:0:{'), + children = { + 'Register(name=a):0:1:@a', + }, + }, + }, + }, { + hl('Curly', '{'), + hl('Register', '@a'), + hl('Curly', '}'), + }) + check_parsing('{@a}{@b}', { + -- 01234567 + ast = { + { + 'ComplexIdentifier:0:4:', + children = { + { + fmtn('CurlyBracesIdentifier', '-di', ':0:0:{'), + children = { + 'Register(name=a):0:1:@a', + }, + }, + { + fmtn('CurlyBracesIdentifier', '--i', ':0:4:{'), + children = { + 'Register(name=b):0:5:@b', + }, + }, + }, + }, + }, + }, { + hl('Curly', '{'), + hl('Register', '@a'), + hl('Curly', '}'), + hl('Curly', '{'), + hl('Register', '@b'), + hl('Curly', '}'), + }) + check_parsing('g:{@a}', { + -- 01234567 + ast = { + { + 'ComplexIdentifier:0:2:', + children = { + 'PlainIdentifier(scope=g,ident=):0:0:g:', + { + fmtn('CurlyBracesIdentifier', '--i', ':0:2:{'), + children = { + 'Register(name=a):0:3:@a', + }, + }, + }, + }, + }, + }, { + hl('IdentifierScope', 'g'), + hl('IdentifierScopeDelimiter', ':'), + hl('Curly', '{'), + hl('Register', '@a'), + hl('Curly', '}'), + }) + check_parsing('{@a}_test', { + -- 012345678 + ast = { + { + 'ComplexIdentifier:0:4:', + children = { + { + fmtn('CurlyBracesIdentifier', '-di', ':0:0:{'), + children = { + 'Register(name=a):0:1:@a', + }, + }, + 'PlainIdentifier(scope=0,ident=_test):0:4:_test', + }, + }, + }, + }, { + hl('Curly', '{'), + hl('Register', '@a'), + hl('Curly', '}'), + hl('IdentifierName', '_test'), + }) + check_parsing('g:{@a}_test', { + -- 01234567890 + ast = { + { + 'ComplexIdentifier:0:2:', + children = { + 'PlainIdentifier(scope=g,ident=):0:0:g:', + { + 'ComplexIdentifier:0:6:', + children = { + { + fmtn('CurlyBracesIdentifier', '--i', ':0:2:{'), + children = { + 'Register(name=a):0:3:@a', + }, + }, + 'PlainIdentifier(scope=0,ident=_test):0:6:_test', + }, + }, + }, + }, + }, + }, { + hl('IdentifierScope', 'g'), + hl('IdentifierScopeDelimiter', ':'), + hl('Curly', '{'), + hl('Register', '@a'), + hl('Curly', '}'), + hl('IdentifierName', '_test'), + }) + check_parsing('g:{@a}_test()', { + -- 0123456789012 + ast = { + { + 'Call:0:11:(', + children = { + { + 'ComplexIdentifier:0:2:', + children = { + 'PlainIdentifier(scope=g,ident=):0:0:g:', + { + 'ComplexIdentifier:0:6:', + children = { + { + fmtn('CurlyBracesIdentifier', '--i', ':0:2:{'), + children = { + 'Register(name=a):0:3:@a', + }, + }, + 'PlainIdentifier(scope=0,ident=_test):0:6:_test', + }, + }, + }, + }, + }, + }, + }, + }, { + hl('IdentifierScope', 'g'), + hl('IdentifierScopeDelimiter', ':'), + hl('Curly', '{'), + hl('Register', '@a'), + hl('Curly', '}'), + hl('IdentifierName', '_test'), + hl('CallingParenthesis', '('), + hl('CallingParenthesis', ')'), + }) + check_parsing('{@a} ()', { + -- 0123456789012 + ast = { + { + 'Call:0:4: (', + children = { + { + fmtn('CurlyBracesIdentifier', '-di', ':0:0:{'), + children = { + 'Register(name=a):0:1:@a', + }, + }, + }, + }, + }, + }, { + hl('Curly', '{'), + hl('Register', '@a'), + hl('Curly', '}'), + hl('CallingParenthesis', '(', 1), + hl('CallingParenthesis', ')'), + }) + check_parsing('g:{@a} ()', { + -- 0123456789012 + ast = { + { + 'Call:0:6: (', + children = { + { + 'ComplexIdentifier:0:2:', + children = { + 'PlainIdentifier(scope=g,ident=):0:0:g:', + { + fmtn('CurlyBracesIdentifier', '--i', ':0:2:{'), + children = { + 'Register(name=a):0:3:@a', + }, + }, + }, + }, + }, + }, + }, + }, { + hl('IdentifierScope', 'g'), + hl('IdentifierScopeDelimiter', ':'), + hl('Curly', '{'), + hl('Register', '@a'), + hl('Curly', '}'), + hl('CallingParenthesis', '(', 1), + hl('CallingParenthesis', ')'), + }) + check_parsing('{@a', { + -- 012 + ast = { + { + fmtn('UnknownFigure', '-di', ':0:0:{'), + children = { + 'Register(name=a):0:1:@a', + }, + }, + }, + err = { + arg = '{@a', + msg = 'E15: Missing closing figure brace: %.*s', + }, + }, { + hl('FigureBrace', '{'), + hl('Register', '@a'), + }) + check_parsing('a ()', { + -- 0123 + ast = { + { + 'Call:0:1: (', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('CallingParenthesis', '(', 1), + hl('CallingParenthesis', ')'), + }) + end) + itp('works with lambdas and dictionaries', function() + check_parsing('{}', { + ast = { + fmtn('DictLiteral', '-di', ':0:0:{'), + }, + }, { + hl('Dict', '{'), + hl('Dict', '}'), + }) + check_parsing('{->@a}', { + ast = { + { + fmtn('Lambda', '\\di', ':0:0:{'), + children = { + { + 'Arrow:0:1:->', + children = { + 'Register(name=a):0:3:@a', + }, + }, + }, + }, + }, + }, { + hl('Lambda', '{'), + hl('Arrow', '->'), + hl('Register', '@a'), + hl('Lambda', '}'), + }) + check_parsing('{->@a+@b}', { + -- 012345678 + ast = { + { + fmtn('Lambda', '\\di', ':0:0:{'), + children = { + { + 'Arrow:0:1:->', + children = { + { + 'BinaryPlus:0:5:+', + children = { + 'Register(name=a):0:3:@a', + 'Register(name=b):0:6:@b', + }, + }, + }, + }, + }, + }, + }, + }, { + hl('Lambda', '{'), + hl('Arrow', '->'), + hl('Register', '@a'), + hl('BinaryPlus', '+'), + hl('Register', '@b'), + hl('Lambda', '}'), + }) + check_parsing('{a->@a}', { + -- 012345678 + ast = { + { + fmtn('Lambda', '\\di', ':0:0:{'), + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + { + 'Arrow:0:2:->', + children = { + 'Register(name=a):0:4:@a', + }, + }, + }, + }, + }, + }, { + hl('Lambda', '{'), + hl('IdentifierName', 'a'), + hl('Arrow', '->'), + hl('Register', '@a'), + hl('Lambda', '}'), + }) + check_parsing('{a,b->@a}', { + -- 012345678 + ast = { + { + fmtn('Lambda', '\\di', ':0:0:{'), + children = { + { + 'Comma:0:2:,', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + 'PlainIdentifier(scope=0,ident=b):0:3:b', + }, + }, + { + 'Arrow:0:4:->', + children = { + 'Register(name=a):0:6:@a', + }, + }, + }, + }, + }, + }, { + hl('Lambda', '{'), + hl('IdentifierName', 'a'), + hl('Comma', ','), + hl('IdentifierName', 'b'), + hl('Arrow', '->'), + hl('Register', '@a'), + hl('Lambda', '}'), + }) + check_parsing('{a,b,c->@a}', { + -- 01234567890 + ast = { + { + fmtn('Lambda', '\\di', ':0:0:{'), + children = { + { + 'Comma:0:2:,', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + { + 'Comma:0:4:,', + children = { + 'PlainIdentifier(scope=0,ident=b):0:3:b', + 'PlainIdentifier(scope=0,ident=c):0:5:c', + }, + }, + }, + }, + { + 'Arrow:0:6:->', + children = { + 'Register(name=a):0:8:@a', + }, + }, + }, + }, + }, + }, { + hl('Lambda', '{'), + hl('IdentifierName', 'a'), + hl('Comma', ','), + hl('IdentifierName', 'b'), + hl('Comma', ','), + hl('IdentifierName', 'c'), + hl('Arrow', '->'), + hl('Register', '@a'), + hl('Lambda', '}'), + }) + check_parsing('{a,b,c,d->@a}', { + -- 0123456789012 + ast = { + { + fmtn('Lambda', '\\di', ':0:0:{'), + children = { + { + 'Comma:0:2:,', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + { + 'Comma:0:4:,', + children = { + 'PlainIdentifier(scope=0,ident=b):0:3:b', + { + 'Comma:0:6:,', + children = { + 'PlainIdentifier(scope=0,ident=c):0:5:c', + 'PlainIdentifier(scope=0,ident=d):0:7:d', + }, + }, + }, + }, + }, + }, + { + 'Arrow:0:8:->', + children = { + 'Register(name=a):0:10:@a', + }, + }, + }, + }, + }, + }, { + hl('Lambda', '{'), + hl('IdentifierName', 'a'), + hl('Comma', ','), + hl('IdentifierName', 'b'), + hl('Comma', ','), + hl('IdentifierName', 'c'), + hl('Comma', ','), + hl('IdentifierName', 'd'), + hl('Arrow', '->'), + hl('Register', '@a'), + hl('Lambda', '}'), + }) + check_parsing('{a,b,c,d,->@a}', { + -- 01234567890123 + ast = { + { + fmtn('Lambda', '\\di', ':0:0:{'), + children = { + { + 'Comma:0:2:,', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + { + 'Comma:0:4:,', + children = { + 'PlainIdentifier(scope=0,ident=b):0:3:b', + { + 'Comma:0:6:,', + children = { + 'PlainIdentifier(scope=0,ident=c):0:5:c', + { + 'Comma:0:8:,', + children = { + 'PlainIdentifier(scope=0,ident=d):0:7:d', + }, + }, + }, + }, + }, + }, + }, + }, + { + 'Arrow:0:9:->', + children = { + 'Register(name=a):0:11:@a', + }, + }, + }, + }, + }, + }, { + hl('Lambda', '{'), + hl('IdentifierName', 'a'), + hl('Comma', ','), + hl('IdentifierName', 'b'), + hl('Comma', ','), + hl('IdentifierName', 'c'), + hl('Comma', ','), + hl('IdentifierName', 'd'), + hl('Comma', ','), + hl('Arrow', '->'), + hl('Register', '@a'), + hl('Lambda', '}'), + }) + check_parsing('{a,b->{c,d->{e,f->@a}}}', { + -- 01234567890123456789012 + -- 0 1 2 + ast = { + { + fmtn('Lambda', '\\di', ':0:0:{'), + children = { + { + 'Comma:0:2:,', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + 'PlainIdentifier(scope=0,ident=b):0:3:b', + }, + }, + { + 'Arrow:0:4:->', + children = { + { + fmtn('Lambda', '\\di', ':0:6:{'), + children = { + { + 'Comma:0:8:,', + children = { + 'PlainIdentifier(scope=0,ident=c):0:7:c', + 'PlainIdentifier(scope=0,ident=d):0:9:d', + }, + }, + { + 'Arrow:0:10:->', + children = { + { + fmtn('Lambda', '\\di', ':0:12:{'), + children = { + { + 'Comma:0:14:,', + children = { + 'PlainIdentifier(scope=0,ident=e):0:13:e', + 'PlainIdentifier(scope=0,ident=f):0:15:f', + }, + }, + { + 'Arrow:0:16:->', + children = { + 'Register(name=a):0:18:@a', + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, { + hl('Lambda', '{'), + hl('IdentifierName', 'a'), + hl('Comma', ','), + hl('IdentifierName', 'b'), + hl('Arrow', '->'), + hl('Lambda', '{'), + hl('IdentifierName', 'c'), + hl('Comma', ','), + hl('IdentifierName', 'd'), + hl('Arrow', '->'), + hl('Lambda', '{'), + hl('IdentifierName', 'e'), + hl('Comma', ','), + hl('IdentifierName', 'f'), + hl('Arrow', '->'), + hl('Register', '@a'), + hl('Lambda', '}'), + hl('Lambda', '}'), + hl('Lambda', '}'), + }) + check_parsing('{a,b->c,d}', { + -- 0123456789 + ast = { + { + fmtn('Lambda', '\\di', ':0:0:{'), + children = { + { + 'Comma:0:2:,', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + 'PlainIdentifier(scope=0,ident=b):0:3:b', + }, + }, + { + 'Arrow:0:4:->', + children = { + { + 'Comma:0:7:,', + children = { + 'PlainIdentifier(scope=0,ident=c):0:6:c', + 'PlainIdentifier(scope=0,ident=d):0:8:d', + }, + }, + }, + }, + }, + }, + }, + err = { + arg = ',d}', + msg = 'E15: Comma outside of call, lambda or literal: %.*s', + }, + }, { + hl('Lambda', '{'), + hl('IdentifierName', 'a'), + hl('Comma', ','), + hl('IdentifierName', 'b'), + hl('Arrow', '->'), + hl('IdentifierName', 'c'), + hl('InvalidComma', ','), + hl('IdentifierName', 'd'), + hl('Lambda', '}'), + }) + check_parsing('a,b,c,d', { + -- 0123456789 + ast = { + { + 'Comma:0:1:,', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'Comma:0:3:,', + children = { + 'PlainIdentifier(scope=0,ident=b):0:2:b', + { + 'Comma:0:5:,', + children = { + 'PlainIdentifier(scope=0,ident=c):0:4:c', + 'PlainIdentifier(scope=0,ident=d):0:6:d', + }, + }, + }, + }, + }, + }, + }, + err = { + arg = ',b,c,d', + msg = 'E15: Comma outside of call, lambda or literal: %.*s', + }, + }, { + hl('IdentifierName', 'a'), + hl('InvalidComma', ','), + hl('IdentifierName', 'b'), + hl('InvalidComma', ','), + hl('IdentifierName', 'c'), + hl('InvalidComma', ','), + hl('IdentifierName', 'd'), + }) + check_parsing('a,b,c,d,', { + -- 0123456789 + ast = { + { + 'Comma:0:1:,', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'Comma:0:3:,', + children = { + 'PlainIdentifier(scope=0,ident=b):0:2:b', + { + 'Comma:0:5:,', + children = { + 'PlainIdentifier(scope=0,ident=c):0:4:c', + { + 'Comma:0:7:,', + children = { + 'PlainIdentifier(scope=0,ident=d):0:6:d', + }, + }, + }, + }, + }, + }, + }, + }, + }, + err = { + arg = ',b,c,d,', + msg = 'E15: Comma outside of call, lambda or literal: %.*s', + }, + }, { + hl('IdentifierName', 'a'), + hl('InvalidComma', ','), + hl('IdentifierName', 'b'), + hl('InvalidComma', ','), + hl('IdentifierName', 'c'), + hl('InvalidComma', ','), + hl('IdentifierName', 'd'), + hl('InvalidComma', ','), + }) + check_parsing(',', { + -- 0123456789 + ast = { + { + 'Comma:0:0:,', + children = { + 'Missing:0:0:', + }, + }, + }, + err = { + arg = ',', + msg = 'E15: Expected value, got comma: %.*s', + }, + }, { + hl('InvalidComma', ','), + }) + check_parsing('{,a->@a}', { + -- 0123456789 + ast = { + { + fmtn('CurlyBracesIdentifier', '-di', ':0:0:{'), + children = { + { + 'Arrow:0:3:->', + children = { + { + 'Comma:0:1:,', + children = { + 'Missing:0:1:', + 'PlainIdentifier(scope=0,ident=a):0:2:a', + }, + }, + 'Register(name=a):0:5:@a', + }, + }, + }, + }, + }, + err = { + arg = ',a->@a}', + msg = 'E15: Expected value, got comma: %.*s', + }, + }, { + hl('Curly', '{'), + hl('InvalidComma', ','), + hl('IdentifierName', 'a'), + hl('InvalidArrow', '->'), + hl('Register', '@a'), + hl('Curly', '}'), + }) + check_parsing('}', { + -- 0123456789 + ast = { + fmtn('UnknownFigure', '---', ':0:0:'), + }, + err = { + arg = '}', + msg = 'E15: Unexpected closing figure brace: %.*s', + }, + }, { + hl('InvalidFigureBrace', '}'), + }) + check_parsing('{->}', { + -- 0123456789 + ast = { + { + fmtn('Lambda', '\\di', ':0:0:{'), + children = { + 'Arrow:0:1:->', + }, + }, + }, + err = { + arg = '}', + msg = 'E15: Expected value, got closing figure brace: %.*s', + }, + }, { + hl('Lambda', '{'), + hl('Arrow', '->'), + hl('InvalidLambda', '}'), + }) + check_parsing('{a,b}', { + -- 0123456789 + ast = { + { + fmtn('Lambda', '-di', ':0:0:{'), + children = { + { + 'Comma:0:2:,', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + 'PlainIdentifier(scope=0,ident=b):0:3:b', + }, + }, + }, + }, + }, + err = { + arg = '}', + msg = 'E15: Expected lambda arguments list or arrow: %.*s', + }, + }, { + hl('Lambda', '{'), + hl('IdentifierName', 'a'), + hl('Comma', ','), + hl('IdentifierName', 'b'), + hl('InvalidLambda', '}'), + }) + check_parsing('{a,}', { + -- 0123456789 + ast = { + { + fmtn('Lambda', '-di', ':0:0:{'), + children = { + { + 'Comma:0:2:,', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + }, + }, + }, + }, + }, + err = { + arg = '}', + msg = 'E15: Expected lambda arguments list or arrow: %.*s', + }, + }, { + hl('Lambda', '{'), + hl('IdentifierName', 'a'), + hl('Comma', ','), + hl('InvalidLambda', '}'), + }) + check_parsing('{@a:@b}', { + -- 0123456789 + ast = { + { + fmtn('DictLiteral', '-di', ':0:0:{'), + children = { + { + 'Colon:0:3::', + children = { + 'Register(name=a):0:1:@a', + 'Register(name=b):0:4:@b', + }, + }, + }, + }, + }, + }, { + hl('Dict', '{'), + hl('Register', '@a'), + hl('Colon', ':'), + hl('Register', '@b'), + hl('Dict', '}'), + }) + check_parsing('{@a:@b,@c:@d}', { + -- 0123456789012 + -- 0 1 + ast = { + { + fmtn('DictLiteral', '-di', ':0:0:{'), + children = { + { + 'Comma:0:6:,', + children = { + { + 'Colon:0:3::', + children = { + 'Register(name=a):0:1:@a', + 'Register(name=b):0:4:@b', + }, + }, + { + 'Colon:0:9::', + children = { + 'Register(name=c):0:7:@c', + 'Register(name=d):0:10:@d', + }, + }, + }, + }, + }, + }, + }, + }, { + hl('Dict', '{'), + hl('Register', '@a'), + hl('Colon', ':'), + hl('Register', '@b'), + hl('Comma', ','), + hl('Register', '@c'), + hl('Colon', ':'), + hl('Register', '@d'), + hl('Dict', '}'), + }) + check_parsing('{@a:@b,@c:@d,@e:@f,}', { + -- 01234567890123456789 + -- 0 1 + ast = { + { + fmtn('DictLiteral', '-di', ':0:0:{'), + children = { + { + 'Comma:0:6:,', + children = { + { + 'Colon:0:3::', + children = { + 'Register(name=a):0:1:@a', + 'Register(name=b):0:4:@b', + }, + }, + { + 'Comma:0:12:,', + children = { + { + 'Colon:0:9::', + children = { + 'Register(name=c):0:7:@c', + 'Register(name=d):0:10:@d', + }, + }, + { + 'Comma:0:18:,', + children = { + { + 'Colon:0:15::', + children = { + 'Register(name=e):0:13:@e', + 'Register(name=f):0:16:@f', + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, { + hl('Dict', '{'), + hl('Register', '@a'), + hl('Colon', ':'), + hl('Register', '@b'), + hl('Comma', ','), + hl('Register', '@c'), + hl('Colon', ':'), + hl('Register', '@d'), + hl('Comma', ','), + hl('Register', '@e'), + hl('Colon', ':'), + hl('Register', '@f'), + hl('Comma', ','), + hl('Dict', '}'), + }) + check_parsing('{@a:@b,@c:@d,@e:@f,@g:}', { + -- 01234567890123456789012 + -- 0 1 2 + ast = { + { + fmtn('DictLiteral', '-di', ':0:0:{'), + children = { + { + 'Comma:0:6:,', + children = { + { + 'Colon:0:3::', + children = { + 'Register(name=a):0:1:@a', + 'Register(name=b):0:4:@b', + }, + }, + { + 'Comma:0:12:,', + children = { + { + 'Colon:0:9::', + children = { + 'Register(name=c):0:7:@c', + 'Register(name=d):0:10:@d', + }, + }, + { + 'Comma:0:18:,', + children = { + { + 'Colon:0:15::', + children = { + 'Register(name=e):0:13:@e', + 'Register(name=f):0:16:@f', + }, + }, + { + 'Colon:0:21::', + children = { + 'Register(name=g):0:19:@g', + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + err = { + arg = '}', + msg = 'E15: Expected value, got closing figure brace: %.*s', + }, + }, { + hl('Dict', '{'), + hl('Register', '@a'), + hl('Colon', ':'), + hl('Register', '@b'), + hl('Comma', ','), + hl('Register', '@c'), + hl('Colon', ':'), + hl('Register', '@d'), + hl('Comma', ','), + hl('Register', '@e'), + hl('Colon', ':'), + hl('Register', '@f'), + hl('Comma', ','), + hl('Register', '@g'), + hl('Colon', ':'), + hl('InvalidDict', '}'), + }) + check_parsing('{@a:@b,}', { + -- 01234567890123 + -- 0 1 + ast = { + { + fmtn('DictLiteral', '-di', ':0:0:{'), + children = { + { + 'Comma:0:6:,', + children = { + { + 'Colon:0:3::', + children = { + 'Register(name=a):0:1:@a', + 'Register(name=b):0:4:@b', + }, + }, + }, + }, + }, + }, + }, + }, { + hl('Dict', '{'), + hl('Register', '@a'), + hl('Colon', ':'), + hl('Register', '@b'), + hl('Comma', ','), + hl('Dict', '}'), + }) + check_parsing('{({f -> g})(@h)(@i)}', { + -- 01234567890123456789 + -- 0 1 + ast = { + { + fmtn('CurlyBracesIdentifier', '-di', ':0:0:{'), + children = { + { + 'Call:0:15:(', + children = { + { + 'Call:0:11:(', + children = { + { + 'Nested:0:1:(', + children = { + { + fmtn('Lambda', '\\di', ':0:2:{'), + children = { + 'PlainIdentifier(scope=0,ident=f):0:3:f', + { + 'Arrow:0:4: ->', + children = { + 'PlainIdentifier(scope=0,ident=g):0:7: g', + }, + }, + }, + }, + }, + }, + 'Register(name=h):0:12:@h', + }, + }, + 'Register(name=i):0:16:@i', + }, + }, + }, + }, + }, + }, { + hl('Curly', '{'), + hl('NestingParenthesis', '('), + hl('Lambda', '{'), + hl('IdentifierName', 'f'), + hl('Arrow', '->', 1), + hl('IdentifierName', 'g', 1), + hl('Lambda', '}'), + hl('NestingParenthesis', ')'), + hl('CallingParenthesis', '('), + hl('Register', '@h'), + hl('CallingParenthesis', ')'), + hl('CallingParenthesis', '('), + hl('Register', '@i'), + hl('CallingParenthesis', ')'), + hl('Curly', '}'), + }) + check_parsing('a:{b()}c', { + -- 01234567 + ast = { + { + 'ComplexIdentifier:0:2:', + children = { + 'PlainIdentifier(scope=a,ident=):0:0:a:', + { + 'ComplexIdentifier:0:7:', + children = { + { + fmtn('CurlyBracesIdentifier', '--i', ':0:2:{'), + children = { + { + 'Call:0:4:(', + children = { + 'PlainIdentifier(scope=0,ident=b):0:3:b', + }, + }, + }, + }, + 'PlainIdentifier(scope=0,ident=c):0:7:c', + }, + }, + }, + }, + }, + }, { + hl('IdentifierScope', 'a'), + hl('IdentifierScopeDelimiter', ':'), + hl('Curly', '{'), + hl('IdentifierName', 'b'), + hl('CallingParenthesis', '('), + hl('CallingParenthesis', ')'), + hl('Curly', '}'), + hl('IdentifierName', 'c'), + }) + check_parsing('a:{{b, c -> @d + @e + ({f -> g})(@h)}(@i)}j', { + -- 01234567890123456789012345678901234567890123456 + -- 0 1 2 3 4 + ast = { + { + 'ComplexIdentifier:0:2:', + children = { + 'PlainIdentifier(scope=a,ident=):0:0:a:', + { + 'ComplexIdentifier:0:42:', + children = { + { + fmtn('CurlyBracesIdentifier', '--i', ':0:2:{'), + children = { + { + 'Call:0:37:(', + children = { + { + fmtn('Lambda', '\\di', ':0:3:{'), + children = { + { + 'Comma:0:5:,', + children = { + 'PlainIdentifier(scope=0,ident=b):0:4:b', + 'PlainIdentifier(scope=0,ident=c):0:6: c', + }, + }, + { + 'Arrow:0:8: ->', + children = { + { + 'BinaryPlus:0:19: +', + children = { + { + 'BinaryPlus:0:14: +', + children = { + 'Register(name=d):0:11: @d', + 'Register(name=e):0:16: @e', + }, + }, + { + 'Call:0:32:(', + children = { + { + 'Nested:0:21: (', + children = { + { + fmtn('Lambda', '\\di', ':0:23:{'), + children = { + 'PlainIdentifier(scope=0,ident=f):0:24:f', + { + 'Arrow:0:25: ->', + children = { + 'PlainIdentifier(scope=0,ident=g):0:28: g', + }, + }, + }, + }, + }, + }, + 'Register(name=h):0:33:@h', + }, + }, + }, + }, + }, + }, + }, + }, + 'Register(name=i):0:38:@i', + }, + }, + }, + }, + 'PlainIdentifier(scope=0,ident=j):0:42:j', + }, + }, + }, + }, + }, + }, { + hl('IdentifierScope', 'a'), + hl('IdentifierScopeDelimiter', ':'), + hl('Curly', '{'), + hl('Lambda', '{'), + hl('IdentifierName', 'b'), + hl('Comma', ','), + hl('IdentifierName', 'c', 1), + hl('Arrow', '->', 1), + hl('Register', '@d', 1), + hl('BinaryPlus', '+', 1), + hl('Register', '@e', 1), + hl('BinaryPlus', '+', 1), + hl('NestingParenthesis', '(', 1), + hl('Lambda', '{'), + hl('IdentifierName', 'f'), + hl('Arrow', '->', 1), + hl('IdentifierName', 'g', 1), + hl('Lambda', '}'), + hl('NestingParenthesis', ')'), + hl('CallingParenthesis', '('), + hl('Register', '@h'), + hl('CallingParenthesis', ')'), + hl('Lambda', '}'), + hl('CallingParenthesis', '('), + hl('Register', '@i'), + hl('CallingParenthesis', ')'), + hl('Curly', '}'), + hl('IdentifierName', 'j'), + }) + check_parsing('{@a + @b : @c + @d, @e + @f : @g + @i}', { + -- 01234567890123456789012345678901234567 + -- 0 1 2 3 + ast = { + { + fmtn('DictLiteral', '-di', ':0:0:{'), + children = { + { + 'Comma:0:18:,', + children = { + { + 'Colon:0:8: :', + children = { + { + 'BinaryPlus:0:3: +', + children = { + 'Register(name=a):0:1:@a', + 'Register(name=b):0:5: @b', + }, + }, + { + 'BinaryPlus:0:13: +', + children = { + 'Register(name=c):0:10: @c', + 'Register(name=d):0:15: @d', + }, + }, + }, + }, + { + 'Colon:0:27: :', + children = { + { + 'BinaryPlus:0:22: +', + children = { + 'Register(name=e):0:19: @e', + 'Register(name=f):0:24: @f', + }, + }, + { + 'BinaryPlus:0:32: +', + children = { + 'Register(name=g):0:29: @g', + 'Register(name=i):0:34: @i', + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, { + hl('Dict', '{'), + hl('Register', '@a'), + hl('BinaryPlus', '+', 1), + hl('Register', '@b', 1), + hl('Colon', ':', 1), + hl('Register', '@c', 1), + hl('BinaryPlus', '+', 1), + hl('Register', '@d', 1), + hl('Comma', ','), + hl('Register', '@e', 1), + hl('BinaryPlus', '+', 1), + hl('Register', '@f', 1), + hl('Colon', ':', 1), + hl('Register', '@g', 1), + hl('BinaryPlus', '+', 1), + hl('Register', '@i', 1), + hl('Dict', '}'), + }) + check_parsing('-> -> ->', { + -- 01234567 + ast = { + { + 'Arrow:0:0:->', + children = { + 'Missing:0:0:', + { + 'Arrow:0:2: ->', + children = { + 'Missing:0:2:', + { + 'Arrow:0:5: ->', + children = { + 'Missing:0:5:', + }, + }, + }, + }, + }, + }, + }, + err = { + arg = '-> -> ->', + msg = 'E15: Unexpected arrow: %.*s', + }, + }, { + hl('InvalidArrow', '->'), + hl('InvalidArrow', '->', 1), + hl('InvalidArrow', '->', 1), + }) + check_parsing('a -> b -> c -> d', { + -- 0123456789012345 + -- 0 1 + ast = { + { + 'Arrow:0:1: ->', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'Arrow:0:6: ->', + children = { + 'PlainIdentifier(scope=0,ident=b):0:4: b', + { + 'Arrow:0:11: ->', + children = { + 'PlainIdentifier(scope=0,ident=c):0:9: c', + 'PlainIdentifier(scope=0,ident=d):0:14: d', + }, + }, + }, + }, + }, + }, + }, + err = { + arg = '-> b -> c -> d', + msg = 'E15: Arrow outside of lambda: %.*s', + }, + }, { + hl('IdentifierName', 'a'), + hl('InvalidArrow', '->', 1), + hl('IdentifierName', 'b', 1), + hl('InvalidArrow', '->', 1), + hl('IdentifierName', 'c', 1), + hl('InvalidArrow', '->', 1), + hl('IdentifierName', 'd', 1), + }) + check_parsing('{a -> b -> c}', { + -- 0123456789012 + -- 0 1 + ast = { + { + fmtn('Lambda', '\\di', ':0:0:{'), + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + { + 'Arrow:0:2: ->', + children = { + { + 'Arrow:0:7: ->', + children = { + 'PlainIdentifier(scope=0,ident=b):0:5: b', + 'PlainIdentifier(scope=0,ident=c):0:10: c', + }, + }, + }, + }, + }, + }, + }, + err = { + arg = '-> c}', + msg = 'E15: Arrow outside of lambda: %.*s', + }, + }, { + hl('Lambda', '{'), + hl('IdentifierName', 'a'), + hl('Arrow', '->', 1), + hl('IdentifierName', 'b', 1), + hl('InvalidArrow', '->', 1), + hl('IdentifierName', 'c', 1), + hl('Lambda', '}'), + }) + check_parsing('{a: -> b}', { + -- 012345678 + ast = { + { + fmtn('CurlyBracesIdentifier', '-di', ':0:0:{'), + children = { + { + 'Arrow:0:3: ->', + children = { + 'PlainIdentifier(scope=a,ident=):0:1:a:', + 'PlainIdentifier(scope=0,ident=b):0:6: b', + }, + }, + }, + }, + }, + err = { + arg = '-> b}', + msg = 'E15: Arrow outside of lambda: %.*s', + }, + }, { + hl('Curly', '{'), + hl('IdentifierScope', 'a'), + hl('IdentifierScopeDelimiter', ':'), + hl('InvalidArrow', '->', 1), + hl('IdentifierName', 'b', 1), + hl('Curly', '}'), + }) + + check_parsing('{a:b -> b}', { + -- 0123456789 + ast = { + { + fmtn('CurlyBracesIdentifier', '-di', ':0:0:{'), + children = { + { + 'Arrow:0:4: ->', + children = { + 'PlainIdentifier(scope=a,ident=b):0:1:a:b', + 'PlainIdentifier(scope=0,ident=b):0:7: b', + }, + }, + }, + }, + }, + err = { + arg = '-> b}', + msg = 'E15: Arrow outside of lambda: %.*s', + }, + }, { + hl('Curly', '{'), + hl('IdentifierScope', 'a'), + hl('IdentifierScopeDelimiter', ':'), + hl('IdentifierName', 'b'), + hl('InvalidArrow', '->', 1), + hl('IdentifierName', 'b', 1), + hl('Curly', '}'), + }) + + check_parsing('{a#b -> b}', { + -- 0123456789 + ast = { + { + fmtn('CurlyBracesIdentifier', '-di', ':0:0:{'), + children = { + { + 'Arrow:0:4: ->', + children = { + 'PlainIdentifier(scope=0,ident=a#b):0:1:a#b', + 'PlainIdentifier(scope=0,ident=b):0:7: b', + }, + }, + }, + }, + }, + err = { + arg = '-> b}', + msg = 'E15: Arrow outside of lambda: %.*s', + }, + }, { + hl('Curly', '{'), + hl('IdentifierName', 'a#b'), + hl('InvalidArrow', '->', 1), + hl('IdentifierName', 'b', 1), + hl('Curly', '}'), + }) + check_parsing('{a : b : c}', { + -- 01234567890 + -- 0 1 + ast = { + { + fmtn('DictLiteral', '-di', ':0:0:{'), + children = { + { + 'Colon:0:2: :', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + { + 'Colon:0:6: :', + children = { + 'PlainIdentifier(scope=0,ident=b):0:4: b', + 'PlainIdentifier(scope=0,ident=c):0:8: c', + }, + }, + }, + }, + }, + }, + }, + err = { + arg = ': c}', + msg = 'E15: Colon outside of dictionary or ternary operator: %.*s', + }, + }, { + hl('Dict', '{'), + hl('IdentifierName', 'a'), + hl('Colon', ':', 1), + hl('IdentifierName', 'b', 1), + hl('InvalidColon', ':', 1), + hl('IdentifierName', 'c', 1), + hl('Dict', '}'), + }) + check_parsing('{', { + -- 0 + ast = { + fmtn('UnknownFigure', '\\di', ':0:0:{'), + }, + err = { + arg = '{', + msg = 'E15: Missing closing figure brace: %.*s', + }, + }, { + hl('FigureBrace', '{'), + }) + check_parsing('{a', { + -- 01 + ast = { + { + fmtn('UnknownFigure', '\\di', ':0:0:{'), + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + }, + }, + }, + err = { + arg = '{a', + msg = 'E15: Missing closing figure brace: %.*s', + }, + }, { + hl('FigureBrace', '{'), + hl('IdentifierName', 'a'), + }) + check_parsing('{a,b', { + -- 0123 + ast = { + { + fmtn('Lambda', '\\di', ':0:0:{'), + children = { + { + 'Comma:0:2:,', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + 'PlainIdentifier(scope=0,ident=b):0:3:b', + }, + }, + }, + }, + }, + err = { + arg = '{a,b', + msg = 'E15: Missing closing figure brace for lambda: %.*s', + }, + }, { + hl('Lambda', '{'), + hl('IdentifierName', 'a'), + hl('Comma', ','), + hl('IdentifierName', 'b'), + }) + check_parsing('{a,b->', { + -- 012345 + ast = { + { + fmtn('Lambda', '\\di', ':0:0:{'), + children = { + { + 'Comma:0:2:,', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + 'PlainIdentifier(scope=0,ident=b):0:3:b', + }, + }, + 'Arrow:0:4:->', + }, + }, + }, + err = { + arg = '', + msg = 'E15: Expected value, got EOC: %.*s', + }, + }, { + hl('Lambda', '{'), + hl('IdentifierName', 'a'), + hl('Comma', ','), + hl('IdentifierName', 'b'), + hl('Arrow', '->'), + }) + check_parsing('{a,b->c', { + -- 0123456 + ast = { + { + fmtn('Lambda', '\\di', ':0:0:{'), + children = { + { + 'Comma:0:2:,', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + 'PlainIdentifier(scope=0,ident=b):0:3:b', + }, + }, + { + 'Arrow:0:4:->', + children = { + 'PlainIdentifier(scope=0,ident=c):0:6:c', + }, + }, + }, + }, + }, + err = { + arg = '{a,b->c', + msg = 'E15: Missing closing figure brace for lambda: %.*s', + }, + }, { + hl('Lambda', '{'), + hl('IdentifierName', 'a'), + hl('Comma', ','), + hl('IdentifierName', 'b'), + hl('Arrow', '->'), + hl('IdentifierName', 'c'), + }) + check_parsing('{a : b', { + -- 012345 + ast = { + { + fmtn('DictLiteral', '-di', ':0:0:{'), + children = { + { + 'Colon:0:2: :', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + 'PlainIdentifier(scope=0,ident=b):0:4: b', + }, + }, + }, + }, + }, + err = { + arg = '{a : b', + msg = 'E723: Missing end of Dictionary \'}\': %.*s', + }, + }, { + hl('Dict', '{'), + hl('IdentifierName', 'a'), + hl('Colon', ':', 1), + hl('IdentifierName', 'b', 1), + }) + check_parsing('{a : b,', { + -- 0123456 + ast = { + { + fmtn('DictLiteral', '-di', ':0:0:{'), + children = { + { + 'Comma:0:6:,', + children = { + { + 'Colon:0:2: :', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + 'PlainIdentifier(scope=0,ident=b):0:4: b', + }, + }, + }, + }, + }, + }, + }, + err = { + arg = '', + msg = 'E15: Expected value, got EOC: %.*s', + }, + }, { + hl('Dict', '{'), + hl('IdentifierName', 'a'), + hl('Colon', ':', 1), + hl('IdentifierName', 'b', 1), + hl('Comma', ','), + }) + end) + itp('works with ternary operator', function() + check_parsing('a ? b : c', { + -- 012345678 + ast = { + { + 'Ternary:0:1: ?', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'TernaryValue:0:5: :', + children = { + 'PlainIdentifier(scope=0,ident=b):0:3: b', + 'PlainIdentifier(scope=0,ident=c):0:7: c', + }, + }, + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('Ternary', '?', 1), + hl('IdentifierName', 'b', 1), + hl('TernaryColon', ':', 1), + hl('IdentifierName', 'c', 1), + }) + check_parsing('@a?@b?@c:@d:@e', { + -- 01234567890123 + -- 0 1 + ast = { + { + 'Ternary:0:2:?', + children = { + 'Register(name=a):0:0:@a', + { + 'TernaryValue:0:11::', + children = { + { + 'Ternary:0:5:?', + children = { + 'Register(name=b):0:3:@b', + { + 'TernaryValue:0:8::', + children = { + 'Register(name=c):0:6:@c', + 'Register(name=d):0:9:@d', + }, + }, + }, + }, + 'Register(name=e):0:12:@e', + }, + }, + }, + }, + }, + }, { + hl('Register', '@a'), + hl('Ternary', '?'), + hl('Register', '@b'), + hl('Ternary', '?'), + hl('Register', '@c'), + hl('TernaryColon', ':'), + hl('Register', '@d'), + hl('TernaryColon', ':'), + hl('Register', '@e'), + }) + check_parsing('@a?@b:@c?@d:@e', { + -- 01234567890123 + -- 0 1 + ast = { + { + 'Ternary:0:2:?', + children = { + 'Register(name=a):0:0:@a', + { + 'TernaryValue:0:5::', + children = { + 'Register(name=b):0:3:@b', + { + 'Ternary:0:8:?', + children = { + 'Register(name=c):0:6:@c', + { + 'TernaryValue:0:11::', + children = { + 'Register(name=d):0:9:@d', + 'Register(name=e):0:12:@e', + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, { + hl('Register', '@a'), + hl('Ternary', '?'), + hl('Register', '@b'), + hl('TernaryColon', ':'), + hl('Register', '@c'), + hl('Ternary', '?'), + hl('Register', '@d'), + hl('TernaryColon', ':'), + hl('Register', '@e'), + }) + check_parsing('@a?@b?@c?@d:@e?@f:@g:@h?@i:@j:@k', { + -- 01234567890123456789012345678901 + -- 0 1 2 3 + ast = { + { + 'Ternary:0:2:?', + children = { + 'Register(name=a):0:0:@a', + { + 'TernaryValue:0:29::', + children = { + { + 'Ternary:0:5:?', + children = { + 'Register(name=b):0:3:@b', + { + 'TernaryValue:0:20::', + children = { + { + 'Ternary:0:8:?', + children = { + 'Register(name=c):0:6:@c', + { + 'TernaryValue:0:11::', + children = { + 'Register(name=d):0:9:@d', + { + 'Ternary:0:14:?', + children = { + 'Register(name=e):0:12:@e', + { + 'TernaryValue:0:17::', + children = { + 'Register(name=f):0:15:@f', + 'Register(name=g):0:18:@g', + }, + }, + }, + }, + }, + }, + }, + }, + { + 'Ternary:0:23:?', + children = { + 'Register(name=h):0:21:@h', + { + 'TernaryValue:0:26::', + children = { + 'Register(name=i):0:24:@i', + 'Register(name=j):0:27:@j', + }, + }, + }, + }, + }, + }, + }, + }, + 'Register(name=k):0:30:@k', + }, + }, + }, + }, + }, + }, { + hl('Register', '@a'), + hl('Ternary', '?'), + hl('Register', '@b'), + hl('Ternary', '?'), + hl('Register', '@c'), + hl('Ternary', '?'), + hl('Register', '@d'), + hl('TernaryColon', ':'), + hl('Register', '@e'), + hl('Ternary', '?'), + hl('Register', '@f'), + hl('TernaryColon', ':'), + hl('Register', '@g'), + hl('TernaryColon', ':'), + hl('Register', '@h'), + hl('Ternary', '?'), + hl('Register', '@i'), + hl('TernaryColon', ':'), + hl('Register', '@j'), + hl('TernaryColon', ':'), + hl('Register', '@k'), + }) + check_parsing('?', { + -- 0 + ast = { + { + 'Ternary:0:0:?', + children = { + 'Missing:0:0:', + 'TernaryValue:0:0:?', + }, + }, + }, + err = { + arg = '?', + msg = 'E15: Expected value, got question mark: %.*s', + }, + }, { + hl('InvalidTernary', '?'), + }) + + check_parsing('?:', { + -- 01 + ast = { + { + 'Ternary:0:0:?', + children = { + 'Missing:0:0:', + { + 'TernaryValue:0:1::', + children = { + 'Missing:0:1:', + }, + }, + }, + }, + }, + err = { + arg = '?:', + msg = 'E15: Expected value, got question mark: %.*s', + }, + }, { + hl('InvalidTernary', '?'), + hl('InvalidTernaryColon', ':'), + }) + + check_parsing('?::', { + -- 012 + ast = { + { + 'Colon:0:2::', + children = { + { + 'Ternary:0:0:?', + children = { + 'Missing:0:0:', + { + 'TernaryValue:0:1::', + children = { + 'Missing:0:1:', + 'Missing:0:2:', + }, + }, + }, + }, + }, + }, + }, + err = { + arg = '?::', + msg = 'E15: Expected value, got question mark: %.*s', + }, + }, { + hl('InvalidTernary', '?'), + hl('InvalidTernaryColon', ':'), + hl('InvalidColon', ':'), + }) + + check_parsing('a?b', { + -- 012 + ast = { + { + 'Ternary:0:1:?', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'TernaryValue:0:1:?', + children = { + 'PlainIdentifier(scope=0,ident=b):0:2:b', + }, + }, + }, + }, + }, + err = { + arg = '?b', + msg = 'E109: Missing \':\' after \'?\': %.*s', + }, + }, { + hl('IdentifierName', 'a'), + hl('Ternary', '?'), + hl('IdentifierName', 'b'), + }) + check_parsing('a?b:', { + -- 0123 + ast = { + { + 'Ternary:0:1:?', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'TernaryValue:0:1:?', + children = { + 'PlainIdentifier(scope=b,ident=):0:2:b:', + }, + }, + }, + }, + }, + err = { + arg = '?b:', + msg = 'E109: Missing \':\' after \'?\': %.*s', + }, + }, { + hl('IdentifierName', 'a'), + hl('Ternary', '?'), + hl('IdentifierScope', 'b'), + hl('IdentifierScopeDelimiter', ':'), + }) + + check_parsing('a?b::c', { + -- 012345 + ast = { + { + 'Ternary:0:1:?', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'TernaryValue:0:4::', + children = { + 'PlainIdentifier(scope=b,ident=):0:2:b:', + 'PlainIdentifier(scope=0,ident=c):0:5:c', + }, + }, + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('Ternary', '?'), + hl('IdentifierScope', 'b'), + hl('IdentifierScopeDelimiter', ':'), + hl('TernaryColon', ':'), + hl('IdentifierName', 'c'), + }) + + check_parsing('a?b :', { + -- 01234 + ast = { + { + 'Ternary:0:1:?', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'TernaryValue:0:3: :', + children = { + 'PlainIdentifier(scope=0,ident=b):0:2:b', + }, + }, + }, + }, + }, + err = { + arg = '', + msg = 'E15: Expected value, got EOC: %.*s', + }, + }, { + hl('IdentifierName', 'a'), + hl('Ternary', '?'), + hl('IdentifierName', 'b'), + hl('TernaryColon', ':', 1), + }) + + check_parsing('(@a?@b:@c)?@d:@e', { + -- 0123456789012345 + -- 0 1 + ast = { + { + 'Ternary:0:10:?', + children = { + { + 'Nested:0:0:(', + children = { + { + 'Ternary:0:3:?', + children = { + 'Register(name=a):0:1:@a', + { + 'TernaryValue:0:6::', + children = { + 'Register(name=b):0:4:@b', + 'Register(name=c):0:7:@c', + }, + }, + }, + }, + }, + }, + { + 'TernaryValue:0:13::', + children = { + 'Register(name=d):0:11:@d', + 'Register(name=e):0:14:@e', + }, + }, + }, + }, + }, + }, { + hl('NestingParenthesis', '('), + hl('Register', '@a'), + hl('Ternary', '?'), + hl('Register', '@b'), + hl('TernaryColon', ':'), + hl('Register', '@c'), + hl('NestingParenthesis', ')'), + hl('Ternary', '?'), + hl('Register', '@d'), + hl('TernaryColon', ':'), + hl('Register', '@e'), + }) + + check_parsing('(@a?@b:@c)?(@d?@e:@f):(@g?@h:@i)', { + -- 01234567890123456789012345678901 + -- 0 1 2 3 + ast = { + { + 'Ternary:0:10:?', + children = { + { + 'Nested:0:0:(', + children = { + { + 'Ternary:0:3:?', + children = { + 'Register(name=a):0:1:@a', + { + 'TernaryValue:0:6::', + children = { + 'Register(name=b):0:4:@b', + 'Register(name=c):0:7:@c', + }, + }, + }, + }, + }, + }, + { + 'TernaryValue:0:21::', + children = { + { + 'Nested:0:11:(', + children = { + { + 'Ternary:0:14:?', + children = { + 'Register(name=d):0:12:@d', + { + 'TernaryValue:0:17::', + children = { + 'Register(name=e):0:15:@e', + 'Register(name=f):0:18:@f', + }, + }, + }, + }, + }, + }, + { + 'Nested:0:22:(', + children = { + { + 'Ternary:0:25:?', + children = { + 'Register(name=g):0:23:@g', + { + 'TernaryValue:0:28::', + children = { + 'Register(name=h):0:26:@h', + 'Register(name=i):0:29:@i', + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, { + hl('NestingParenthesis', '('), + hl('Register', '@a'), + hl('Ternary', '?'), + hl('Register', '@b'), + hl('TernaryColon', ':'), + hl('Register', '@c'), + hl('NestingParenthesis', ')'), + hl('Ternary', '?'), + hl('NestingParenthesis', '('), + hl('Register', '@d'), + hl('Ternary', '?'), + hl('Register', '@e'), + hl('TernaryColon', ':'), + hl('Register', '@f'), + hl('NestingParenthesis', ')'), + hl('TernaryColon', ':'), + hl('NestingParenthesis', '('), + hl('Register', '@g'), + hl('Ternary', '?'), + hl('Register', '@h'), + hl('TernaryColon', ':'), + hl('Register', '@i'), + hl('NestingParenthesis', ')'), + }) + + check_parsing('(@a?@b:@c)?@d?@e:@f:@g?@h:@i', { + -- 0123456789012345678901234567 + -- 0 1 2 + ast = { + { + 'Ternary:0:10:?', + children = { + { + 'Nested:0:0:(', + children = { + { + 'Ternary:0:3:?', + children = { + 'Register(name=a):0:1:@a', + { + 'TernaryValue:0:6::', + children = { + 'Register(name=b):0:4:@b', + 'Register(name=c):0:7:@c', + }, + }, + }, + }, + }, + }, + { + 'TernaryValue:0:19::', + children = { + { + 'Ternary:0:13:?', + children = { + 'Register(name=d):0:11:@d', + { + 'TernaryValue:0:16::', + children = { + 'Register(name=e):0:14:@e', + 'Register(name=f):0:17:@f', + }, + }, + }, + }, + { + 'Ternary:0:22:?', + children = { + 'Register(name=g):0:20:@g', + { + 'TernaryValue:0:25::', + children = { + 'Register(name=h):0:23:@h', + 'Register(name=i):0:26:@i', + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, { + hl('NestingParenthesis', '('), + hl('Register', '@a'), + hl('Ternary', '?'), + hl('Register', '@b'), + hl('TernaryColon', ':'), + hl('Register', '@c'), + hl('NestingParenthesis', ')'), + hl('Ternary', '?'), + hl('Register', '@d'), + hl('Ternary', '?'), + hl('Register', '@e'), + hl('TernaryColon', ':'), + hl('Register', '@f'), + hl('TernaryColon', ':'), + hl('Register', '@g'), + hl('Ternary', '?'), + hl('Register', '@h'), + hl('TernaryColon', ':'), + hl('Register', '@i'), + }) + check_parsing('a?b{cdef}g:h', { + -- 012345678901 + -- 0 1 + ast = { + { + 'Ternary:0:1:?', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'TernaryValue:0:10::', + children = { + { + 'ComplexIdentifier:0:3:', + children = { + 'PlainIdentifier(scope=0,ident=b):0:2:b', + { + 'ComplexIdentifier:0:9:', + children = { + { + fmtn('CurlyBracesIdentifier', '--i', ':0:3:{'), + children = { + 'PlainIdentifier(scope=0,ident=cdef):0:4:cdef', + }, + }, + 'PlainIdentifier(scope=0,ident=g):0:9:g', + }, + }, + }, + }, + 'PlainIdentifier(scope=0,ident=h):0:11:h', + }, + }, + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('Ternary', '?'), + hl('IdentifierName', 'b'), + hl('Curly', '{'), + hl('IdentifierName', 'cdef'), + hl('Curly', '}'), + hl('IdentifierName', 'g'), + hl('TernaryColon', ':'), + hl('IdentifierName', 'h'), + }) + check_parsing('a ? b : c : d', { + -- 0123456789012 + -- 0 1 + ast = { + { + 'Colon:0:9: :', + children = { + { + 'Ternary:0:1: ?', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'TernaryValue:0:5: :', + children = { + 'PlainIdentifier(scope=0,ident=b):0:3: b', + 'PlainIdentifier(scope=0,ident=c):0:7: c', + }, + }, + }, + }, + 'PlainIdentifier(scope=0,ident=d):0:11: d', + }, + }, + }, + err = { + arg = ': d', + msg = 'E15: Colon outside of dictionary or ternary operator: %.*s', + }, + }, { + hl('IdentifierName', 'a'), + hl('Ternary', '?', 1), + hl('IdentifierName', 'b', 1), + hl('TernaryColon', ':', 1), + hl('IdentifierName', 'c', 1), + hl('InvalidColon', ':', 1), + hl('IdentifierName', 'd', 1), + }) + end) + itp('works with comparison operators', function() + check_parsing('a == b', { + -- 012345 + ast = { + { + 'Comparison(type=Equal,inv=0,ccs=UseOption):0:1: ==', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainIdentifier(scope=0,ident=b):0:4: b', + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('Comparison', '==', 1), + hl('IdentifierName', 'b', 1), + }) + + check_parsing('a ==? b', { + -- 0123456 + ast = { + { + 'Comparison(type=Equal,inv=0,ccs=IgnoreCase):0:1: ==?', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainIdentifier(scope=0,ident=b):0:5: b', + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('Comparison', '==', 1), + hl('ComparisonModifier', '?'), + hl('IdentifierName', 'b', 1), + }) + + check_parsing('a ==# b', { + -- 0123456 + ast = { + { + 'Comparison(type=Equal,inv=0,ccs=MatchCase):0:1: ==#', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainIdentifier(scope=0,ident=b):0:5: b', + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('Comparison', '==', 1), + hl('ComparisonModifier', '#'), + hl('IdentifierName', 'b', 1), + }) + + check_parsing('a !=# b', { + -- 0123456 + ast = { + { + 'Comparison(type=Equal,inv=1,ccs=MatchCase):0:1: !=#', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainIdentifier(scope=0,ident=b):0:5: b', + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('Comparison', '!=', 1), + hl('ComparisonModifier', '#'), + hl('IdentifierName', 'b', 1), + }) + + check_parsing('a <=# b', { + -- 0123456 + ast = { + { + 'Comparison(type=Greater,inv=1,ccs=MatchCase):0:1: <=#', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainIdentifier(scope=0,ident=b):0:5: b', + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('Comparison', '<=', 1), + hl('ComparisonModifier', '#'), + hl('IdentifierName', 'b', 1), + }) + + check_parsing('a >=# b', { + -- 0123456 + ast = { + { + 'Comparison(type=GreaterOrEqual,inv=0,ccs=MatchCase):0:1: >=#', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainIdentifier(scope=0,ident=b):0:5: b', + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('Comparison', '>=', 1), + hl('ComparisonModifier', '#'), + hl('IdentifierName', 'b', 1), + }) + + check_parsing('a ># b', { + -- 012345 + ast = { + { + 'Comparison(type=Greater,inv=0,ccs=MatchCase):0:1: >#', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainIdentifier(scope=0,ident=b):0:4: b', + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('Comparison', '>', 1), + hl('ComparisonModifier', '#'), + hl('IdentifierName', 'b', 1), + }) + + check_parsing('a <# b', { + -- 012345 + ast = { + { + 'Comparison(type=GreaterOrEqual,inv=1,ccs=MatchCase):0:1: <#', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainIdentifier(scope=0,ident=b):0:4: b', + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('Comparison', '<', 1), + hl('ComparisonModifier', '#'), + hl('IdentifierName', 'b', 1), + }) + + check_parsing('a is#b', { + -- 012345 + ast = { + { + 'Comparison(type=Identical,inv=0,ccs=MatchCase):0:1: is#', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainIdentifier(scope=0,ident=b):0:5:b', + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('Comparison', 'is', 1), + hl('ComparisonModifier', '#'), + hl('IdentifierName', 'b'), + }) + + check_parsing('a is?b', { + -- 012345 + ast = { + { + 'Comparison(type=Identical,inv=0,ccs=IgnoreCase):0:1: is?', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainIdentifier(scope=0,ident=b):0:5:b', + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('Comparison', 'is', 1), + hl('ComparisonModifier', '?'), + hl('IdentifierName', 'b'), + }) + + check_parsing('a isnot b', { + -- 012345678 + ast = { + { + 'Comparison(type=Identical,inv=1,ccs=UseOption):0:1: isnot', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainIdentifier(scope=0,ident=b):0:7: b', + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('Comparison', 'isnot', 1), + hl('IdentifierName', 'b', 1), + }) + + check_parsing('a < b < c', { + -- 012345678 + ast = { + { + 'Comparison(type=GreaterOrEqual,inv=1,ccs=UseOption):0:1: <', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'Comparison(type=GreaterOrEqual,inv=1,ccs=UseOption):0:5: <', + children = { + 'PlainIdentifier(scope=0,ident=b):0:3: b', + 'PlainIdentifier(scope=0,ident=c):0:7: c', + }, + }, + }, + }, + }, + err = { + arg = ' < c', + msg = 'E15: Operator is not associative: %.*s', + }, + }, { + hl('IdentifierName', 'a'), + hl('Comparison', '<', 1), + hl('IdentifierName', 'b', 1), + hl('InvalidComparison', '<', 1), + hl('IdentifierName', 'c', 1), + }) + + check_parsing('a < b <# c', { + -- 012345678 + ast = { + { + 'Comparison(type=GreaterOrEqual,inv=1,ccs=UseOption):0:1: <', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'Comparison(type=GreaterOrEqual,inv=1,ccs=MatchCase):0:5: <#', + children = { + 'PlainIdentifier(scope=0,ident=b):0:3: b', + 'PlainIdentifier(scope=0,ident=c):0:8: c', + }, + }, + }, + }, + }, + err = { + arg = ' <# c', + msg = 'E15: Operator is not associative: %.*s', + }, + }, { + hl('IdentifierName', 'a'), + hl('Comparison', '<', 1), + hl('IdentifierName', 'b', 1), + hl('InvalidComparison', '<', 1), + hl('InvalidComparisonModifier', '#'), + hl('IdentifierName', 'c', 1), + }) + + check_parsing('a += b', { + -- 012345 + ast = { + { + 'Assignment(Add):0:1: +=', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainIdentifier(scope=0,ident=b):0:4: b', + }, + }, + }, + err = { + arg = '+= b', + msg = 'E15: Misplaced assignment: %.*s', + }, + }, { + hl('IdentifierName', 'a'), + hl('InvalidAssignmentWithAddition', '+=', 1), + hl('IdentifierName', 'b', 1), + }) + check_parsing('a + b == c + d', { + -- 01234567890123 + -- 0 1 + ast = { + { + 'Comparison(type=Equal,inv=0,ccs=UseOption):0:5: ==', + children = { + { + 'BinaryPlus:0:1: +', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainIdentifier(scope=0,ident=b):0:3: b', + }, + }, + { + 'BinaryPlus:0:10: +', + children = { + 'PlainIdentifier(scope=0,ident=c):0:8: c', + 'PlainIdentifier(scope=0,ident=d):0:12: d', + }, + }, + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('BinaryPlus', '+', 1), + hl('IdentifierName', 'b', 1), + hl('Comparison', '==', 1), + hl('IdentifierName', 'c', 1), + hl('BinaryPlus', '+', 1), + hl('IdentifierName', 'd', 1), + }) + check_parsing('+ a == + b', { + -- 0123456789 + ast = { + { + 'Comparison(type=Equal,inv=0,ccs=UseOption):0:3: ==', + children = { + { + 'UnaryPlus:0:0:+', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1: a', + }, + }, + { + 'UnaryPlus:0:6: +', + children = { + 'PlainIdentifier(scope=0,ident=b):0:8: b', + }, + }, + }, + }, + }, + }, { + hl('UnaryPlus', '+'), + hl('IdentifierName', 'a', 1), + hl('Comparison', '==', 1), + hl('UnaryPlus', '+', 1), + hl('IdentifierName', 'b', 1), + }) + end) + itp('works with concat/subscript', function() + check_parsing('.', { + -- 0 + ast = { + { + 'ConcatOrSubscript:0:0:.', + children = { + 'Missing:0:0:', + }, + }, + }, + err = { + arg = '.', + msg = 'E15: Unexpected dot: %.*s', + }, + }, { + hl('InvalidConcatOrSubscript', '.'), + }) + + check_parsing('a.', { + -- 01 + ast = { + { + 'ConcatOrSubscript:0:1:.', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + }, + }, + }, + err = { + arg = '', + msg = 'E15: Expected value, got EOC: %.*s', + }, + }, { + hl('IdentifierName', 'a'), + hl('ConcatOrSubscript', '.'), + }) + + check_parsing('a.b', { + -- 012 + ast = { + { + 'ConcatOrSubscript:0:1:.', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainKey(key=b):0:2:b', + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('ConcatOrSubscript', '.'), + hl('IdentifierKey', 'b'), + }) + + check_parsing('1.2', { + -- 012 + ast = { + 'Float(val=1.200000e+00):0:0:1.2', + }, + }, { + hl('Float', '1.2'), + }) + + check_parsing('1.2 + 1.3e-5', { + -- 012345678901 + -- 0 1 + ast = { + { + 'BinaryPlus:0:3: +', + children = { + 'Float(val=1.200000e+00):0:0:1.2', + 'Float(val=1.300000e-05):0:5: 1.3e-5', + }, + }, + }, + }, { + hl('Float', '1.2'), + hl('BinaryPlus', '+', 1), + hl('Float', '1.3e-5', 1), + }) + + check_parsing('a . 1.2 + 1.3e-5', { + -- 0123456789012345 + -- 0 1 + ast = { + { + 'BinaryPlus:0:7: +', + children = { + { + 'Concat:0:1: .', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'ConcatOrSubscript:0:5:.', + children = { + 'Integer(val=1):0:3: 1', + 'PlainKey(key=2):0:6:2', + }, + }, + }, + }, + 'Float(val=1.300000e-05):0:9: 1.3e-5', + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('Concat', '.', 1), + hl('Number', '1', 1), + hl('ConcatOrSubscript', '.'), + hl('IdentifierKey', '2'), + hl('BinaryPlus', '+', 1), + hl('Float', '1.3e-5', 1), + }) + + check_parsing('1.3e-5 + 1.2 . a', { + -- 0123456789012345 + -- 0 1 + ast = { + { + 'Concat:0:12: .', + children = { + { + 'BinaryPlus:0:6: +', + children = { + 'Float(val=1.300000e-05):0:0:1.3e-5', + 'Float(val=1.200000e+00):0:8: 1.2', + }, + }, + 'PlainIdentifier(scope=0,ident=a):0:14: a', + }, + }, + }, + }, { + hl('Float', '1.3e-5'), + hl('BinaryPlus', '+', 1), + hl('Float', '1.2', 1), + hl('Concat', '.', 1), + hl('IdentifierName', 'a', 1), + }) + + check_parsing('1.3e-5 + a . 1.2', { + -- 0123456789012345 + -- 0 1 + ast = { + { + 'Concat:0:10: .', + children = { + { + 'BinaryPlus:0:6: +', + children = { + 'Float(val=1.300000e-05):0:0:1.3e-5', + 'PlainIdentifier(scope=0,ident=a):0:8: a', + }, + }, + { + 'ConcatOrSubscript:0:14:.', + children = { + 'Integer(val=1):0:12: 1', + 'PlainKey(key=2):0:15:2', + }, + }, + }, + }, + }, + }, { + hl('Float', '1.3e-5'), + hl('BinaryPlus', '+', 1), + hl('IdentifierName', 'a', 1), + hl('Concat', '.', 1), + hl('Number', '1', 1), + hl('ConcatOrSubscript', '.'), + hl('IdentifierKey', '2'), + }) + + check_parsing('1.2.3', { + -- 01234 + ast = { + { + 'ConcatOrSubscript:0:3:.', + children = { + { + 'ConcatOrSubscript:0:1:.', + children = { + 'Integer(val=1):0:0:1', + 'PlainKey(key=2):0:2:2', + }, + }, + 'PlainKey(key=3):0:4:3', + }, + }, + }, + }, { + hl('Number', '1'), + hl('ConcatOrSubscript', '.'), + hl('IdentifierKey', '2'), + hl('ConcatOrSubscript', '.'), + hl('IdentifierKey', '3'), + }) + + check_parsing('a.1.2', { + -- 01234 + ast = { + { + 'ConcatOrSubscript:0:3:.', + children = { + { + 'ConcatOrSubscript:0:1:.', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainKey(key=1):0:2:1', + }, + }, + 'PlainKey(key=2):0:4:2', + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('ConcatOrSubscript', '.'), + hl('IdentifierKey', '1'), + hl('ConcatOrSubscript', '.'), + hl('IdentifierKey', '2'), + }) + + check_parsing('a . 1.2', { + -- 0123456 + ast = { + { + 'Concat:0:1: .', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'ConcatOrSubscript:0:5:.', + children = { + 'Integer(val=1):0:3: 1', + 'PlainKey(key=2):0:6:2', + }, + }, + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('Concat', '.', 1), + hl('Number', '1', 1), + hl('ConcatOrSubscript', '.'), + hl('IdentifierKey', '2'), + }) + + check_parsing('+a . +b', { + -- 0123456 + ast = { + { + 'Concat:0:2: .', + children = { + { + 'UnaryPlus:0:0:+', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + }, + }, + { + 'UnaryPlus:0:4: +', + children = { + 'PlainIdentifier(scope=0,ident=b):0:6:b', + }, + }, + }, + }, + }, + }, { + hl('UnaryPlus', '+'), + hl('IdentifierName', 'a'), + hl('Concat', '.', 1), + hl('UnaryPlus', '+', 1), + hl('IdentifierName', 'b'), + }) + + check_parsing('a. b', { + -- 0123 + ast = { + { + 'Concat:0:1:.', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainIdentifier(scope=0,ident=b):0:2: b', + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('ConcatOrSubscript', '.'), + hl('IdentifierName', 'b', 1), + }) + + check_parsing('a. 1', { + -- 0123 + ast = { + { + 'Concat:0:1:.', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'Integer(val=1):0:2: 1', + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('ConcatOrSubscript', '.'), + hl('Number', '1', 1), + }) + end) + itp('works with bracket subscripts', function() + check_parsing(':', { + -- 0 + ast = { + { + 'Colon:0:0::', + children = { + 'Missing:0:0:', + }, + }, + }, + err = { + arg = ':', + msg = 'E15: Colon outside of dictionary or ternary operator: %.*s', + }, + }, { + hl('InvalidColon', ':'), + }) + check_parsing('a[]', { + -- 012 + ast = { + { + 'Subscript:0:1:[', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + }, + }, + }, + err = { + arg = ']', + msg = 'E15: Expected value, got closing bracket: %.*s', + }, + }, { + hl('IdentifierName', 'a'), + hl('SubscriptBracket', '['), + hl('InvalidSubscriptBracket', ']'), + }) + check_parsing('a[b:]', { + -- 01234 + ast = { + { + 'Subscript:0:1:[', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainIdentifier(scope=b,ident=):0:2:b:', + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('SubscriptBracket', '['), + hl('IdentifierScope', 'b'), + hl('IdentifierScopeDelimiter', ':'), + hl('SubscriptBracket', ']'), + }) + + check_parsing('a[b:c]', { + -- 012345 + ast = { + { + 'Subscript:0:1:[', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainIdentifier(scope=b,ident=c):0:2:b:c', + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('SubscriptBracket', '['), + hl('IdentifierScope', 'b'), + hl('IdentifierScopeDelimiter', ':'), + hl('IdentifierName', 'c'), + hl('SubscriptBracket', ']'), + }) + check_parsing('a[b : c]', { + -- 01234567 + ast = { + { + 'Subscript:0:1:[', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'Colon:0:3: :', + children = { + 'PlainIdentifier(scope=0,ident=b):0:2:b', + 'PlainIdentifier(scope=0,ident=c):0:5: c', + }, + }, + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('SubscriptBracket', '['), + hl('IdentifierName', 'b'), + hl('SubscriptColon', ':', 1), + hl('IdentifierName', 'c', 1), + hl('SubscriptBracket', ']'), + }) + + check_parsing('a[: b]', { + -- 012345 + ast = { + { + 'Subscript:0:1:[', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'Colon:0:2::', + children = { + 'Missing:0:2:', + 'PlainIdentifier(scope=0,ident=b):0:3: b', + }, + }, + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('SubscriptBracket', '['), + hl('SubscriptColon', ':'), + hl('IdentifierName', 'b', 1), + hl('SubscriptBracket', ']'), + }) + + check_parsing('a[b :]', { + -- 012345 + ast = { + { + 'Subscript:0:1:[', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'Colon:0:3: :', + children = { + 'PlainIdentifier(scope=0,ident=b):0:2:b', + }, + }, + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('SubscriptBracket', '['), + hl('IdentifierName', 'b'), + hl('SubscriptColon', ':', 1), + hl('SubscriptBracket', ']'), + }) + check_parsing('a[b][c][d](e)(f)(g)', { + -- 0123456789012345678 + -- 0 1 + ast = { + { + 'Call:0:16:(', + children = { + { + 'Call:0:13:(', + children = { + { + 'Call:0:10:(', + children = { + { + 'Subscript:0:7:[', + children = { + { + 'Subscript:0:4:[', + children = { + { + 'Subscript:0:1:[', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainIdentifier(scope=0,ident=b):0:2:b', + }, + }, + 'PlainIdentifier(scope=0,ident=c):0:5:c', + }, + }, + 'PlainIdentifier(scope=0,ident=d):0:8:d', + }, + }, + 'PlainIdentifier(scope=0,ident=e):0:11:e', + }, + }, + 'PlainIdentifier(scope=0,ident=f):0:14:f', + }, + }, + 'PlainIdentifier(scope=0,ident=g):0:17:g', + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('SubscriptBracket', '['), + hl('IdentifierName', 'b'), + hl('SubscriptBracket', ']'), + hl('SubscriptBracket', '['), + hl('IdentifierName', 'c'), + hl('SubscriptBracket', ']'), + hl('SubscriptBracket', '['), + hl('IdentifierName', 'd'), + hl('SubscriptBracket', ']'), + hl('CallingParenthesis', '('), + hl('IdentifierName', 'e'), + hl('CallingParenthesis', ')'), + hl('CallingParenthesis', '('), + hl('IdentifierName', 'f'), + hl('CallingParenthesis', ')'), + hl('CallingParenthesis', '('), + hl('IdentifierName', 'g'), + hl('CallingParenthesis', ')'), + }) + check_parsing('{a}{b}{c}[d][e][f]', { + -- 012345678901234567 + -- 0 1 + ast = { + { + 'Subscript:0:15:[', + children = { + { + 'Subscript:0:12:[', + children = { + { + 'Subscript:0:9:[', + children = { + { + 'ComplexIdentifier:0:3:', + children = { + { + fmtn('CurlyBracesIdentifier', '-di', ':0:0:{'), + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + }, + }, + { + 'ComplexIdentifier:0:6:', + children = { + { + fmtn('CurlyBracesIdentifier', '--i', ':0:3:{'), + children = { + 'PlainIdentifier(scope=0,ident=b):0:4:b', + }, + }, + { + fmtn('CurlyBracesIdentifier', '--i', ':0:6:{'), + children = { + 'PlainIdentifier(scope=0,ident=c):0:7:c', + }, + }, + }, + }, + }, + }, + 'PlainIdentifier(scope=0,ident=d):0:10:d', + }, + }, + 'PlainIdentifier(scope=0,ident=e):0:13:e', + }, + }, + 'PlainIdentifier(scope=0,ident=f):0:16:f', + }, + }, + }, + }, { + hl('Curly', '{'), + hl('IdentifierName', 'a'), + hl('Curly', '}'), + hl('Curly', '{'), + hl('IdentifierName', 'b'), + hl('Curly', '}'), + hl('Curly', '{'), + hl('IdentifierName', 'c'), + hl('Curly', '}'), + hl('SubscriptBracket', '['), + hl('IdentifierName', 'd'), + hl('SubscriptBracket', ']'), + hl('SubscriptBracket', '['), + hl('IdentifierName', 'e'), + hl('SubscriptBracket', ']'), + hl('SubscriptBracket', '['), + hl('IdentifierName', 'f'), + hl('SubscriptBracket', ']'), + }) + end) + itp('supports list literals', function() + check_parsing('[]', { + -- 01 + ast = { + 'ListLiteral:0:0:[', + }, + }, { + hl('List', '['), + hl('List', ']'), + }) + + check_parsing('[a]', { + -- 012 + ast = { + { + 'ListLiteral:0:0:[', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + }, + }, + }, + }, { + hl('List', '['), + hl('IdentifierName', 'a'), + hl('List', ']'), + }) + + check_parsing('[a, b]', { + -- 012345 + ast = { + { + 'ListLiteral:0:0:[', + children = { + { + 'Comma:0:2:,', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + 'PlainIdentifier(scope=0,ident=b):0:3: b', + }, + }, + }, + }, + }, + }, { + hl('List', '['), + hl('IdentifierName', 'a'), + hl('Comma', ','), + hl('IdentifierName', 'b', 1), + hl('List', ']'), + }) + + check_parsing('[a, b, c]', { + -- 012345678 + ast = { + { + 'ListLiteral:0:0:[', + children = { + { + 'Comma:0:2:,', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + { + 'Comma:0:5:,', + children = { + 'PlainIdentifier(scope=0,ident=b):0:3: b', + 'PlainIdentifier(scope=0,ident=c):0:6: c', + }, + }, + }, + }, + }, + }, + }, + }, { + hl('List', '['), + hl('IdentifierName', 'a'), + hl('Comma', ','), + hl('IdentifierName', 'b', 1), + hl('Comma', ','), + hl('IdentifierName', 'c', 1), + hl('List', ']'), + }) + + check_parsing('[a, b, c, ]', { + -- 01234567890 + -- 0 1 + ast = { + { + 'ListLiteral:0:0:[', + children = { + { + 'Comma:0:2:,', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + { + 'Comma:0:5:,', + children = { + 'PlainIdentifier(scope=0,ident=b):0:3: b', + { + 'Comma:0:8:,', + children = { + 'PlainIdentifier(scope=0,ident=c):0:6: c', + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, { + hl('List', '['), + hl('IdentifierName', 'a'), + hl('Comma', ','), + hl('IdentifierName', 'b', 1), + hl('Comma', ','), + hl('IdentifierName', 'c', 1), + hl('Comma', ','), + hl('List', ']', 1), + }) + + check_parsing('[a : b, c : d]', { + -- 01234567890123 + -- 0 1 + ast = { + { + 'ListLiteral:0:0:[', + children = { + { + 'Comma:0:6:,', + children = { + { + 'Colon:0:2: :', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + 'PlainIdentifier(scope=0,ident=b):0:4: b', + }, + }, + { + 'Colon:0:9: :', + children = { + 'PlainIdentifier(scope=0,ident=c):0:7: c', + 'PlainIdentifier(scope=0,ident=d):0:11: d', + }, + }, + }, + }, + }, + }, + }, + err = { + arg = ': b, c : d]', + msg = 'E15: Colon outside of dictionary or ternary operator: %.*s', + }, + }, { + hl('List', '['), + hl('IdentifierName', 'a'), + hl('InvalidColon', ':', 1), + hl('IdentifierName', 'b', 1), + hl('Comma', ','), + hl('IdentifierName', 'c', 1), + hl('InvalidColon', ':', 1), + hl('IdentifierName', 'd', 1), + hl('List', ']'), + }) + + check_parsing(']', { + -- 0 + ast = { + 'ListLiteral:0:0:', + }, + err = { + arg = ']', + msg = 'E15: Unexpected closing figure brace: %.*s', + }, + }, { + hl('InvalidList', ']'), + }) + + check_parsing('a]', { + -- 01 + ast = { + { + 'ListLiteral:0:1:', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + }, + }, + }, + err = { + arg = ']', + msg = 'E15: Unexpected closing figure brace: %.*s', + }, + }, { + hl('IdentifierName', 'a'), + hl('InvalidList', ']'), + }) + + check_parsing('[] []', { + -- 01234 + ast = { + { + 'OpMissing:0:2:', + children = { + 'ListLiteral:0:0:[', + 'ListLiteral:0:2: [', + }, + }, + }, + err = { + arg = '[]', + msg = 'E15: Missing operator: %.*s', + }, + }, { + hl('List', '['), + hl('List', ']'), + hl('InvalidSpacing', ' '), + hl('List', '['), + hl('List', ']'), + }, { + [1] = { + ast = { + len = 3, + err = REMOVE_THIS, + ast = { + 'ListLiteral:0:0:[', + }, + }, + hl_fs = { + [3] = REMOVE_THIS, + [4] = REMOVE_THIS, + [5] = REMOVE_THIS, + }, + }, + }) + + check_parsing('[][]', { + -- 0123 + ast = { + { + 'Subscript:0:2:[', + children = { + 'ListLiteral:0:0:[', + }, + }, + }, + err = { + arg = ']', + msg = 'E15: Expected value, got closing bracket: %.*s', + }, + }, { + hl('List', '['), + hl('List', ']'), + hl('SubscriptBracket', '['), + hl('InvalidSubscriptBracket', ']'), + }) + + check_parsing('[', { + -- 0 + ast = { + 'ListLiteral:0:0:[', + }, + err = { + arg = '', + msg = 'E15: Expected value, got EOC: %.*s', + }, + }, { + hl('List', '['), + }) + + check_parsing('[1', { + -- 01 + ast = { + { + 'ListLiteral:0:0:[', + children = { + 'Integer(val=1):0:1:1', + }, + }, + }, + err = { + arg = '[1', + msg = 'E697: Missing end of List \']\': %.*s', + }, + }, { + hl('List', '['), + hl('Number', '1'), + }) + end) + itp('works with strings', function() + check_parsing('\'abc\'', { + -- 01234 + ast = { + fmtn('SingleQuotedString', 'val="abc"', ':0:0:\'abc\''), + }, + }, { + hl('SingleQuote', '\''), + hl('SingleQuotedBody', 'abc'), + hl('SingleQuote', '\''), + }) + check_parsing('"abc"', { + -- 01234 + ast = { + fmtn('DoubleQuotedString', 'val="abc"', ':0:0:"abc"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedBody', 'abc'), + hl('DoubleQuote', '"'), + }) + check_parsing('\'\'', { + -- 01 + ast = { + fmtn('SingleQuotedString', 'val=NULL', ':0:0:\'\''), + }, + }, { + hl('SingleQuote', '\''), + hl('SingleQuote', '\''), + }) + check_parsing('""', { + -- 01 + ast = { + fmtn('DoubleQuotedString', 'val=NULL', ':0:0:""'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuote', '"'), + }) + check_parsing('"', { + -- 0 + ast = { + fmtn('DoubleQuotedString', 'val=NULL', ':0:0:"'), + }, + err = { + arg = '"', + msg = 'E114: Missing double quote: %.*s', + }, + }, { + hl('InvalidDoubleQuote', '"'), + }) + check_parsing('\'', { + -- 0 + ast = { + fmtn('SingleQuotedString', 'val=NULL', ':0:0:\''), + }, + err = { + arg = '\'', + msg = 'E115: Missing single quote: %.*s', + }, + }, { + hl('InvalidSingleQuote', '\''), + }) + check_parsing('"a', { + -- 01 + ast = { + fmtn('DoubleQuotedString', 'val="a"', ':0:0:"a'), + }, + err = { + arg = '"a', + msg = 'E114: Missing double quote: %.*s', + }, + }, { + hl('InvalidDoubleQuote', '"'), + hl('InvalidDoubleQuotedBody', 'a'), + }) + check_parsing('\'a', { + -- 01 + ast = { + fmtn('SingleQuotedString', 'val="a"', ':0:0:\'a'), + }, + err = { + arg = '\'a', + msg = 'E115: Missing single quote: %.*s', + }, + }, { + hl('InvalidSingleQuote', '\''), + hl('InvalidSingleQuotedBody', 'a'), + }) + check_parsing('\'abc\'\'def\'', { + -- 0123456789 + ast = { + fmtn('SingleQuotedString', 'val="abc\'def"', ':0:0:\'abc\'\'def\''), + }, + }, { + hl('SingleQuote', '\''), + hl('SingleQuotedBody', 'abc'), + hl('SingleQuotedQuote', '\'\''), + hl('SingleQuotedBody', 'def'), + hl('SingleQuote', '\''), + }) + check_parsing('\'abc\'\'', { + -- 012345 + ast = { + fmtn('SingleQuotedString', 'val="abc\'"', ':0:0:\'abc\'\''), + }, + err = { + arg = '\'abc\'\'', + msg = 'E115: Missing single quote: %.*s', + }, + }, { + hl('InvalidSingleQuote', '\''), + hl('InvalidSingleQuotedBody', 'abc'), + hl('InvalidSingleQuotedQuote', '\'\''), + }) + check_parsing('\'\'\'\'\'\'\'\'', { + -- 01234567 + ast = { + fmtn('SingleQuotedString', 'val="\'\'\'"', ':0:0:\'\'\'\'\'\'\'\''), + }, + }, { + hl('SingleQuote', '\''), + hl('SingleQuotedQuote', '\'\''), + hl('SingleQuotedQuote', '\'\''), + hl('SingleQuotedQuote', '\'\''), + hl('SingleQuote', '\''), + }) + check_parsing('\'\'\'a\'\'\'\'bc\'', { + -- 01234567890 + -- 0 1 + ast = { + fmtn('SingleQuotedString', 'val="\'a\'\'bc"', ':0:0:\'\'\'a\'\'\'\'bc\''), + }, + }, { + hl('SingleQuote', '\''), + hl('SingleQuotedQuote', '\'\''), + hl('SingleQuotedBody', 'a'), + hl('SingleQuotedQuote', '\'\''), + hl('SingleQuotedQuote', '\'\''), + hl('SingleQuotedBody', 'bc'), + hl('SingleQuote', '\''), + }) + check_parsing('"\\"\\"\\"\\""', { + -- 0123456789 + ast = { + fmtn('DoubleQuotedString', 'val="\\"\\"\\"\\""', ':0:0:"\\"\\"\\"\\""'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\"'), + hl('DoubleQuotedEscape', '\\"'), + hl('DoubleQuotedEscape', '\\"'), + hl('DoubleQuotedEscape', '\\"'), + hl('DoubleQuote', '"'), + }) + check_parsing('"abc\\"def\\"ghi\\"jkl\\"mno"', { + -- 0123456789012345678901234 + -- 0 1 2 + ast = { + fmtn('DoubleQuotedString', 'val="abc\\"def\\"ghi\\"jkl\\"mno"', ':0:0:"abc\\"def\\"ghi\\"jkl\\"mno"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedBody', 'abc'), + hl('DoubleQuotedEscape', '\\"'), + hl('DoubleQuotedBody', 'def'), + hl('DoubleQuotedEscape', '\\"'), + hl('DoubleQuotedBody', 'ghi'), + hl('DoubleQuotedEscape', '\\"'), + hl('DoubleQuotedBody', 'jkl'), + hl('DoubleQuotedEscape', '\\"'), + hl('DoubleQuotedBody', 'mno'), + hl('DoubleQuote', '"'), + }) + check_parsing('"\\b\\e\\f\\r\\t\\\\"', { + -- 0123456789012345 + -- 0 1 + ast = { + [[DoubleQuotedString(val="\8\27\12\13\9\\"):0:0:"\b\e\f\r\t\\"]], + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\b'), + hl('DoubleQuotedEscape', '\\e'), + hl('DoubleQuotedEscape', '\\f'), + hl('DoubleQuotedEscape', '\\r'), + hl('DoubleQuotedEscape', '\\t'), + hl('DoubleQuotedEscape', '\\\\'), + hl('DoubleQuote', '"'), + }) + check_parsing('"\\n\n"', { + -- 01234 + ast = { + fmtn('DoubleQuotedString', 'val="\\\n\\\n"', ':0:0:"\\n\n"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\n'), + hl('DoubleQuotedBody', '\n'), + hl('DoubleQuote', '"'), + }) + check_parsing('"\\x00"', { + -- 012345 + ast = { + fmtn('DoubleQuotedString', 'val="\\0"', ':0:0:"\\x00"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\x00'), + hl('DoubleQuote', '"'), + }) + check_parsing('"\\xFF"', { + -- 012345 + ast = { + fmtn('DoubleQuotedString', 'val="\255"', ':0:0:"\\xFF"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\xFF'), + hl('DoubleQuote', '"'), + }) + check_parsing('"\\xF"', { + -- 012345 + ast = { + fmtn('DoubleQuotedString', 'val="\\15"', ':0:0:"\\xF"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\xF'), + hl('DoubleQuote', '"'), + }) + check_parsing('"\\u00AB"', { + -- 01234567 + ast = { + fmtn('DoubleQuotedString', 'val="«"', ':0:0:"\\u00AB"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\u00AB'), + hl('DoubleQuote', '"'), + }) + check_parsing('"\\U000000AB"', { + -- 01234567 + ast = { + fmtn('DoubleQuotedString', 'val="«"', ':0:0:"\\U000000AB"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\U000000AB'), + hl('DoubleQuote', '"'), + }) + check_parsing('"\\x"', { + -- 0123 + ast = { + fmtn('DoubleQuotedString', 'val="x"', ':0:0:"\\x"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedUnknownEscape', '\\x'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\x', { + -- 012 + ast = { + fmtn('DoubleQuotedString', 'val="x"', ':0:0:"\\x'), + }, + err = { + arg = '"\\x', + msg = 'E114: Missing double quote: %.*s', + }, + }, { + hl('InvalidDoubleQuote', '"'), + hl('InvalidDoubleQuotedUnknownEscape', '\\x'), + }) + + check_parsing('"\\xF', { + -- 0123 + ast = { + fmtn('DoubleQuotedString', 'val="\\15"', ':0:0:"\\xF'), + }, + err = { + arg = '"\\xF', + msg = 'E114: Missing double quote: %.*s', + }, + }, { + hl('InvalidDoubleQuote', '"'), + hl('InvalidDoubleQuotedEscape', '\\xF'), + }) + + check_parsing('"\\u"', { + -- 0123 + ast = { + fmtn('DoubleQuotedString', 'val="u"', ':0:0:"\\u"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedUnknownEscape', '\\u'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\u', { + -- 012 + ast = { + fmtn('DoubleQuotedString', 'val="u"', ':0:0:"\\u'), + }, + err = { + arg = '"\\u', + msg = 'E114: Missing double quote: %.*s', + }, + }, { + hl('InvalidDoubleQuote', '"'), + hl('InvalidDoubleQuotedUnknownEscape', '\\u'), + }) + + check_parsing('"\\U', { + -- 012 + ast = { + fmtn('DoubleQuotedString', 'val="U"', ':0:0:"\\U'), + }, + err = { + arg = '"\\U', + msg = 'E114: Missing double quote: %.*s', + }, + }, { + hl('InvalidDoubleQuote', '"'), + hl('InvalidDoubleQuotedUnknownEscape', '\\U'), + }) + + check_parsing('"\\U"', { + -- 0123 + ast = { + fmtn('DoubleQuotedString', 'val="U"', ':0:0:"\\U"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedUnknownEscape', '\\U'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\xFX"', { + -- 012345 + ast = { + fmtn('DoubleQuotedString', 'val="\\15X"', ':0:0:"\\xFX"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\xF'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\XFX"', { + -- 012345 + ast = { + fmtn('DoubleQuotedString', 'val="\\15X"', ':0:0:"\\XFX"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\XF'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\xX"', { + -- 01234 + ast = { + fmtn('DoubleQuotedString', 'val="xX"', ':0:0:"\\xX"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedUnknownEscape', '\\x'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\XX"', { + -- 01234 + ast = { + fmtn('DoubleQuotedString', 'val="XX"', ':0:0:"\\XX"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedUnknownEscape', '\\X'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\uX"', { + -- 01234 + ast = { + fmtn('DoubleQuotedString', 'val="uX"', ':0:0:"\\uX"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedUnknownEscape', '\\u'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\UX"', { + -- 01234 + ast = { + fmtn('DoubleQuotedString', 'val="UX"', ':0:0:"\\UX"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedUnknownEscape', '\\U'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\x0X"', { + -- 012345 + ast = { + fmtn('DoubleQuotedString', 'val="\\0X"', ':0:0:"\\x0X"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\x0'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\X0X"', { + -- 012345 + ast = { + fmtn('DoubleQuotedString', 'val="\\0X"', ':0:0:"\\X0X"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\X0'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\u0X"', { + -- 012345 + ast = { + fmtn('DoubleQuotedString', 'val="\\0X"', ':0:0:"\\u0X"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\u0'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\U0X"', { + -- 012345 + ast = { + fmtn('DoubleQuotedString', 'val="\\0X"', ':0:0:"\\U0X"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\U0'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\x00X"', { + -- 0123456 + ast = { + fmtn('DoubleQuotedString', 'val="\\0X"', ':0:0:"\\x00X"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\x00'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\X00X"', { + -- 0123456 + ast = { + fmtn('DoubleQuotedString', 'val="\\0X"', ':0:0:"\\X00X"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\X00'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\u00X"', { + -- 0123456 + ast = { + fmtn('DoubleQuotedString', 'val="\\0X"', ':0:0:"\\u00X"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\u00'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\U00X"', { + -- 0123456 + ast = { + fmtn('DoubleQuotedString', 'val="\\0X"', ':0:0:"\\U00X"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\U00'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\u000X"', { + -- 01234567 + ast = { + fmtn('DoubleQuotedString', 'val="\\0X"', ':0:0:"\\u000X"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\u000'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\U000X"', { + -- 01234567 + ast = { + fmtn('DoubleQuotedString', 'val="\\0X"', ':0:0:"\\U000X"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\U000'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\u0000X"', { + -- 012345678 + ast = { + fmtn('DoubleQuotedString', 'val="\\0X"', ':0:0:"\\u0000X"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\u0000'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\U0000X"', { + -- 012345678 + ast = { + fmtn('DoubleQuotedString', 'val="\\0X"', ':0:0:"\\U0000X"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\U0000'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\U00000X"', { + -- 0123456789 + ast = { + fmtn('DoubleQuotedString', 'val="\\0X"', ':0:0:"\\U00000X"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\U00000'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\U000000X"', { + -- 01234567890 + -- 0 1 + ast = { + fmtn('DoubleQuotedString', 'val="\\0X"', ':0:0:"\\U000000X"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\U000000'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\U0000000X"', { + -- 012345678901 + -- 0 1 + ast = { + fmtn('DoubleQuotedString', 'val="\\0X"', ':0:0:"\\U0000000X"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\U0000000'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\U00000000X"', { + -- 0123456789012 + -- 0 1 + ast = { + fmtn('DoubleQuotedString', 'val="\\0X"', ':0:0:"\\U00000000X"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\U00000000'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\x000X"', { + -- 01234567 + ast = { + fmtn('DoubleQuotedString', 'val="\\0000X"', ':0:0:"\\x000X"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\x00'), + hl('DoubleQuotedBody', '0X'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\X000X"', { + -- 01234567 + ast = { + fmtn('DoubleQuotedString', 'val="\\0000X"', ':0:0:"\\X000X"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\X00'), + hl('DoubleQuotedBody', '0X'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\u00000X"', { + -- 0123456789 + ast = { + fmtn('DoubleQuotedString', 'val="\\0000X"', ':0:0:"\\u00000X"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\u0000'), + hl('DoubleQuotedBody', '0X'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\U000000000X"', { + -- 01234567890123 + -- 0 1 + ast = { + fmtn('DoubleQuotedString', 'val="\\0000X"', ':0:0:"\\U000000000X"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\U00000000'), + hl('DoubleQuotedBody', '0X'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\0"', { + -- 0123 + ast = { + fmtn('DoubleQuotedString', 'val="\\0"', ':0:0:"\\0"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\0'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\00"', { + -- 01234 + ast = { + fmtn('DoubleQuotedString', 'val="\\0"', ':0:0:"\\00"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\00'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\000"', { + -- 012345 + ast = { + fmtn('DoubleQuotedString', 'val="\\0"', ':0:0:"\\000"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\000'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\0000"', { + -- 0123456 + ast = { + fmtn('DoubleQuotedString', 'val="\\0000"', ':0:0:"\\0000"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\000'), + hl('DoubleQuotedBody', '0'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\8"', { + -- 0123 + ast = { + fmtn('DoubleQuotedString', 'val="8"', ':0:0:"\\8"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedUnknownEscape', '\\8'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\08"', { + -- 01234 + ast = { + fmtn('DoubleQuotedString', 'val="\\0008"', ':0:0:"\\08"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\0'), + hl('DoubleQuotedBody', '8'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\008"', { + -- 012345 + ast = { + fmtn('DoubleQuotedString', 'val="\\0008"', ':0:0:"\\008"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\00'), + hl('DoubleQuotedBody', '8'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\0008"', { + -- 0123456 + ast = { + fmtn('DoubleQuotedString', 'val="\\0008"', ':0:0:"\\0008"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\000'), + hl('DoubleQuotedBody', '8'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\777"', { + -- 012345 + ast = { + fmtn('DoubleQuotedString', 'val="\255"', ':0:0:"\\777"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\777'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\050"', { + -- 012345 + ast = { + fmtn('DoubleQuotedString', 'val="\40"', ':0:0:"\\050"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\050'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\"', { + -- 012345 + ast = { + fmtn('DoubleQuotedString', 'val="\\21"', ':0:0:"\\"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\<', { + -- 012 + ast = { + fmtn('DoubleQuotedString', 'val="<"', ':0:0:"\\<'), + }, + err = { + arg = '"\\<', + msg = 'E114: Missing double quote: %.*s', + }, + }, { + hl('InvalidDoubleQuote', '"'), + hl('InvalidDoubleQuotedUnknownEscape', '\\<'), + }) + + check_parsing('"\\<"', { + -- 0123 + ast = { + fmtn('DoubleQuotedString', 'val="<"', ':0:0:"\\<"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedUnknownEscape', '\\<'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\ {b{3}: 4}[5]}()] += 6', { + -- 012345678901234567890123456 + -- 0 1 2 + ast = { + { + 'Assignment(Add):0:22: +=', + children = { + { + 'Subscript:0:1:[', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'Call:0:19:(', + children = { + { + fmtn('Lambda', '\\di', ':0:2:{'), + children = { + { + 'Arrow:0:3:->', + children = { + { + 'Subscript:0:15:[', + children = { + { + fmtn('DictLiteral', '-di', ':0:5: {'), + children = { + { + 'Colon:0:11::', + children = { + { + 'ComplexIdentifier:0:8:', + children = { + 'PlainIdentifier(scope=0,ident=b):0:7:b', + { + fmtn('CurlyBracesIdentifier', '--i', ':0:8:{'), + children = { + 'Integer(val=3):0:9:3', + }, + }, + }, + }, + 'Integer(val=4):0:12: 4', + }, + }, + }, + }, + 'Integer(val=5):0:16:5', + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + 'Integer(val=6):0:25: 6', + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('SubscriptBracket', '['), + hl('Lambda', '{'), + hl('Arrow', '->'), + hl('Dict', '{', 1), + hl('IdentifierName', 'b'), + hl('Curly', '{'), + hl('Number', '3'), + hl('Curly', '}'), + hl('Colon', ':'), + hl('Number', '4', 1), + hl('Dict', '}'), + hl('SubscriptBracket', '['), + hl('Number', '5'), + hl('SubscriptBracket', ']'), + hl('Lambda', '}'), + hl('CallingParenthesis', '('), + hl('CallingParenthesis', ')'), + hl('SubscriptBracket', ']'), + hl('AssignmentWithAddition', '+=', 1), + hl('Number', '6', 1), + }) + + check_asgn_parsing('a{1}.2[{-> {b{3}: 4}[5]}()]', { + -- 012345678901234567890123456 + -- 0 1 2 + ast = { + { + 'Subscript:0:6:[', + children = { + { + 'ConcatOrSubscript:0:4:.', + children = { + { + 'ComplexIdentifier:0:1:', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + fmtn('CurlyBracesIdentifier', '--i', ':0:1:{'), + children = { + 'Integer(val=1):0:2:1', + }, + }, + }, + }, + 'PlainKey(key=2):0:5:2', + }, + }, + { + 'Call:0:24:(', + children = { + { + fmtn('Lambda', '\\di', ':0:7:{'), + children = { + { + 'Arrow:0:8:->', + children = { + { + 'Subscript:0:20:[', + children = { + { + fmtn('DictLiteral', '-di', ':0:10: {'), + children = { + { + 'Colon:0:16::', + children = { + { + 'ComplexIdentifier:0:13:', + children = { + 'PlainIdentifier(scope=0,ident=b):0:12:b', + { + fmtn('CurlyBracesIdentifier', '--i', ':0:13:{'), + children = { + 'Integer(val=3):0:14:3', + }, + }, + }, + }, + 'Integer(val=4):0:17: 4', + }, + }, + }, + }, + 'Integer(val=5):0:21:5', + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('Curly', '{'), + hl('Number', '1'), + hl('Curly', '}'), + hl('ConcatOrSubscript', '.'), + hl('IdentifierKey', '2'), + hl('SubscriptBracket', '['), + hl('Lambda', '{'), + hl('Arrow', '->'), + hl('Dict', '{', 1), + hl('IdentifierName', 'b'), + hl('Curly', '{'), + hl('Number', '3'), + hl('Curly', '}'), + hl('Colon', ':'), + hl('Number', '4', 1), + hl('Dict', '}'), + hl('SubscriptBracket', '['), + hl('Number', '5'), + hl('SubscriptBracket', ']'), + hl('Lambda', '}'), + hl('CallingParenthesis', '('), + hl('CallingParenthesis', ')'), + hl('SubscriptBracket', ']'), + }) + + check_asgn_parsing('a', { + -- 0 + ast = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + }, + }, { + hl('IdentifierName', 'a'), + }) + + check_asgn_parsing('{a}', { + -- 012 + ast = { + { + fmtn('CurlyBracesIdentifier', '--i', ':0:0:{'), + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + }, + }, + }, + }, { + hl('FigureBrace', '{'), + hl('IdentifierName', 'a'), + hl('Curly', '}'), + }) + + check_asgn_parsing('{a}b', { + -- 0123 + ast = { + { + 'ComplexIdentifier:0:3:', + children = { + { + fmtn('CurlyBracesIdentifier', '--i', ':0:0:{'), + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + }, + }, + 'PlainIdentifier(scope=0,ident=b):0:3:b', + }, + }, + }, + }, { + hl('FigureBrace', '{'), + hl('IdentifierName', 'a'), + hl('Curly', '}'), + hl('IdentifierName', 'b'), + }) + + check_asgn_parsing('a{b}c', { + -- 01234 + ast = { + { + 'ComplexIdentifier:0:1:', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'ComplexIdentifier:0:4:', + children = { + { + fmtn('CurlyBracesIdentifier', '--i', ':0:1:{'), + children = { + 'PlainIdentifier(scope=0,ident=b):0:2:b', + }, + }, + 'PlainIdentifier(scope=0,ident=c):0:4:c', + }, + }, + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('Curly', '{'), + hl('IdentifierName', 'b'), + hl('Curly', '}'), + hl('IdentifierName', 'c'), + }) + + check_asgn_parsing('a{b}c[0]', { + -- 01234567 + ast = { + { + 'Subscript:0:5:[', + children = { + { + 'ComplexIdentifier:0:1:', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'ComplexIdentifier:0:4:', + children = { + { + fmtn('CurlyBracesIdentifier', '--i', ':0:1:{'), + children = { + 'PlainIdentifier(scope=0,ident=b):0:2:b', + }, + }, + 'PlainIdentifier(scope=0,ident=c):0:4:c', + }, + }, + }, + }, + 'Integer(val=0):0:6:0', + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('Curly', '{'), + hl('IdentifierName', 'b'), + hl('Curly', '}'), + hl('IdentifierName', 'c'), + hl('SubscriptBracket', '['), + hl('Number', '0'), + hl('SubscriptBracket', ']'), + }) + + check_asgn_parsing('a{b}c.0', { + -- 0123456 + ast = { + { + 'ConcatOrSubscript:0:5:.', + children = { + { + 'ComplexIdentifier:0:1:', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'ComplexIdentifier:0:4:', + children = { + { + fmtn('CurlyBracesIdentifier', '--i', ':0:1:{'), + children = { + 'PlainIdentifier(scope=0,ident=b):0:2:b', + }, + }, + 'PlainIdentifier(scope=0,ident=c):0:4:c', + }, + }, + }, + }, + 'PlainKey(key=0):0:6:0', + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('Curly', '{'), + hl('IdentifierName', 'b'), + hl('Curly', '}'), + hl('IdentifierName', 'c'), + hl('ConcatOrSubscript', '.'), + hl('IdentifierKey', '0'), + }) + + check_asgn_parsing('[a{b}c[0].0]', { + -- 012345678901 + -- 0 1 + ast = { + { + 'ListLiteral:0:0:[', + children = { + { + 'ConcatOrSubscript:0:9:.', + children = { + { + 'Subscript:0:6:[', + children = { + { + 'ComplexIdentifier:0:2:', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + { + 'ComplexIdentifier:0:5:', + children = { + { + fmtn('CurlyBracesIdentifier', '--i', ':0:2:{'), + children = { + 'PlainIdentifier(scope=0,ident=b):0:3:b', + }, + }, + 'PlainIdentifier(scope=0,ident=c):0:5:c', + }, + }, + }, + }, + 'Integer(val=0):0:7:0', + }, + }, + 'PlainKey(key=0):0:10:0', + }, + }, + }, + }, + }, + }, { + hl('List', '['), + hl('IdentifierName', 'a'), + hl('Curly', '{'), + hl('IdentifierName', 'b'), + hl('Curly', '}'), + hl('IdentifierName', 'c'), + hl('SubscriptBracket', '['), + hl('Number', '0'), + hl('SubscriptBracket', ']'), + hl('ConcatOrSubscript', '.'), + hl('IdentifierKey', '0'), + hl('List', ']'), + }) + + check_asgn_parsing('{a}{b}', { + -- 012345 + ast = { + { + 'ComplexIdentifier:0:3:', + children = { + { + fmtn('CurlyBracesIdentifier', '--i', ':0:0:{'), + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + }, + }, + { + fmtn('CurlyBracesIdentifier', '--i', ':0:3:{'), + children = { + 'PlainIdentifier(scope=0,ident=b):0:4:b', + }, + }, + }, + }, + }, + }, { + hl('FigureBrace', '{'), + hl('IdentifierName', 'a'), + hl('Curly', '}'), + hl('Curly', '{'), + hl('IdentifierName', 'b'), + hl('Curly', '}'), + }) + + check_asgn_parsing('a.b{c}{d}', { + -- 012345678 + ast = { + { + 'OpMissing:0:3:', + children = { + { + 'ConcatOrSubscript:0:1:.', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainKey(key=b):0:2:b', + }, + }, + { + 'ComplexIdentifier:0:6:', + children = { + { + fmtn('CurlyBracesIdentifier', '--i', ':0:3:{'), + children = { + 'PlainIdentifier(scope=0,ident=c):0:4:c', + }, + }, + { + fmtn('CurlyBracesIdentifier', '--i', ':0:6:{'), + children = { + 'PlainIdentifier(scope=0,ident=d):0:7:d', + }, + }, + }, + }, + }, + }, + }, + err = { + arg = '{c}{d}', + msg = 'E15: Missing operator: %.*s', + }, + }, { + hl('IdentifierName', 'a'), + hl('ConcatOrSubscript', '.'), + hl('IdentifierKey', 'b'), + hl('InvalidFigureBrace', '{'), + hl('IdentifierName', 'c'), + hl('Curly', '}'), + hl('Curly', '{'), + hl('IdentifierName', 'd'), + hl('Curly', '}'), + }) + + check_asgn_parsing('[a] = 1', { + -- 0123456 + ast = { + { + 'Assignment(Plain):0:3: =', + children = { + { + 'ListLiteral:0:0:[', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + }, + }, + 'Integer(val=1):0:5: 1', + }, + }, + }, + }, { + hl('List', '['), + hl('IdentifierName', 'a'), + hl('List', ']'), + hl('PlainAssignment', '=', 1), + hl('Number', '1', 1), + }) + + check_asgn_parsing('[a[b], [c, [d, [e]]]] = 1', { + -- 0123456789012345678901234 + -- 0 1 2 + ast = { + { + 'Assignment(Plain):0:21: =', + children = { + { + 'ListLiteral:0:0:[', + children = { + { + 'Comma:0:5:,', + children = { + { + 'Subscript:0:2:[', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + 'PlainIdentifier(scope=0,ident=b):0:3:b', + }, + }, + { + 'ListLiteral:0:6: [', + children = { + { + 'Comma:0:9:,', + children = { + 'PlainIdentifier(scope=0,ident=c):0:8:c', + { + 'ListLiteral:0:10: [', + children = { + { + 'Comma:0:13:,', + children = { + 'PlainIdentifier(scope=0,ident=d):0:12:d', + { + 'ListLiteral:0:14: [', + children = { + 'PlainIdentifier(scope=0,ident=e):0:16:e', + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + 'Integer(val=1):0:23: 1', + }, + }, + }, + err = { + arg = '[c, [d, [e]]]] = 1', + msg = 'E475: Nested lists not allowed when assigning: %.*s', + }, + }, { + hl('List', '['), + hl('IdentifierName', 'a'), + hl('SubscriptBracket', '['), + hl('IdentifierName', 'b'), + hl('SubscriptBracket', ']'), + hl('Comma', ','), + hl('InvalidList', '[', 1), + hl('IdentifierName', 'c'), + hl('Comma', ','), + hl('InvalidList', '[', 1), + hl('IdentifierName', 'd'), + hl('Comma', ','), + hl('InvalidList', '[', 1), + hl('IdentifierName', 'e'), + hl('List', ']'), + hl('List', ']'), + hl('List', ']'), + hl('List', ']'), + hl('PlainAssignment', '=', 1), + hl('Number', '1', 1), + }) + + check_asgn_parsing('$X += 1', { + -- 0123456 + ast = { + { + 'Assignment(Add):0:2: +=', + children = { + 'Environment(ident=X):0:0:$X', + 'Integer(val=1):0:5: 1', + }, + }, + }, + }, { + hl('EnvironmentSigil', '$'), + hl('EnvironmentName', 'X'), + hl('AssignmentWithAddition', '+=', 1), + hl('Number', '1', 1), + }) + + check_asgn_parsing('@a .= 1', { + -- 0123456 + ast = { + { + 'Assignment(Concat):0:2: .=', + children = { + 'Register(name=a):0:0:@a', + 'Integer(val=1):0:5: 1', + }, + }, + }, + }, { + hl('Register', '@a'), + hl('AssignmentWithConcatenation', '.=', 1), + hl('Number', '1', 1), + }) + + check_asgn_parsing('&option -= 1', { + -- 012345678901 + -- 0 1 + ast = { + { + 'Assignment(Subtract):0:7: -=', + children = { + 'Option(scope=0,ident=option):0:0:&option', + 'Integer(val=1):0:10: 1', + }, + }, + }, + }, { + hl('OptionSigil', '&'), + hl('OptionName', 'option'), + hl('AssignmentWithSubtraction', '-=', 1), + hl('Number', '1', 1), + }) + + check_asgn_parsing('[$X, @a, &l:option] = [1, 2, 3]', { + -- 0123456789012345678901234567890 + -- 0 1 2 3 + ast = { + { + 'Assignment(Plain):0:19: =', + children = { + { + 'ListLiteral:0:0:[', + children = { + { + 'Comma:0:3:,', + children = { + 'Environment(ident=X):0:1:$X', + { + 'Comma:0:7:,', + children = { + 'Register(name=a):0:4: @a', + 'Option(scope=l,ident=option):0:8: &l:option', + }, + }, + }, + }, + }, + }, + { + 'ListLiteral:0:21: [', + children = { + { + 'Comma:0:24:,', + children = { + 'Integer(val=1):0:23:1', + { + 'Comma:0:27:,', + children = { + 'Integer(val=2):0:25: 2', + 'Integer(val=3):0:28: 3', + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, { + hl('List', '['), + hl('EnvironmentSigil', '$'), + hl('EnvironmentName', 'X'), + hl('Comma', ','), + hl('Register', '@a', 1), + hl('Comma', ','), + hl('OptionSigil', '&', 1), + hl('OptionScope', 'l'), + hl('OptionScopeDelimiter', ':'), + hl('OptionName', 'option'), + hl('List', ']'), + hl('PlainAssignment', '=', 1), + hl('List', '[', 1), + hl('Number', '1'), + hl('Comma', ','), + hl('Number', '2', 1), + hl('Comma', ','), + hl('Number', '3', 1), + hl('List', ']'), + }) + end) +end -- cgit From f20f97c936f1438589c8176f62ce69c26e255f85 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 19 Nov 2017 21:13:27 +0300 Subject: *: Fix linter errors --- src/nvim/viml/parser/expressions.c | 21 ++++++++++++--------- test/functional/api/vim_spec.lua | 1 - test/helpers.lua | 2 +- test/unit/viml/expressions/parser_spec.lua | 10 +++++----- 4 files changed, 18 insertions(+), 16 deletions(-) diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index 0824b3ca7d..6c7c328b6d 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -2014,17 +2014,20 @@ viml_pexpr_parse_process_token: lambda_node->data.fig.type_guesses.allow_lambda = false; if (lambda_node->children != NULL && lambda_node->children->type == kExprNodeComma) { - // If lambda has comma child this means that parser has already seen at - // least "{arg1,", so node cannot possibly be anything, but lambda. - - // Vim may give E121 or E720 in this case, but it does not look right to - // have either because both are results of reevaluation possibly-lambda - // node as a dictionary and here this is not going to happen. + // If lambda has comma child this means that parser has already seen + // at least "{arg1,", so node cannot possibly be anything, but + // lambda. + + // Vim may give E121 or E720 in this case, but it does not look + // right to have either because both are results of reevaluation + // possibly-lambda node as a dictionary and here this is not going + // to happen. ERROR_FROM_TOKEN_AND_MSG( - cur_token, _("E15: Expected lambda arguments list or arrow: %.*s")); + cur_token, + _("E15: Expected lambda arguments list or arrow: %.*s")); } else { - // Else it may appear that possibly-lambda node is actually a dictionary - // or curly-braces-name identifier. + // Else it may appear that possibly-lambda node is actually + // a dictionary or curly-braces-name identifier. lambda_node = NULL; kv_drop(pt_stack, 1); } diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index 3939bc9b52..2572675a58 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -12,7 +12,6 @@ local request = helpers.request local meth_pcall = helpers.meth_pcall local command = helpers.command -local REMOVE_THIS = global_helpers.REMOVE_THIS local intchar2lua = global_helpers.intchar2lua local format_string = global_helpers.format_string local mergedicts_copy = global_helpers.mergedicts_copy diff --git a/test/helpers.lua b/test/helpers.lua index ada690a4d2..fc3936a4ce 100644 --- a/test/helpers.lua +++ b/test/helpers.lua @@ -522,7 +522,7 @@ local function fixtbl(tbl) end local function fixtbl_rec(tbl) - for k, v in pairs(tbl) do + for _, v in pairs(tbl) do if type(v) == 'table' then fixtbl_rec(v) end diff --git a/test/unit/viml/expressions/parser_spec.lua b/test/unit/viml/expressions/parser_spec.lua index 24f681f438..2ae235ca86 100644 --- a/test/unit/viml/expressions/parser_spec.lua +++ b/test/unit/viml/expressions/parser_spec.lua @@ -7,7 +7,6 @@ local make_enum_conv_tab = helpers.make_enum_conv_tab local child_call_once = helpers.child_call_once local alloc_log_new = helpers.alloc_log_new local kvi_destroy = helpers.kvi_destroy -local array_size = helpers.array_size local conv_enum = helpers.conv_enum local debug_log = helpers.debug_log local ptr2key = helpers.ptr2key @@ -26,7 +25,6 @@ local mergedicts_copy = global_helpers.mergedicts_copy local format_string = global_helpers.format_string local format_luav = global_helpers.format_luav local intchar2lua = global_helpers.intchar2lua -local REMOVE_THIS = global_helpers.REMOVE_THIS local dictdiff = global_helpers.dictdiff local lib = cimport('./src/nvim/viml/parser/expressions.h', @@ -147,12 +145,14 @@ child_call_once(function() -- nvim_hl_defs. eq(true, not not (nvim_hl_defs[grp_link] or predefined_hl_defs[grp_link])) + eq(false, not not (nvim_hl_defs[new_grp] + or predefined_hl_defs[new_grp])) nvim_hl_defs[new_grp] = {'link', grp_link} else local new_grp, grp_args = s:match('^(%w+) (.*)') neq(nil, new_grp) - eq(false, not not (nvim_hl_defs[grp_link] - or predefined_hl_defs[grp_link])) + eq(false, not not (nvim_hl_defs[new_grp] + or predefined_hl_defs[new_grp])) nvim_hl_defs[new_grp] = {'definition', grp_args} end end) @@ -206,7 +206,7 @@ local function format_check(expr, format_check_data, opts) -- That forces specific order. local zflags = opts.flags[1] local zdata = format_check_data[zflags] - local dig_len = 0 + local dig_len if opts.funcname then print(format_string('\n%s(%r, {', opts.funcname, expr)) dig_len = #opts.funcname + 2 -- cgit From 731dc82f8c04e3019ecc3243b6b512535998635b Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 19 Nov 2017 21:21:45 +0300 Subject: ex_getln: Fix memory leak in color_expr_cmdline --- src/nvim/ex_getln.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index e6fb2571c4..cabdda28cf 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -2419,6 +2419,7 @@ static void color_expr_cmdline(const CmdlineInfo *const colored_ccline, .attr = 0, })); } + kvi_destroy(colors); } /// Color command-line -- cgit From ebb33eddd9ad0e9cec5013be2e37c8f9b0546c77 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 19 Nov 2017 21:40:34 +0300 Subject: tests: Stabilize float format and %e in format_luav and format_string --- test/functional/api/vim_spec.lua | 2 +- test/helpers.lua | 15 +++++++++++++-- test/unit/viml/expressions/parser_spec.lua | 2 +- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index 2572675a58..5b5340d9e2 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -750,7 +750,7 @@ describe('api', function() typ = typ .. ('(val=%u)'):format(east_api_node.ivalue) east_api_node.ivalue = nil elseif typ == 'Float' then - typ = typ .. ('(val=%e)'):format(east_api_node.fvalue) + typ = typ .. format_string('(val=%e)', east_api_node.fvalue) east_api_node.fvalue = nil elseif typ == 'SingleQuotedString' or typ == 'DoubleQuotedString' then typ = format_string('%s(val=%q)', typ, east_api_node.svalue) diff --git a/test/helpers.lua b/test/helpers.lua index fc3936a4ce..cef05046d8 100644 --- a/test/helpers.lua +++ b/test/helpers.lua @@ -395,6 +395,13 @@ local function dedent(str, leave_indent) return str end +local function format_float(v) + -- On windows exponent appears to have three digits and not two + local ret = ('%.6e'):format(v) + local l, f, es, e = ret:match('^(%-?%d)%.(%d+)e([+%-])0*(%d%d+)$') + return l .. '.' .. f .. 'e' .. es .. e +end + local SUBTBL = { '\\000', '\\001', '\\002', '\\003', '\\004', '\\005', '\\006', '\\007', '\\008', '\\t', @@ -468,7 +475,7 @@ format_luav = function(v, indent, opts) if v % 1 == 0 then ret = ('%d'):format(v) else - ret = ('%e'):format(v) + ret = format_float(v) end elseif type(v) == 'nil' then ret = 'nil' @@ -501,7 +508,11 @@ local function format_string(fmt, ...) subfmt = subfmt:sub(1, -2) .. 's' arg = format_luav(arg) end - return subfmt:format(arg) + if subfmt == '%e' then + return format_float(arg) + else + return subfmt:format(arg) + end end) return ret end diff --git a/test/unit/viml/expressions/parser_spec.lua b/test/unit/viml/expressions/parser_spec.lua index 2ae235ca86..79b19de833 100644 --- a/test/unit/viml/expressions/parser_spec.lua +++ b/test/unit/viml/expressions/parser_spec.lua @@ -375,7 +375,7 @@ local function eastnode2lua(pstate, eastnode, checked_nodes) elseif typ == 'Integer' then typ = typ .. ('(val=%u)'):format(tonumber(eastnode.data.num.value)) elseif typ == 'Float' then - typ = typ .. ('(val=%e)'):format(tonumber(eastnode.data.flt.value)) + typ = typ .. format_string('(val=%e)', tonumber(eastnode.data.flt.value)) elseif typ == 'SingleQuotedString' or typ == 'DoubleQuotedString' then if eastnode.data.str.value == nil then typ = typ .. '(val=NULL)' -- cgit From 7c20f60b88b1df97af8ea4a6a5b9727c72748ec1 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 19 Nov 2017 21:46:54 +0300 Subject: unittests: Avoid infinite cycle somewhere because of init failure --- test/unit/helpers.lua | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/test/unit/helpers.lua b/test/unit/helpers.lua index 2c148630dd..7689c1824b 100644 --- a/test/unit/helpers.lua +++ b/test/unit/helpers.lua @@ -644,13 +644,15 @@ local function itp_child(wr, func) s = s:sub(1, hook_msglen - 2) sc.write(wr, '>' .. s .. (' '):rep(hook_msglen - 2 - #s) .. '\n') end - init() - collectgarbage('stop') - child_sethook(wr) - local err, emsg = pcall(func) - collectgarbage('restart') - collectgarbage() - debug.sethook() + local err, emsg = pcall(init) + if err then + collectgarbage('stop') + child_sethook(wr) + err, emsg = pcall(func) + collectgarbage('restart') + collectgarbage() + debug.sethook() + end emsg = tostring(emsg) sc.write(wr, trace_end_msg) if not err then -- cgit From 6ea3a08fdbb276fe64dda60c5fb934360327ed39 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 19 Nov 2017 21:55:36 +0300 Subject: syntax: Fix duplicate group definitions --- src/nvim/syntax.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index c9e99d82f8..9a537130fd 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -6067,7 +6067,6 @@ const char *const highlight_init_cmdline[] = { "default link NVimSubscript NVimParenthesis", "default link NVimSubscriptBracket NVimSubscript", "default link NVimSubscriptColon NVimSubscript", - "default link NVimSubscriptColon NVimSubscript", "default link NVimCurly NVimSubscript", "default link NVimContainer NVimParenthesis", @@ -6164,7 +6163,6 @@ const char *const highlight_init_cmdline[] = { "default link NVimInvalidSubscript NVimInvalidParenthesis", "default link NVimInvalidSubscriptBracket NVimInvalidSubscript", "default link NVimInvalidSubscriptColon NVimInvalidSubscript", - "default link NVimInvalidSubscriptColon NVimInvalidSubscript", "default link NVimInvalidCurly NVimInvalidSubscript", "default link NVimInvalidContainer NVimInvalidParenthesis", -- cgit From fe3a58273e7665e795e0402c961fad869f4e34f9 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 19 Nov 2017 22:24:26 +0300 Subject: cmake: Fix api/version test failure --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index cad3ea6786..fb0cc262b1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -68,9 +68,9 @@ set(NVIM_VERSION_PATCH 3) set(NVIM_VERSION_PRERELEASE "-dev") # for package maintainers # API level -set(NVIM_API_LEVEL 3) # Bump this after any API change. +set(NVIM_API_LEVEL 4) # Bump this after any API change. set(NVIM_API_LEVEL_COMPAT 0) # Adjust this after a _breaking_ API change. -set(NVIM_API_PRERELEASE false) +set(NVIM_API_PRERELEASE true) file(TO_CMAKE_PATH ${CMAKE_CURRENT_LIST_DIR}/.git FORCED_GIT_DIR) include(GetGitRevisionDescription) -- cgit From 64158f2b0b8f0d2e058f7be6f0a0c3faa7de5f22 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 19 Nov 2017 22:32:02 +0300 Subject: unittests: Populate ARGTYPES in child process only --- test/unit/charset/vim_str2nr_spec.lua | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/test/unit/charset/vim_str2nr_spec.lua b/test/unit/charset/vim_str2nr_spec.lua index 22504649f6..1a0a000abb 100644 --- a/test/unit/charset/vim_str2nr_spec.lua +++ b/test/unit/charset/vim_str2nr_spec.lua @@ -3,6 +3,7 @@ local global_helpers = require('test.helpers') local itp = helpers.gen_itp(it) +local child_call_once = helpers.child_call_once local cimport = helpers.cimport local ffi = helpers.ffi @@ -11,12 +12,16 @@ local updated = global_helpers.updated local lib = cimport('./src/nvim/charset.h') -local ARGTYPES = { - num = ffi.typeof('varnumber_T[1]'), - unum = ffi.typeof('uvarnumber_T[1]'), - pre = ffi.typeof('int[1]'), - len = ffi.typeof('int[1]'), -} +local ARGTYPES + +child_call_once(function() + ARGTYPES = { + num = ffi.typeof('varnumber_T[1]'), + unum = ffi.typeof('uvarnumber_T[1]'), + pre = ffi.typeof('int[1]'), + len = ffi.typeof('int[1]'), + } +end) local icnt = -42 local ucnt = 4242 -- cgit From 1ffa4e50477eb8a95c7097973c4d13d97551d784 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 19 Nov 2017 23:33:02 +0300 Subject: doc: Update documentation --- runtime/doc/eval.txt | 121 ++++++++++++++++++++++++++++++++++++++++++++++- runtime/doc/vim_diff.txt | 13 ++--- 2 files changed, 127 insertions(+), 7 deletions(-) diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index b752667d9a..090eb022e2 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -10506,7 +10506,7 @@ option will still be marked as it was set in the sandbox. In a few situations it is not allowed to change the text in the buffer, jump to another window and some other things that might confuse or break what Vim is currently doing. This mostly applies to things that happen when Vim is -actually doing something else. For example, evaluating the 'balloonexpr' may +actually doing something else. For example, evaluating the 'balloonexpr' may happen any moment the mouse cursor is resting at some position. This is not allowed when the textlock is active: @@ -10516,5 +10516,124 @@ This is not allowed when the textlock is active: - closing a window or quitting Vim - etc. +============================================================================== +13. Command-line expressions coloring *expr-coloring* + +Expressions entered by user in |i_CTRL-R_=|, |c_CTRL-\_e|, |quote=| are +colored by built-in expressions parser. It uses highlight groups described in +the below table, which may be overriden by user colorschemes, all linked to +some other highlighting group. + *hl-NVimInvalid* +In addition to highlighting groups prefixed with NVim described below there +are highlighting groups prefixed with NVimInvalid which have just the same +meaning, but used to indicate that relevant token contains an error or that +error had occurred just before it. They have mostly the same hierarchy, +except that by default in place of any non-NVim-prefixed group NVimInvalid +linking to `Error` is used and some other intermediate groups are present. + +Group Default link Colored expression ~ +*hl-NVimInternalError* None, red/red Parser bug + +*hl-NVimAssignment* Operator Generic assignment +*hl-NVimPlainAssignment* NVimAssignment `=` in |:let| +*hl-NVimAugmentedAssignment* NVimAssignment Generic, `+=`/`-=`/`.=` +*hl-NVimAssignmentWithAddition* NVimAugmentedAssignment `+=` in |:let+=| +*hl-NVimAssignmentWithSubtraction* NVimAugmentedAssignment `-=` in |:let-=| +*hl-NVimAssignmentWithConcatenation* NVimAugmentedAssignment `.=` in |:let.=| + +*hl-NVimOperator* Operator Generic operator + +*hl-NVimUnaryOperator* NVimOperator Generic unary op +*hl-NVimUnaryPlus* NVimUnaryOperator |expr-unary-+| +*hl-NVimUnaryMinus* NVimUnaryOperator |expr-unary--| +*hl-NVimNot* NVimUnaryOperator |expr-!| + +*hl-NVimBinaryOperator* NVimOperator Generic binary op +*hl-NVimComparison* NVimBinaryOperator Any |expr4| operator +*hl-NVimComparisonModifier* NVimComparison `#`/`?` near |expr4| op +*hl-NVimBinaryPlus* NVimBinaryOperator |expr-+| +*hl-NVimBinaryMinus* NVimBinaryOperator |expr--| +*hl-NVimConcat* NVimBinaryOperator |expr-.| +*hl-NVimConcatOrSubscript* NVimConcat |expr-.| or |expr-entry| +*hl-NVimOr* NVimBinaryOperator |expr-barbar| +*hl-NVimAnd* NVimBinaryOperator |expr-&&| +*hl-NVimMultiplication* NVimBinaryOperator |expr-star| +*hl-NVimDivision* NVimBinaryOperator |expr-/| +*hl-NVimMod* NVimBinaryOperator |expr-%| + +*hl-NVimTernary* NVimOperator `?` in |expr1| +*hl-NVimTernaryColon* NVimTernary `:` in |expr1| + +*hl-NVimParenthesis* Delimiter Generic bracket +*hl-NVimLambda* NVimParenthesis `{`/`}` in |lambda| +*hl-NVimNestingParenthesis* NVimParenthesis `(`/`)` in |expr-nesting| +*hl-NVimCallingParenthesis* NVimParenthesis `(`/`)` in |expr-function| + +*hl-NVimSubscript* NVimParenthesis Generic subscript +*hl-NVimSubscriptBracket* NVimSubscript `[`/`]` in |expr-[]| +*hl-NVimSubscriptColon* NVimSubscript `:` in |expr-[:]| +*hl-NVimCurly* NVimSubscript `{`/`}` in + |curly-braces-names| + +*hl-NVimContainer* NVimParenthesis Generic container +*hl-NVimDict* NVimContainer `{`/`}` in |dict| literal +*hl-NVimList* NVimContainer `[`/`]` in |list| literal + +*hl-NVimIdentifier* Identifier Generic identifier +*hl-NVimIdentifierScope* NVimIdentifier Namespace: letter + before `:` in + |internal-variables| +*hl-NVimIdentifierScopeDelimiter* NVimIdentifier `:` after namespace + letter +*hl-NVimIdentifierName* NVimIdentifier Rest of the ident +*hl-NVimIdentifierKey* NVimIdentifier Identifier after + |expr-entry| + +*hl-NVimColon* Delimiter `:` in |dict| literal +*hl-NVimComma* Delimiter `,` in |dict|/|list| + literal or + |expr-function| +*hl-NVimArrow* Delimiter `->` in |lambda| + +*hl-NVimRegister* SpecialChar |expr-register| +*hl-NVimNumber* Number Non-prefix digits + in integer + |expr-number| +*hl-NVimNumberPrefix* Type `0` for |octal-number| + `0x` for |hex-number| + `0b` for |binary-number| +*hl-NVimFloat* NVimNumber Floating-point + number + +*hl-NVimOptionSigil* Type `&` in |expr-option| +*hl-NVimOptionScope* NVimIdentifierScope Option scope if any +*hl-NVimOptionScopeDelimiter* NVimIdentifierScopeDelimiter + `:` after option scope +*hl-NVimOptionName* NVimIdentifier Option name + +*hl-NVimEnvironmentSigil* NVimOptionSigil `$` in |expr-env| +*hl-NVimEnvironmentName* NVimIdentifier Env variable name + +*hl-NVimString* String Generic string +*hl-NVimStringBody* NVimString Generic string + literal body +*hl-NVimStringQuote* NVimString Generic string quote +*hl-NVimStringSpecial* SpecialChar Generic string + non-literal body + +*hl-NVimSingleQuote* NVimStringQuote `'` in |expr-'| +*hl-NVimSingleQuotedBody* NVimStringBody Literal part of + |expr-'| string body +*hl-NVimSingleQuotedQuote* NVimStringSpecial `''` inside |expr-'| + string body + +*hl-NVimDoubleQuote* NVimStringQuote `"` in |expr-quote| +*hl-NVimDoubleQuotedBody* NVimStringBody Literal part of + |expr-quote| body +*hl-NVimDoubleQuotedEscape* NVimStringSpecial Valid |expr-quote| + escape sequence +*hl-NVimDoubleQuotedUnknownEscape* NVimInvalidValue Unrecognized + |expr-quote| escape + sequence vim:tw=78:ts=8:noet:ft=help:norl: diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index 026ff6a0fb..22e6f11bb5 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -167,12 +167,13 @@ Highlight groups: |hl-Whitespace| highlights 'listchars' whitespace UI: - *E5408* *E5409* *g:Nvim_color_expr* *g:Nvim_color_cmdline* - Command-line coloring is supported. Only |input()| and |inputdialog()| may - be colored. For testing purposes expressions (e.g. |i_CTRL-R_=|) and regular - command-line (|:|) are colored by callbacks defined in `g:Nvim_color_expr` - and `g:Nvim_color_cmdline` respectively (these callbacks are for testing - only, and will be removed in a future version). + *E5408* *E5409* *g:Nvim_color_cmdline* + Command-line coloring is supported. Only |input()| and |inputdialog()| may + be colored by user. For testing purposes regular command-line (|:|) is + colored by callback defined in `g:Nvim_color_cmdline` (this callback is for + testing only, and will be removed in a future version). Additionally + expression command-line is colored using built-in expressions parser (it is + not yet used for actually parsing expressions though), see |expr-coloring|. ============================================================================== 4. Changed features *nvim-features-changed* -- cgit From 05a3c12118a6dae0ac8f3603f9ee4d9fd9450cce Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 19 Nov 2017 23:36:40 +0300 Subject: unittests: Run vim_str2nr tests with GC enabled --- test/unit/charset/vim_str2nr_spec.lua | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/unit/charset/vim_str2nr_spec.lua b/test/unit/charset/vim_str2nr_spec.lua index 1a0a000abb..394934c339 100644 --- a/test/unit/charset/vim_str2nr_spec.lua +++ b/test/unit/charset/vim_str2nr_spec.lua @@ -56,6 +56,12 @@ local function test_vim_str2nr(s, what, exp, maxlen) end end +local _itp = itp +itp = function(...) + collectgarbage('restart') + _itp(...) +end + describe('vim_str2nr()', function() itp('works fine when it has nothing to do', function() test_vim_str2nr('', 0, {len = 0, num = 0, unum = 0, pre = 0}, 0) -- cgit From 11a05e778fd5f33f791ca8047f9839caa15b8da5 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 26 Nov 2017 15:56:27 +0300 Subject: doc: Some small fixes --- runtime/doc/eval.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index 2e04c1d96e..46a7d92730 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -10578,13 +10578,13 @@ This is not allowed when the textlock is active: Expressions entered by user in |i_CTRL-R_=|, |c_CTRL-\_e|, |quote=| are colored by built-in expressions parser. It uses highlight groups described in -the below table, which may be overriden by user colorschemes, all linked to +the table below, which may be overriden by user colorschemes, all linked to some other highlighting group. *hl-NVimInvalid* In addition to highlighting groups prefixed with NVim described below there are highlighting groups prefixed with NVimInvalid which have just the same -meaning, but used to indicate that relevant token contains an error or that -error had occurred just before it. They have mostly the same hierarchy, +meaning, but used to indicate that the relevant token contains an error or +that error had occurred just before it. They have mostly the same hierarchy, except that by default in place of any non-NVim-prefixed group NVimInvalid linking to `Error` is used and some other intermediate groups are present. -- cgit From 17077b68133a62d0dc1b84cb48779464c117e028 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 26 Nov 2017 16:08:53 +0300 Subject: viml/parser/expressions: Make $ENV not depend on &isident --- src/nvim/api/vim.c | 2 +- src/nvim/viml/parser/expressions.c | 19 +++++++++++-------- test/functional/api/vim_spec.lua | 3 +++ 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 7838f9faa3..a0816dbc8e 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -983,7 +983,7 @@ typedef kvec_withinit_t(ExprASTConvStackItem, 16) ExprASTConvStack; /// "DoubleQuotedString" nodes. Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, Error *err) - FUNC_API_SINCE(4) + FUNC_API_SINCE(4) FUNC_API_ASYNC { int pflags = 0; for (size_t i = 0 ; i < flags.size ; i++) { diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index 6c7c328b6d..63ad6bab35 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -47,6 +47,8 @@ // type of what is in the first expression is generally not known when // parsing, so to have separate expressions like this separate them with // spaces. +// 7. 'isident' no longer applies to environment variables, they always include +// ASCII alphanumeric characters and underscore and nothing except this. #include #include @@ -383,10 +385,14 @@ LexExprToken viml_pexpr_next_token(ParserState *const pstate, const int flags) break; } +#define ISWORD_OR_AUTOLOAD(x) \ + (ASCII_ISALNUM(x) || (x) == AUTOLOAD_CHAR || (x) == '_') +#define ISWORD(x) \ + (ASCII_ISALNUM(x) || (x) == '_') + // Environment variable. case '$': { - // FIXME: Parser function can’t be thread-safe with vim_isIDc. - CHARREG(kExprLexEnv, vim_isIDc); + CHARREG(kExprLexEnv, ISWORD); break; } @@ -400,10 +406,6 @@ LexExprToken viml_pexpr_next_token(ParserState *const pstate, const int flags) case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': case '_': { -#define ISWORD_OR_AUTOLOAD(x) \ - (ASCII_ISALNUM(x) || (x) == AUTOLOAD_CHAR || (x) == '_') -#define ISWORD(x) \ - (ASCII_ISALNUM(x) || (x) == '_') ret.data.var.scope = 0; ret.data.var.autoload = false; CHARREG(kExprLexPlainIdentifier, ISWORD); @@ -441,9 +443,10 @@ LexExprToken viml_pexpr_next_token(ParserState *const pstate, const int flags) CHARREG(kExprLexPlainIdentifier, ISWORD_OR_AUTOLOAD); } break; -#undef ISWORD_OR_AUTOLOAD -#undef ISWORD } + +#undef ISWORD +#undef ISWORD_OR_AUTOLOAD #undef CHARREG // Option. diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index 5b5340d9e2..841b7a584a 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -717,6 +717,9 @@ describe('api', function() end) describe('nvim_parse_expression', function() + before_each(function() + meths.set_option('isident', '') + end) local function simplify_east_api_node(line, east_api_node) if east_api_node == NIL then return nil -- cgit From cddf84c3982b8225f1592b6a61b63f8d1883ca94 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 26 Nov 2017 16:45:29 +0300 Subject: functests: Add some more tests --- src/nvim/syntax.c | 16 ++--- test/functional/ui/cmdline_highlight_spec.lua | 87 ++++++++++++++++++++++++-- test/unit/viml/expressions/parser_tests.lua | 88 +++++++++++++++++++++++++++ 3 files changed, 179 insertions(+), 12 deletions(-) diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index 8e5a119b1f..55ff49d160 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -6170,18 +6170,18 @@ const char *const highlight_init_cmdline[] = { "default link NVimInvalidDict NVimInvalidContainer", "default link NVimInvalidList NVimInvalidContainer", - "default link NVimInvalidIdentifier Identifier", - "default link NVimInvalidIdentifierScope NVimIdentifier", - "default link NVimInvalidIdentifierScopeDelimiter NVimIdentifier", - "default link NVimInvalidIdentifierName NVimIdentifier", - "default link NVimInvalidIdentifierKey NVimIdentifier", + "default link NVimInvalidValue NVimInvalid", + + "default link NVimInvalidIdentifier NVimInvalidValue", + "default link NVimInvalidIdentifierScope NVimInvalidIdentifier", + "default link NVimInvalidIdentifierScopeDelimiter NVimInvalidIdentifier", + "default link NVimInvalidIdentifierName NVimInvalidIdentifier", + "default link NVimInvalidIdentifierKey NVimInvalidIdentifier", "default link NVimInvalidColon NVimInvalidDelimiter", "default link NVimInvalidComma NVimInvalidDelimiter", "default link NVimInvalidArrow NVimInvalidDelimiter", - "default link NVimInvalidValue NVimInvalid", - "default link NVimInvalidRegister NVimInvalidValue", "default link NVimInvalidNumber NVimInvalidValue", "default link NVimInvalidFloat NVimInvalidNumber", @@ -6199,7 +6199,7 @@ const char *const highlight_init_cmdline[] = { // Invalid string bodies and specials are still highlighted as valid ones to // minimize the red area. "default link NVimInvalidString NVimInvalidValue", - "default link NVimInvalidStringBody NVimString", + "default link NVimInvalidStringBody NVimStringBody", "default link NVimInvalidStringQuote NVimInvalidString", "default link NVimInvalidStringSpecial NVimStringSpecial", diff --git a/test/functional/ui/cmdline_highlight_spec.lua b/test/functional/ui/cmdline_highlight_spec.lua index 023673738d..54d27723f0 100644 --- a/test/functional/ui/cmdline_highlight_spec.lua +++ b/test/functional/ui/cmdline_highlight_spec.lua @@ -147,6 +147,10 @@ before_each(function() PE={bold = true, foreground = Screen.colors.SeaGreen4}, NUM={foreground = Screen.colors.Blue2}, NPAR={foreground = Screen.colors.Yellow}, + SQ={foreground = Screen.colors.Blue3}, + SB={foreground = Screen.colors.Blue4}, + E={foreground = Screen.colors.Red, background = Screen.colors.Blue}, + M={bold = true}, }) end) @@ -898,8 +902,83 @@ describe('Expressions coloring support', function() ={NUM:1}^ | ]]) end) - -- FIXME: Test expr coloring when using -u NORC and -u NONE. - -- FIXME: Test different ways of triggering expression highlighting (:=, - -- i=, :e, "=). - -- FIXME: Test with various invalid unicode and multibyte characters. + it('works correctly with non-ASCII and control characters', function() + meths.command('hi clear NVimStringBody') + meths.command('hi clear NVimStringQuote') + meths.command('hi clear NVimInvalid') + meths.command('hi NVimStringQuote guifg=Blue3') + meths.command('hi NVimStringBody guifg=Blue4') + meths.command('hi NVimInvalid guifg=Red guibg=Blue') + feed('i="«»"«»') + screen:expect([[ + | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + ={SQ:"}{SB:«»}{SQ:"}{E:«»}^ | + ]]) + feed('') + screen:expect([[ + ^ | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {M:-- INSERT --} | + ]]) + feed('') + screen:expect([[ + ^ | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + | + ]]) + feed(':e""') + -- TODO(ZyX-I): Parser highlighting should not override special character + -- highlighting. + screen:expect([[ + | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + ={SQ:"}{SB:^X}{SQ:"}{ERR:^X}^ | + ]]) + feed('') + screen:expect([[ + | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + :^ | + ]]) + funcs.setreg('a', {'\192'}) + feed('="a"a"foo"') + -- TODO(ZyX-I): Parser highlighting should not override special character + -- highlighting. + screen:expect([[ + | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + ={SQ:"}{SB:}{SQ:"}{E:"}{SB:foo}{E:"}^ | + ]]) + end) end) diff --git a/test/unit/viml/expressions/parser_tests.lua b/test/unit/viml/expressions/parser_tests.lua index f0c2723a38..4700b6ee42 100644 --- a/test/unit/viml/expressions/parser_tests.lua +++ b/test/unit/viml/expressions/parser_tests.lua @@ -8182,4 +8182,92 @@ return function(itp, _check_parsing, hl, fmtn) hl('List', ']'), }) end) + itp('works with non-ASCII characters', function() + check_parsing('"«»"«»', { + -- 013568 + ast = { + { + 'OpMissing:0:6:', + children = { + 'DoubleQuotedString(val="«»"):0:0:"«»"', + { + 'ComplexIdentifier:0:8:', + children = { + 'PlainIdentifier(scope=0,ident=«):0:6:«', + 'PlainIdentifier(scope=0,ident=»):0:8:»', + }, + }, + }, + }, + }, + err = { + arg = '«»', + msg = 'E15: Unidentified character: %.*s', + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedBody', '«»'), + hl('DoubleQuote', '"'), + hl('InvalidIdentifierName', '«'), + hl('InvalidIdentifierName', '»'), + }, { + [1] = { + ast = { + ast = { + 'DoubleQuotedString(val="«»"):0:0:"«»"', + }, + len = 6, + }, + hl_fs = { + [5] = REMOVE_THIS, + [4] = REMOVE_THIS, + }, + }, + }) + check_parsing('"\192"\192"foo"', { + -- 01 23 45678 + ast = { + { + 'OpMissing:0:3:', + children = { + 'DoubleQuotedString(val="\192"):0:0:"\192"', + { + 'OpMissing:0:4:', + children = { + 'PlainIdentifier(scope=0,ident=\192):0:3:\192', + 'DoubleQuotedString(val="foo"):0:4:"foo"', + }, + }, + }, + }, + }, + err = { + arg = '\192"foo"', + msg = 'E15: Unidentified character: %.*s', + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedBody', '\192'), + hl('DoubleQuote', '"'), + hl('InvalidIdentifierName', '\192'), + hl('InvalidDoubleQuote', '"'), + hl('InvalidDoubleQuotedBody', 'foo'), + hl('InvalidDoubleQuote', '"'), + }, { + [1] = { + ast = { + ast = { + 'DoubleQuotedString(val="\192"):0:0:"\192"', + }, + len = 3, + }, + hl_fs = { + [4] = REMOVE_THIS, + [5] = REMOVE_THIS, + [6] = REMOVE_THIS, + [7] = REMOVE_THIS, + }, + }, + }) + end) end -- cgit From 36a4f3a259ffa282129b18358cce4130397077c5 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 26 Nov 2017 16:57:42 +0300 Subject: viml/parser/expressions: Make sure that listed nodes may be present With the new test leaving `assert(false);` for any of the cases makes tests crash. --- src/nvim/viml/parser/expressions.c | 5 +--- test/unit/viml/expressions/parser_tests.lua | 44 +++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index 63ad6bab35..9773e60bbd 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -3065,12 +3065,9 @@ viml_pexpr_parse_end: // to be caught later. break; } + case kExprNodeSubscript: case kExprNodeConcatOrSubscript: case kExprNodeComplexIdentifier: - case kExprNodeSubscript: { - // FIXME: Investigate whether above are OK to be present in the stack. - break; - } case kExprNodeAssignment: case kExprNodeMod: case kExprNodeDivision: diff --git a/test/unit/viml/expressions/parser_tests.lua b/test/unit/viml/expressions/parser_tests.lua index 4700b6ee42..e085d7e932 100644 --- a/test/unit/viml/expressions/parser_tests.lua +++ b/test/unit/viml/expressions/parser_tests.lua @@ -4228,6 +4228,50 @@ return function(itp, _check_parsing, hl, fmtn) hl('ConcatOrSubscript', '.'), hl('Number', '1', 1), }) + + check_parsing('a[1][2][3[4', { + -- 01234567890 + -- 0 1 + ast = { + { + 'Subscript:0:7:[', + children = { + { + 'Subscript:0:4:[', + children = { + { + 'Subscript:0:1:[', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'Integer(val=1):0:2:1', + }, + }, + 'Integer(val=2):0:5:2', + }, + }, + { + 'Subscript:0:9:[', + children = { + 'Integer(val=3):0:8:3', + 'Integer(val=4):0:10:4', + }, + }, + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('SubscriptBracket', '['), + hl('Number', '1'), + hl('SubscriptBracket', ']'), + hl('SubscriptBracket', '['), + hl('Number', '2'), + hl('SubscriptBracket', ']'), + hl('SubscriptBracket', '['), + hl('Number', '3'), + hl('SubscriptBracket', '['), + hl('Number', '4'), + }) end) itp('works with bracket subscripts', function() check_parsing(':', { -- cgit From de45ec0146486c49719ff6f6dcceb4914b471c7a Mon Sep 17 00:00:00 2001 From: ZyX Date: Thu, 30 Nov 2017 02:01:49 +0300 Subject: keymap: Do not use vim_isIDc in keymap.c Note: there are three changes to ascii_isident. Reverting first two (in find_special_key and first in get_special_key_code) normally fails the new test with empty &isident, but reverting the third does not. Hence adding `>` to &isident. Ref vim/vim#2389. --- src/nvim/ascii.h | 13 +++++++++++++ src/nvim/keymap.c | 6 +++--- src/nvim/viml/parser/expressions.c | 9 +++------ test/functional/ex_cmds/map_spec.lua | 21 +++++++++++++++++++++ test/symbolic/klee/nvim/charset.c | 5 ----- test/symbolic/klee/nvim/keymap.c | 7 ++++--- 6 files changed, 44 insertions(+), 17 deletions(-) create mode 100644 test/functional/ex_cmds/map_spec.lua diff --git a/src/nvim/ascii.h b/src/nvim/ascii.h index adde91f9ec..9ccb70764d 100644 --- a/src/nvim/ascii.h +++ b/src/nvim/ascii.h @@ -3,6 +3,7 @@ #include +#include "nvim/macros.h" #include "nvim/func_attr.h" #include "nvim/os/os_defs.h" @@ -98,6 +99,10 @@ static inline bool ascii_isxdigit(int) REAL_FATTR_CONST REAL_FATTR_ALWAYS_INLINE; +static inline bool ascii_isident(int) + REAL_FATTR_CONST + REAL_FATTR_ALWAYS_INLINE; + static inline bool ascii_isbdigit(int) REAL_FATTR_CONST REAL_FATTR_ALWAYS_INLINE; @@ -138,6 +143,14 @@ static inline bool ascii_isxdigit(int c) || (c >= 'A' && c <= 'F'); } +/// Checks if `c` is an “identifier” character +/// +/// That is, whether it is alphanumeric character or underscore. +static inline bool ascii_isident(const int c) +{ + return ASCII_ISALNUM(c) || c == '_'; +} + /// Checks if `c` is a binary digit, that is, 0-1. /// /// @see {ascii_isdigit} diff --git a/src/nvim/keymap.c b/src/nvim/keymap.c index aca21c20a5..0eb5a41b1e 100644 --- a/src/nvim/keymap.c +++ b/src/nvim/keymap.c @@ -567,7 +567,7 @@ int find_special_key(const char_u **srcp, const size_t src_len, int *const modp, // Find end of modifier list last_dash = src; - for (bp = src + 1; bp <= end && (*bp == '-' || vim_isIDc(*bp)); bp++) { + for (bp = src + 1; bp <= end && (*bp == '-' || ascii_isident(*bp)); bp++) { if (*bp == '-') { last_dash = bp; if (bp + 1 <= end) { @@ -721,12 +721,12 @@ int get_special_key_code(const char_u *name) for (int i = 0; key_names_table[i].name != NULL; i++) { const char *const table_name = key_names_table[i].name; int j; - for (j = 0; vim_isIDc(name[j]) && table_name[j] != NUL; j++) { + for (j = 0; ascii_isident(name[j]) && table_name[j] != NUL; j++) { if (TOLOWER_ASC(table_name[j]) != TOLOWER_ASC(name[j])) { break; } } - if (!vim_isIDc(name[j]) && table_name[j] == NUL) { + if (!ascii_isident(name[j]) && table_name[j] == NUL) { return key_names_table[i].key; } } diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index 9773e60bbd..cfcde6bb38 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -386,13 +386,11 @@ LexExprToken viml_pexpr_next_token(ParserState *const pstate, const int flags) } #define ISWORD_OR_AUTOLOAD(x) \ - (ASCII_ISALNUM(x) || (x) == AUTOLOAD_CHAR || (x) == '_') -#define ISWORD(x) \ - (ASCII_ISALNUM(x) || (x) == '_') + (ascii_isident(x) || (x) == AUTOLOAD_CHAR) // Environment variable. case '$': { - CHARREG(kExprLexEnv, ISWORD); + CHARREG(kExprLexEnv, ascii_isident); break; } @@ -408,7 +406,7 @@ LexExprToken viml_pexpr_next_token(ParserState *const pstate, const int flags) case '_': { ret.data.var.scope = 0; ret.data.var.autoload = false; - CHARREG(kExprLexPlainIdentifier, ISWORD); + CHARREG(kExprLexPlainIdentifier, ascii_isident); // "is" and "isnot" operators. if (!(flags & kELFlagIsNotCmp) && ((ret.len == 2 && memcmp(pline.data, "is", 2) == 0) @@ -445,7 +443,6 @@ LexExprToken viml_pexpr_next_token(ParserState *const pstate, const int flags) break; } -#undef ISWORD #undef ISWORD_OR_AUTOLOAD #undef CHARREG diff --git a/test/functional/ex_cmds/map_spec.lua b/test/functional/ex_cmds/map_spec.lua new file mode 100644 index 0000000000..b46f83405e --- /dev/null +++ b/test/functional/ex_cmds/map_spec.lua @@ -0,0 +1,21 @@ +local helpers = require("test.functional.helpers")(after_each) + +local eq = helpers.eq +local feed = helpers.feed +local meths = helpers.meths +local clear = helpers.clear +local command = helpers.command + +describe(':*map', function() + before_each(clear) + + it('are not affected by &isident', function() + meths.set_var('counter', 0) + command('nnoremap :let counter+=1') + meths.set_option('isident', ('%u'):format(('>'):byte())) + command('nnoremap :let counter+=1') + -- &isident used to disable keycode parsing here as well + feed('\24\25') + eq(4, meths.get_var('counter')) + end) +end) diff --git a/test/symbolic/klee/nvim/charset.c b/test/symbolic/klee/nvim/charset.c index 77f690f08d..95853a6834 100644 --- a/test/symbolic/klee/nvim/charset.c +++ b/test/symbolic/klee/nvim/charset.c @@ -6,11 +6,6 @@ #include "nvim/eval/typval.h" #include "nvim/vim.h" -bool vim_isIDc(int c) -{ - return ASCII_ISALNUM(c); -} - int hex2nr(int c) { if ((c >= 'a') && (c <= 'f')) { diff --git a/test/symbolic/klee/nvim/keymap.c b/test/symbolic/klee/nvim/keymap.c index ff5e46e75b..a341a73689 100644 --- a/test/symbolic/klee/nvim/keymap.c +++ b/test/symbolic/klee/nvim/keymap.c @@ -2,6 +2,7 @@ #include "nvim/types.h" #include "nvim/keymap.h" +#include "nvim/ascii.h" #include "nvim/eval/typval.h" #define MOD_KEYS_ENTRY_SIZE 5 @@ -294,12 +295,12 @@ int get_special_key_code(const char_u *name) for (int i = 0; key_names_table[i].name != NULL; i++) { const char *const table_name = key_names_table[i].name; int j; - for (j = 0; vim_isIDc(name[j]) && table_name[j] != NUL; j++) { + for (j = 0; ascii_isident(name[j]) && table_name[j] != NUL; j++) { if (TOLOWER_ASC(table_name[j]) != TOLOWER_ASC(name[j])) { break; } } - if (!vim_isIDc(name[j]) && table_name[j] == NUL) { + if (!ascii_isident(name[j]) && table_name[j] == NUL) { return key_names_table[i].key; } } @@ -386,7 +387,7 @@ int find_special_key(const char_u **srcp, const size_t src_len, int *const modp, // Find end of modifier list last_dash = src; - for (bp = src + 1; bp <= end && (*bp == '-' || vim_isIDc(*bp)); bp++) { + for (bp = src + 1; bp <= end && (*bp == '-' || ascii_isident(*bp)); bp++) { if (*bp == '-') { last_dash = bp; if (bp + 1 <= end) { -- cgit From 0b4054e043257137ccfd3b2207da48910ce32a5f Mon Sep 17 00:00:00 2001 From: ZyX Date: Thu, 30 Nov 2017 11:44:48 +0300 Subject: unittests: Reduce memory used by vim_str2nr test --- test/README.md | 3 +++ test/unit/charset/vim_str2nr_spec.lua | 32 +++++++++++++++++++++++++++----- test/unit/helpers.lua | 2 ++ 3 files changed, 32 insertions(+), 5 deletions(-) diff --git a/test/README.md b/test/README.md index 6ad70e2f45..1cb814d85b 100644 --- a/test/README.md +++ b/test/README.md @@ -344,3 +344,6 @@ function calls, returns and lua source lines exuecuted. `NVIM_TEST_TRACE_ON_ERROR` (U) (1): makes unit tests yield trace on error in addition to regular error message. + +`NVIM_TEST_MAXTRACE` (U) (N): specifies maximum number of trace lines to keep. +Default is 1024. diff --git a/test/unit/charset/vim_str2nr_spec.lua b/test/unit/charset/vim_str2nr_spec.lua index 394934c339..a6fe613563 100644 --- a/test/unit/charset/vim_str2nr_spec.lua +++ b/test/unit/charset/vim_str2nr_spec.lua @@ -1,5 +1,6 @@ local helpers = require("test.unit.helpers")(after_each) local global_helpers = require('test.helpers') +local bit = require('bit') local itp = helpers.gen_itp(it) @@ -36,15 +37,36 @@ local function arginit(arg) end end +local function argreset(arg, args) + if arg == 'unum' then + ucnt = ucnt + 1 + args[arg][0] = ucnt + else + icnt = icnt - 1 + args[arg][0] = icnt + end +end + local function test_vim_str2nr(s, what, exp, maxlen) - local comb = {[''] = {}} + local comb = {[{}] = true} + local bits = {} for k, _ in pairs(exp) do - for ck, cv in pairs(comb) do - comb[ck .. ',' .. k] = updated(shallowcopy(cv), { [k] = arginit(k) }) - end + bits[#bits + 1] = k end maxlen = maxlen or #s - for _, cv in pairs(comb) do + local args = {} + for k, _ in pairs(ARGTYPES) do + args[k] = arginit(k) + end + for case = 0, ((2 ^ (#bits)) - 1) do + local cv = {} + for b = 0, (#bits - 1) do + if bit.band(case, (2 ^ b)) == 0 then + local k = bits[b + 1] + argreset(k, args) + cv[k] = args[k] + end + end lib.vim_str2nr(s, cv.pre, cv.len, what, cv.num, cv.unum, maxlen) for cck, ccv in pairs(cv) do if exp[cck] ~= tonumber(ccv[0]) then diff --git a/test/unit/helpers.lua b/test/unit/helpers.lua index c26b1c1bcb..b1e709c444 100644 --- a/test/unit/helpers.lua +++ b/test/unit/helpers.lua @@ -675,6 +675,7 @@ end local function check_child_err(rd) local trace = {} local did_traceline = false + local maxtrace = tonumber(os.getenv('NVIM_TEST_MAXTRACE')) or 1024 while true do local traceline = sc.read(rd, hook_msglen) if #traceline ~= hook_msglen then @@ -689,6 +690,7 @@ local function check_child_err(rd) break end trace[#trace + 1] = traceline + table.remove(trace, maxtrace + 1) end local res = sc.read(rd, 2) if #res ~= 2 then -- cgit From 5ab0f988caffad5e8c87a075cbd3f91f0f7e002c Mon Sep 17 00:00:00 2001 From: ZyX Date: Thu, 30 Nov 2017 11:53:25 +0300 Subject: *: Replace all occurrences of NVim with Nvim --- runtime/doc/eval.txt | 154 +++++------ src/nvim/syntax.c | 364 +++++++++++++------------- src/nvim/viml/parser/expressions.c | 4 +- test/functional/api/vim_spec.lua | 2 +- test/functional/ui/cmdline_highlight_spec.lua | 24 +- test/unit/viml/expressions/parser_spec.lua | 16 +- 6 files changed, 282 insertions(+), 282 deletions(-) diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index 46a7d92730..2b2fda25e9 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -10580,116 +10580,116 @@ Expressions entered by user in |i_CTRL-R_=|, |c_CTRL-\_e|, |quote=| are colored by built-in expressions parser. It uses highlight groups described in the table below, which may be overriden by user colorschemes, all linked to some other highlighting group. - *hl-NVimInvalid* -In addition to highlighting groups prefixed with NVim described below there -are highlighting groups prefixed with NVimInvalid which have just the same + *hl-NvimInvalid* +In addition to highlighting groups prefixed with Nvim described below there +are highlighting groups prefixed with NvimInvalid which have just the same meaning, but used to indicate that the relevant token contains an error or that error had occurred just before it. They have mostly the same hierarchy, -except that by default in place of any non-NVim-prefixed group NVimInvalid +except that by default in place of any non-Nvim-prefixed group NvimInvalid linking to `Error` is used and some other intermediate groups are present. Group Default link Colored expression ~ -*hl-NVimInternalError* None, red/red Parser bug - -*hl-NVimAssignment* Operator Generic assignment -*hl-NVimPlainAssignment* NVimAssignment `=` in |:let| -*hl-NVimAugmentedAssignment* NVimAssignment Generic, `+=`/`-=`/`.=` -*hl-NVimAssignmentWithAddition* NVimAugmentedAssignment `+=` in |:let+=| -*hl-NVimAssignmentWithSubtraction* NVimAugmentedAssignment `-=` in |:let-=| -*hl-NVimAssignmentWithConcatenation* NVimAugmentedAssignment `.=` in |:let.=| - -*hl-NVimOperator* Operator Generic operator - -*hl-NVimUnaryOperator* NVimOperator Generic unary op -*hl-NVimUnaryPlus* NVimUnaryOperator |expr-unary-+| -*hl-NVimUnaryMinus* NVimUnaryOperator |expr-unary--| -*hl-NVimNot* NVimUnaryOperator |expr-!| - -*hl-NVimBinaryOperator* NVimOperator Generic binary op -*hl-NVimComparison* NVimBinaryOperator Any |expr4| operator -*hl-NVimComparisonModifier* NVimComparison `#`/`?` near |expr4| op -*hl-NVimBinaryPlus* NVimBinaryOperator |expr-+| -*hl-NVimBinaryMinus* NVimBinaryOperator |expr--| -*hl-NVimConcat* NVimBinaryOperator |expr-.| -*hl-NVimConcatOrSubscript* NVimConcat |expr-.| or |expr-entry| -*hl-NVimOr* NVimBinaryOperator |expr-barbar| -*hl-NVimAnd* NVimBinaryOperator |expr-&&| -*hl-NVimMultiplication* NVimBinaryOperator |expr-star| -*hl-NVimDivision* NVimBinaryOperator |expr-/| -*hl-NVimMod* NVimBinaryOperator |expr-%| - -*hl-NVimTernary* NVimOperator `?` in |expr1| -*hl-NVimTernaryColon* NVimTernary `:` in |expr1| - -*hl-NVimParenthesis* Delimiter Generic bracket -*hl-NVimLambda* NVimParenthesis `{`/`}` in |lambda| -*hl-NVimNestingParenthesis* NVimParenthesis `(`/`)` in |expr-nesting| -*hl-NVimCallingParenthesis* NVimParenthesis `(`/`)` in |expr-function| - -*hl-NVimSubscript* NVimParenthesis Generic subscript -*hl-NVimSubscriptBracket* NVimSubscript `[`/`]` in |expr-[]| -*hl-NVimSubscriptColon* NVimSubscript `:` in |expr-[:]| -*hl-NVimCurly* NVimSubscript `{`/`}` in +*hl-NvimInternalError* None, red/red Parser bug + +*hl-NvimAssignment* Operator Generic assignment +*hl-NvimPlainAssignment* NvimAssignment `=` in |:let| +*hl-NvimAugmentedAssignment* NvimAssignment Generic, `+=`/`-=`/`.=` +*hl-NvimAssignmentWithAddition* NvimAugmentedAssignment `+=` in |:let+=| +*hl-NvimAssignmentWithSubtraction* NvimAugmentedAssignment `-=` in |:let-=| +*hl-NvimAssignmentWithConcatenation* NvimAugmentedAssignment `.=` in |:let.=| + +*hl-NvimOperator* Operator Generic operator + +*hl-NvimUnaryOperator* NvimOperator Generic unary op +*hl-NvimUnaryPlus* NvimUnaryOperator |expr-unary-+| +*hl-NvimUnaryMinus* NvimUnaryOperator |expr-unary--| +*hl-NvimNot* NvimUnaryOperator |expr-!| + +*hl-NvimBinaryOperator* NvimOperator Generic binary op +*hl-NvimComparison* NvimBinaryOperator Any |expr4| operator +*hl-NvimComparisonModifier* NvimComparison `#`/`?` near |expr4| op +*hl-NvimBinaryPlus* NvimBinaryOperator |expr-+| +*hl-NvimBinaryMinus* NvimBinaryOperator |expr--| +*hl-NvimConcat* NvimBinaryOperator |expr-.| +*hl-NvimConcatOrSubscript* NvimConcat |expr-.| or |expr-entry| +*hl-NvimOr* NvimBinaryOperator |expr-barbar| +*hl-NvimAnd* NvimBinaryOperator |expr-&&| +*hl-NvimMultiplication* NvimBinaryOperator |expr-star| +*hl-NvimDivision* NvimBinaryOperator |expr-/| +*hl-NvimMod* NvimBinaryOperator |expr-%| + +*hl-NvimTernary* NvimOperator `?` in |expr1| +*hl-NvimTernaryColon* NvimTernary `:` in |expr1| + +*hl-NvimParenthesis* Delimiter Generic bracket +*hl-NvimLambda* NvimParenthesis `{`/`}` in |lambda| +*hl-NvimNestingParenthesis* NvimParenthesis `(`/`)` in |expr-nesting| +*hl-NvimCallingParenthesis* NvimParenthesis `(`/`)` in |expr-function| + +*hl-NvimSubscript* NvimParenthesis Generic subscript +*hl-NvimSubscriptBracket* NvimSubscript `[`/`]` in |expr-[]| +*hl-NvimSubscriptColon* NvimSubscript `:` in |expr-[:]| +*hl-NvimCurly* NvimSubscript `{`/`}` in |curly-braces-names| -*hl-NVimContainer* NVimParenthesis Generic container -*hl-NVimDict* NVimContainer `{`/`}` in |dict| literal -*hl-NVimList* NVimContainer `[`/`]` in |list| literal +*hl-NvimContainer* NvimParenthesis Generic container +*hl-NvimDict* NvimContainer `{`/`}` in |dict| literal +*hl-NvimList* NvimContainer `[`/`]` in |list| literal -*hl-NVimIdentifier* Identifier Generic identifier -*hl-NVimIdentifierScope* NVimIdentifier Namespace: letter +*hl-NvimIdentifier* Identifier Generic identifier +*hl-NvimIdentifierScope* NvimIdentifier Namespace: letter before `:` in |internal-variables| -*hl-NVimIdentifierScopeDelimiter* NVimIdentifier `:` after namespace +*hl-NvimIdentifierScopeDelimiter* NvimIdentifier `:` after namespace letter -*hl-NVimIdentifierName* NVimIdentifier Rest of the ident -*hl-NVimIdentifierKey* NVimIdentifier Identifier after +*hl-NvimIdentifierName* NvimIdentifier Rest of the ident +*hl-NvimIdentifierKey* NvimIdentifier Identifier after |expr-entry| -*hl-NVimColon* Delimiter `:` in |dict| literal -*hl-NVimComma* Delimiter `,` in |dict|/|list| +*hl-NvimColon* Delimiter `:` in |dict| literal +*hl-NvimComma* Delimiter `,` in |dict|/|list| literal or |expr-function| -*hl-NVimArrow* Delimiter `->` in |lambda| +*hl-NvimArrow* Delimiter `->` in |lambda| -*hl-NVimRegister* SpecialChar |expr-register| -*hl-NVimNumber* Number Non-prefix digits +*hl-NvimRegister* SpecialChar |expr-register| +*hl-NvimNumber* Number Non-prefix digits in integer |expr-number| -*hl-NVimNumberPrefix* Type `0` for |octal-number| +*hl-NvimNumberPrefix* Type `0` for |octal-number| `0x` for |hex-number| `0b` for |binary-number| -*hl-NVimFloat* NVimNumber Floating-point +*hl-NvimFloat* NvimNumber Floating-point number -*hl-NVimOptionSigil* Type `&` in |expr-option| -*hl-NVimOptionScope* NVimIdentifierScope Option scope if any -*hl-NVimOptionScopeDelimiter* NVimIdentifierScopeDelimiter +*hl-NvimOptionSigil* Type `&` in |expr-option| +*hl-NvimOptionScope* NvimIdentifierScope Option scope if any +*hl-NvimOptionScopeDelimiter* NvimIdentifierScopeDelimiter `:` after option scope -*hl-NVimOptionName* NVimIdentifier Option name +*hl-NvimOptionName* NvimIdentifier Option name -*hl-NVimEnvironmentSigil* NVimOptionSigil `$` in |expr-env| -*hl-NVimEnvironmentName* NVimIdentifier Env variable name +*hl-NvimEnvironmentSigil* NvimOptionSigil `$` in |expr-env| +*hl-NvimEnvironmentName* NvimIdentifier Env variable name -*hl-NVimString* String Generic string -*hl-NVimStringBody* NVimString Generic string +*hl-NvimString* String Generic string +*hl-NvimStringBody* NvimString Generic string literal body -*hl-NVimStringQuote* NVimString Generic string quote -*hl-NVimStringSpecial* SpecialChar Generic string +*hl-NvimStringQuote* NvimString Generic string quote +*hl-NvimStringSpecial* SpecialChar Generic string non-literal body -*hl-NVimSingleQuote* NVimStringQuote `'` in |expr-'| -*hl-NVimSingleQuotedBody* NVimStringBody Literal part of +*hl-NvimSingleQuote* NvimStringQuote `'` in |expr-'| +*hl-NvimSingleQuotedBody* NvimStringBody Literal part of |expr-'| string body -*hl-NVimSingleQuotedQuote* NVimStringSpecial `''` inside |expr-'| +*hl-NvimSingleQuotedQuote* NvimStringSpecial `''` inside |expr-'| string body -*hl-NVimDoubleQuote* NVimStringQuote `"` in |expr-quote| -*hl-NVimDoubleQuotedBody* NVimStringBody Literal part of +*hl-NvimDoubleQuote* NvimStringQuote `"` in |expr-quote| +*hl-NvimDoubleQuotedBody* NvimStringBody Literal part of |expr-quote| body -*hl-NVimDoubleQuotedEscape* NVimStringSpecial Valid |expr-quote| +*hl-NvimDoubleQuotedEscape* NvimStringSpecial Valid |expr-quote| escape sequence -*hl-NVimDoubleQuotedUnknownEscape* NVimInvalidValue Unrecognized +*hl-NvimDoubleQuotedUnknownEscape* NvimInvalidValue Unrecognized |expr-quote| escape sequence diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index 55ff49d160..d1a5f0bd1c 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -6025,204 +6025,204 @@ const char *const highlight_init_cmdline[] = { // XXX When modifying a list modify it in both valid and invalid halfs. // TODO(ZyX-I): merge valid and invalid groups via a macros. - // NVimInternalError should appear only when highlighter has a bug. - "NVimInternalError ctermfg=Red ctermbg=Red guifg=Red guibg=Red", + // NvimInternalError should appear only when highlighter has a bug. + "NvimInternalError ctermfg=Red ctermbg=Red guifg=Red guibg=Red", // Highlight groups (links) used by parser: - "default link NVimAssignment Operator", - "default link NVimPlainAssignment NVimAssignment", - "default link NVimAugmentedAssignment NVimAssignment", - "default link NVimAssignmentWithAddition NVimAugmentedAssignment", - "default link NVimAssignmentWithSubtraction NVimAugmentedAssignment", - "default link NVimAssignmentWithConcatenation NVimAugmentedAssignment", - - "default link NVimOperator Operator", - - "default link NVimUnaryOperator NVimOperator", - "default link NVimUnaryPlus NVimUnaryOperator", - "default link NVimUnaryMinus NVimUnaryOperator", - "default link NVimNot NVimUnaryOperator", - - "default link NVimBinaryOperator NVimOperator", - "default link NVimComparison NVimBinaryOperator", - "default link NVimComparisonModifier NVimComparison", - "default link NVimBinaryPlus NVimBinaryOperator", - "default link NVimBinaryMinus NVimBinaryOperator", - "default link NVimConcat NVimBinaryOperator", - "default link NVimConcatOrSubscript NVimConcat", - "default link NVimOr NVimBinaryOperator", - "default link NVimAnd NVimBinaryOperator", - "default link NVimMultiplication NVimBinaryOperator", - "default link NVimDivision NVimBinaryOperator", - "default link NVimMod NVimBinaryOperator", - - "default link NVimTernary NVimOperator", - "default link NVimTernaryColon NVimTernary", - - "default link NVimParenthesis Delimiter", - "default link NVimLambda NVimParenthesis", - "default link NVimNestingParenthesis NVimParenthesis", - "default link NVimCallingParenthesis NVimParenthesis", - - "default link NVimSubscript NVimParenthesis", - "default link NVimSubscriptBracket NVimSubscript", - "default link NVimSubscriptColon NVimSubscript", - "default link NVimCurly NVimSubscript", - - "default link NVimContainer NVimParenthesis", - "default link NVimDict NVimContainer", - "default link NVimList NVimContainer", - - "default link NVimIdentifier Identifier", - "default link NVimIdentifierScope NVimIdentifier", - "default link NVimIdentifierScopeDelimiter NVimIdentifier", - "default link NVimIdentifierName NVimIdentifier", - "default link NVimIdentifierKey NVimIdentifier", - - "default link NVimColon Delimiter", - "default link NVimComma Delimiter", - "default link NVimArrow Delimiter", - - "default link NVimRegister SpecialChar", - "default link NVimNumber Number", - "default link NVimFloat NVimNumber", - "default link NVimNumberPrefix Type", - - "default link NVimOptionSigil Type", - "default link NVimOptionName NVimIdentifier", - "default link NVimOptionScope NVimIdentifierScope", - "default link NVimOptionScopeDelimiter NVimIdentifierScopeDelimiter", - - "default link NVimEnvironmentSigil NVimOptionSigil", - "default link NVimEnvironmentName NVimIdentifier", - - "default link NVimString String", - "default link NVimStringBody NVimString", - "default link NVimStringQuote NVimString", - "default link NVimStringSpecial SpecialChar", - - "default link NVimSingleQuote NVimStringQuote", - "default link NVimSingleQuotedBody NVimStringBody", - "default link NVimSingleQuotedQuote NVimStringSpecial", - - "default link NVimDoubleQuote NVimStringQuote", - "default link NVimDoubleQuotedBody NVimStringBody", - "default link NVimDoubleQuotedEscape NVimStringSpecial", - - "default link NVimFigureBrace NVimInternalError", - "default link NVimSingleQuotedUnknownEscape NVimInternalError", - - "default link NVimSpacing Normal", - - // NVimInvalid groups: - - "default link NVimInvalidSingleQuotedUnknownEscape NVimInternalError", - - "default link NVimInvalid Error", - - "default link NVimInvalidAssignment NVimInvalid", - "default link NVimInvalidPlainAssignment NVimInvalidAssignment", - "default link NVimInvalidAugmentedAssignment NVimInvalidAssignment", - "default link NVimInvalidAssignmentWithAddition " - "NVimInvalidAugmentedAssignment", - "default link NVimInvalidAssignmentWithSubtraction " - "NVimInvalidAugmentedAssignment", - "default link NVimInvalidAssignmentWithConcatenation " - "NVimInvalidAugmentedAssignment", - - "default link NVimInvalidOperator NVimInvalid", - - "default link NVimInvalidUnaryOperator NVimInvalidOperator", - "default link NVimInvalidUnaryPlus NVimInvalidUnaryOperator", - "default link NVimInvalidUnaryMinus NVimInvalidUnaryOperator", - "default link NVimInvalidNot NVimInvalidUnaryOperator", - - "default link NVimInvalidBinaryOperator NVimInvalidOperator", - "default link NVimInvalidComparison NVimInvalidBinaryOperator", - "default link NVimInvalidComparisonModifier NVimInvalidComparison", - "default link NVimInvalidBinaryPlus NVimInvalidBinaryOperator", - "default link NVimInvalidBinaryMinus NVimInvalidBinaryOperator", - "default link NVimInvalidConcat NVimInvalidBinaryOperator", - "default link NVimInvalidConcatOrSubscript NVimInvalidConcat", - "default link NVimInvalidOr NVimInvalidBinaryOperator", - "default link NVimInvalidAnd NVimInvalidBinaryOperator", - "default link NVimInvalidMultiplication NVimInvalidBinaryOperator", - "default link NVimInvalidDivision NVimInvalidBinaryOperator", - "default link NVimInvalidMod NVimInvalidBinaryOperator", - - "default link NVimInvalidTernary NVimInvalidOperator", - "default link NVimInvalidTernaryColon NVimInvalidTernary", - - "default link NVimInvalidDelimiter NVimInvalid", - - "default link NVimInvalidParenthesis NVimInvalidDelimiter", - "default link NVimInvalidLambda NVimInvalidParenthesis", - "default link NVimInvalidNestingParenthesis NVimInvalidParenthesis", - "default link NVimInvalidCallingParenthesis NVimInvalidParenthesis", - - "default link NVimInvalidSubscript NVimInvalidParenthesis", - "default link NVimInvalidSubscriptBracket NVimInvalidSubscript", - "default link NVimInvalidSubscriptColon NVimInvalidSubscript", - "default link NVimInvalidCurly NVimInvalidSubscript", - - "default link NVimInvalidContainer NVimInvalidParenthesis", - "default link NVimInvalidDict NVimInvalidContainer", - "default link NVimInvalidList NVimInvalidContainer", - - "default link NVimInvalidValue NVimInvalid", - - "default link NVimInvalidIdentifier NVimInvalidValue", - "default link NVimInvalidIdentifierScope NVimInvalidIdentifier", - "default link NVimInvalidIdentifierScopeDelimiter NVimInvalidIdentifier", - "default link NVimInvalidIdentifierName NVimInvalidIdentifier", - "default link NVimInvalidIdentifierKey NVimInvalidIdentifier", - - "default link NVimInvalidColon NVimInvalidDelimiter", - "default link NVimInvalidComma NVimInvalidDelimiter", - "default link NVimInvalidArrow NVimInvalidDelimiter", - - "default link NVimInvalidRegister NVimInvalidValue", - "default link NVimInvalidNumber NVimInvalidValue", - "default link NVimInvalidFloat NVimInvalidNumber", - "default link NVimInvalidNumberPrefix NVimInvalidNumber", - - "default link NVimInvalidOptionSigil NVimInvalidIdentifier", - "default link NVimInvalidOptionName NVimInvalidIdentifier", - "default link NVimInvalidOptionScope NVimInvalidIdentifierScope", - "default link NVimInvalidOptionScopeDelimiter " - "NVimInvalidIdentifierScopeDelimiter", - - "default link NVimInvalidEnvironmentSigil NVimInvalidOptionSigil", - "default link NVimInvalidEnvironmentName NVimInvalidIdentifier", + "default link NvimAssignment Operator", + "default link NvimPlainAssignment NvimAssignment", + "default link NvimAugmentedAssignment NvimAssignment", + "default link NvimAssignmentWithAddition NvimAugmentedAssignment", + "default link NvimAssignmentWithSubtraction NvimAugmentedAssignment", + "default link NvimAssignmentWithConcatenation NvimAugmentedAssignment", + + "default link NvimOperator Operator", + + "default link NvimUnaryOperator NvimOperator", + "default link NvimUnaryPlus NvimUnaryOperator", + "default link NvimUnaryMinus NvimUnaryOperator", + "default link NvimNot NvimUnaryOperator", + + "default link NvimBinaryOperator NvimOperator", + "default link NvimComparison NvimBinaryOperator", + "default link NvimComparisonModifier NvimComparison", + "default link NvimBinaryPlus NvimBinaryOperator", + "default link NvimBinaryMinus NvimBinaryOperator", + "default link NvimConcat NvimBinaryOperator", + "default link NvimConcatOrSubscript NvimConcat", + "default link NvimOr NvimBinaryOperator", + "default link NvimAnd NvimBinaryOperator", + "default link NvimMultiplication NvimBinaryOperator", + "default link NvimDivision NvimBinaryOperator", + "default link NvimMod NvimBinaryOperator", + + "default link NvimTernary NvimOperator", + "default link NvimTernaryColon NvimTernary", + + "default link NvimParenthesis Delimiter", + "default link NvimLambda NvimParenthesis", + "default link NvimNestingParenthesis NvimParenthesis", + "default link NvimCallingParenthesis NvimParenthesis", + + "default link NvimSubscript NvimParenthesis", + "default link NvimSubscriptBracket NvimSubscript", + "default link NvimSubscriptColon NvimSubscript", + "default link NvimCurly NvimSubscript", + + "default link NvimContainer NvimParenthesis", + "default link NvimDict NvimContainer", + "default link NvimList NvimContainer", + + "default link NvimIdentifier Identifier", + "default link NvimIdentifierScope NvimIdentifier", + "default link NvimIdentifierScopeDelimiter NvimIdentifier", + "default link NvimIdentifierName NvimIdentifier", + "default link NvimIdentifierKey NvimIdentifier", + + "default link NvimColon Delimiter", + "default link NvimComma Delimiter", + "default link NvimArrow Delimiter", + + "default link NvimRegister SpecialChar", + "default link NvimNumber Number", + "default link NvimFloat NvimNumber", + "default link NvimNumberPrefix Type", + + "default link NvimOptionSigil Type", + "default link NvimOptionName NvimIdentifier", + "default link NvimOptionScope NvimIdentifierScope", + "default link NvimOptionScopeDelimiter NvimIdentifierScopeDelimiter", + + "default link NvimEnvironmentSigil NvimOptionSigil", + "default link NvimEnvironmentName NvimIdentifier", + + "default link NvimString String", + "default link NvimStringBody NvimString", + "default link NvimStringQuote NvimString", + "default link NvimStringSpecial SpecialChar", + + "default link NvimSingleQuote NvimStringQuote", + "default link NvimSingleQuotedBody NvimStringBody", + "default link NvimSingleQuotedQuote NvimStringSpecial", + + "default link NvimDoubleQuote NvimStringQuote", + "default link NvimDoubleQuotedBody NvimStringBody", + "default link NvimDoubleQuotedEscape NvimStringSpecial", + + "default link NvimFigureBrace NvimInternalError", + "default link NvimSingleQuotedUnknownEscape NvimInternalError", + + "default link NvimSpacing Normal", + + // NvimInvalid groups: + + "default link NvimInvalidSingleQuotedUnknownEscape NvimInternalError", + + "default link NvimInvalid Error", + + "default link NvimInvalidAssignment NvimInvalid", + "default link NvimInvalidPlainAssignment NvimInvalidAssignment", + "default link NvimInvalidAugmentedAssignment NvimInvalidAssignment", + "default link NvimInvalidAssignmentWithAddition " + "NvimInvalidAugmentedAssignment", + "default link NvimInvalidAssignmentWithSubtraction " + "NvimInvalidAugmentedAssignment", + "default link NvimInvalidAssignmentWithConcatenation " + "NvimInvalidAugmentedAssignment", + + "default link NvimInvalidOperator NvimInvalid", + + "default link NvimInvalidUnaryOperator NvimInvalidOperator", + "default link NvimInvalidUnaryPlus NvimInvalidUnaryOperator", + "default link NvimInvalidUnaryMinus NvimInvalidUnaryOperator", + "default link NvimInvalidNot NvimInvalidUnaryOperator", + + "default link NvimInvalidBinaryOperator NvimInvalidOperator", + "default link NvimInvalidComparison NvimInvalidBinaryOperator", + "default link NvimInvalidComparisonModifier NvimInvalidComparison", + "default link NvimInvalidBinaryPlus NvimInvalidBinaryOperator", + "default link NvimInvalidBinaryMinus NvimInvalidBinaryOperator", + "default link NvimInvalidConcat NvimInvalidBinaryOperator", + "default link NvimInvalidConcatOrSubscript NvimInvalidConcat", + "default link NvimInvalidOr NvimInvalidBinaryOperator", + "default link NvimInvalidAnd NvimInvalidBinaryOperator", + "default link NvimInvalidMultiplication NvimInvalidBinaryOperator", + "default link NvimInvalidDivision NvimInvalidBinaryOperator", + "default link NvimInvalidMod NvimInvalidBinaryOperator", + + "default link NvimInvalidTernary NvimInvalidOperator", + "default link NvimInvalidTernaryColon NvimInvalidTernary", + + "default link NvimInvalidDelimiter NvimInvalid", + + "default link NvimInvalidParenthesis NvimInvalidDelimiter", + "default link NvimInvalidLambda NvimInvalidParenthesis", + "default link NvimInvalidNestingParenthesis NvimInvalidParenthesis", + "default link NvimInvalidCallingParenthesis NvimInvalidParenthesis", + + "default link NvimInvalidSubscript NvimInvalidParenthesis", + "default link NvimInvalidSubscriptBracket NvimInvalidSubscript", + "default link NvimInvalidSubscriptColon NvimInvalidSubscript", + "default link NvimInvalidCurly NvimInvalidSubscript", + + "default link NvimInvalidContainer NvimInvalidParenthesis", + "default link NvimInvalidDict NvimInvalidContainer", + "default link NvimInvalidList NvimInvalidContainer", + + "default link NvimInvalidValue NvimInvalid", + + "default link NvimInvalidIdentifier NvimInvalidValue", + "default link NvimInvalidIdentifierScope NvimInvalidIdentifier", + "default link NvimInvalidIdentifierScopeDelimiter NvimInvalidIdentifier", + "default link NvimInvalidIdentifierName NvimInvalidIdentifier", + "default link NvimInvalidIdentifierKey NvimInvalidIdentifier", + + "default link NvimInvalidColon NvimInvalidDelimiter", + "default link NvimInvalidComma NvimInvalidDelimiter", + "default link NvimInvalidArrow NvimInvalidDelimiter", + + "default link NvimInvalidRegister NvimInvalidValue", + "default link NvimInvalidNumber NvimInvalidValue", + "default link NvimInvalidFloat NvimInvalidNumber", + "default link NvimInvalidNumberPrefix NvimInvalidNumber", + + "default link NvimInvalidOptionSigil NvimInvalidIdentifier", + "default link NvimInvalidOptionName NvimInvalidIdentifier", + "default link NvimInvalidOptionScope NvimInvalidIdentifierScope", + "default link NvimInvalidOptionScopeDelimiter " + "NvimInvalidIdentifierScopeDelimiter", + + "default link NvimInvalidEnvironmentSigil NvimInvalidOptionSigil", + "default link NvimInvalidEnvironmentName NvimInvalidIdentifier", // Invalid string bodies and specials are still highlighted as valid ones to // minimize the red area. - "default link NVimInvalidString NVimInvalidValue", - "default link NVimInvalidStringBody NVimStringBody", - "default link NVimInvalidStringQuote NVimInvalidString", - "default link NVimInvalidStringSpecial NVimStringSpecial", + "default link NvimInvalidString NvimInvalidValue", + "default link NvimInvalidStringBody NvimStringBody", + "default link NvimInvalidStringQuote NvimInvalidString", + "default link NvimInvalidStringSpecial NvimStringSpecial", - "default link NVimInvalidSingleQuote NVimInvalidStringQuote", - "default link NVimInvalidSingleQuotedBody NVimInvalidStringBody", - "default link NVimInvalidSingleQuotedQuote NVimInvalidStringSpecial", + "default link NvimInvalidSingleQuote NvimInvalidStringQuote", + "default link NvimInvalidSingleQuotedBody NvimInvalidStringBody", + "default link NvimInvalidSingleQuotedQuote NvimInvalidStringSpecial", - "default link NVimInvalidDoubleQuote NVimInvalidStringQuote", - "default link NVimInvalidDoubleQuotedBody NVimInvalidStringBody", - "default link NVimInvalidDoubleQuotedEscape NVimInvalidStringSpecial", - "default link NVimInvalidDoubleQuotedUnknownEscape NVimInvalidValue", + "default link NvimInvalidDoubleQuote NvimInvalidStringQuote", + "default link NvimInvalidDoubleQuotedBody NvimInvalidStringBody", + "default link NvimInvalidDoubleQuotedEscape NvimInvalidStringSpecial", + "default link NvimInvalidDoubleQuotedUnknownEscape NvimInvalidValue", - "default link NVimInvalidFigureBrace NVimInvalidDelimiter", + "default link NvimInvalidFigureBrace NvimInvalidDelimiter", - "default link NVimInvalidSpacing ErrorMsg", + "default link NvimInvalidSpacing ErrorMsg", // Not actually invalid, but we highlight user that he is doing something // wrong. - "default link NVimDoubleQuotedUnknownEscape NVimInvalidValue", + "default link NvimDoubleQuotedUnknownEscape NvimInvalidValue", NULL, }; -/// Create default links for NVim* highlight groups used for cmdline coloring +/// Create default links for Nvim* highlight groups used for cmdline coloring void syn_init_cmdline_highlight(bool reset, bool init) { for (size_t i = 0 ; highlight_init_cmdline[i] != NULL ; i++) { diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index cfcde6bb38..4196ecb9d2 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -1326,7 +1326,7 @@ static inline ParserPosition recol_pos(const ParserPosition pos, } /// Get highlight group name -#define HL(g) (is_invalid ? "NVimInvalid" #g : "NVim" #g) +#define HL(g) (is_invalid ? "NvimInvalid" #g : "Nvim" #g) /// Highlight current token with the given group #define HL_CUR_TOKEN(g) \ @@ -2570,7 +2570,7 @@ viml_pexpr_parse_bracket_closing_error: new_top_node, _("E15: Don't know what figure brace means: %.*s")); if (pstate->colors) { - // Will reset to NVimInvalidFigureBrace. + // Will reset to NvimInvalidFigureBrace. kv_A(*pstate->colors, new_top_node->data.fig.opening_hl_idx).group = ( HL(FigureBrace)); diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index 841b7a584a..ff28e3d133 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -885,7 +885,7 @@ describe('api', function() return function(next_col) local col = next_col + (shift or 0) return (('%s:%u:%u:%s'):format( - 'NVim' .. group, + 'Nvim' .. group, 0, col, str)), (col + #str) diff --git a/test/functional/ui/cmdline_highlight_spec.lua b/test/functional/ui/cmdline_highlight_spec.lua index 54d27723f0..73fe94c056 100644 --- a/test/functional/ui/cmdline_highlight_spec.lua +++ b/test/functional/ui/cmdline_highlight_spec.lua @@ -869,10 +869,10 @@ describe('Ex commands coloring support', function() end) describe('Expressions coloring support', function() it('works', function() - meths.command('hi clear NVimNumber') - meths.command('hi clear NVimNestingParenthesis') - meths.command('hi NVimNumber guifg=Blue2') - meths.command('hi NVimNestingParenthesis guifg=Yellow') + meths.command('hi clear NvimNumber') + meths.command('hi clear NvimNestingParenthesis') + meths.command('hi NvimNumber guifg=Blue2') + meths.command('hi NvimNestingParenthesis guifg=Yellow') feed(':echo =(((1)))') screen:expect([[ | @@ -888,8 +888,8 @@ describe('Expressions coloring support', function() it('does not use Nvim_color_expr', function() meths.set_var('Nvim_color_expr', 42) -- Used to error out due to failing to get callback. - meths.command('hi clear NVimNumber') - meths.command('hi NVimNumber guifg=Blue2') + meths.command('hi clear NvimNumber') + meths.command('hi NvimNumber guifg=Blue2') feed(':=1') screen:expect([[ | @@ -903,12 +903,12 @@ describe('Expressions coloring support', function() ]]) end) it('works correctly with non-ASCII and control characters', function() - meths.command('hi clear NVimStringBody') - meths.command('hi clear NVimStringQuote') - meths.command('hi clear NVimInvalid') - meths.command('hi NVimStringQuote guifg=Blue3') - meths.command('hi NVimStringBody guifg=Blue4') - meths.command('hi NVimInvalid guifg=Red guibg=Blue') + meths.command('hi clear NvimStringBody') + meths.command('hi clear NvimStringQuote') + meths.command('hi clear NvimInvalid') + meths.command('hi NvimStringQuote guifg=Blue3') + meths.command('hi NvimStringBody guifg=Blue4') + meths.command('hi NvimInvalid guifg=Red guibg=Blue') feed('i="«»"«»') screen:expect([[ | diff --git a/test/unit/viml/expressions/parser_spec.lua b/test/unit/viml/expressions/parser_spec.lua index 79b19de833..73388e5dd2 100644 --- a/test/unit/viml/expressions/parser_spec.lua +++ b/test/unit/viml/expressions/parser_spec.lua @@ -164,14 +164,14 @@ child_call_once(function() i = i + 1 end for k, _ in ipairs(nvim_hl_defs) do - eq('NVim', k:sub(1, 4)) - -- NVimInvalid + eq('Nvim', k:sub(1, 4)) + -- NvimInvalid -- 12345678901 local err, msg = pcall(function() if k:sub(5, 11) == 'Invalid' then - neq(nil, nvim_hl_defs['NVim' .. k:sub(12)]) + neq(nil, nvim_hl_defs['Nvim' .. k:sub(12)]) else - neq(nil, nvim_hl_defs['NVimInvalid' .. k:sub(5)]) + neq(nil, nvim_hl_defs['NvimInvalid' .. k:sub(5)]) end end) if not err then @@ -185,7 +185,7 @@ local function hls_to_hl_fs(hls) local ret = {} local next_col = 0 for i, v in ipairs(hls) do - local group, line, col, str = v:match('^NVim([a-zA-Z]+):(%d+):(%d+):(.*)$') + local group, line, col, str = v:match('^Nvim([a-zA-Z]+):(%d+):(%d+):(.*)$') col = tonumber(col) line = tonumber(line) assert(line == 0) @@ -521,12 +521,12 @@ describe('Expressions parser', function() end local function hl(group, str, shift) return function(next_col) - if nvim_hl_defs['NVim' .. group] == nil then - error(('Unknown group: NVim%s'):format(group)) + if nvim_hl_defs['Nvim' .. group] == nil then + error(('Unknown group: Nvim%s'):format(group)) end local col = next_col + (shift or 0) return (('%s:%u:%u:%s'):format( - 'NVim' .. group, + 'Nvim' .. group, 0, col, str)), (col + #str) -- cgit From 62993323494d846190590606d37aff1136421671 Mon Sep 17 00:00:00 2001 From: ZyX Date: Fri, 1 Dec 2017 00:34:16 +0300 Subject: fix! set lsan options --- .travis.yml | 1 + src/nvim/eval/encode.c | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 2bab1635ad..a32532d21a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -39,6 +39,7 @@ env: - ASAN_OPTIONS="detect_leaks=1:check_initialization_order=1:log_path=$LOG_DIR/asan" - TSAN_OPTIONS="log_path=$LOG_DIR/tsan" - UBSAN_OPTIONS="print_stacktrace=1 log_path=$LOG_DIR/ubsan" + - LSAN_OPTIONS="verbosity=1:log_threads=1" # Environment variables for Valgrind. - VALGRIND_LOG="$LOG_DIR/valgrind-%p.log" # Cache marker for third-party dependencies cache. diff --git a/src/nvim/eval/encode.c b/src/nvim/eval/encode.c index ef647b3ee4..650f11a989 100644 --- a/src/nvim/eval/encode.c +++ b/src/nvim/eval/encode.c @@ -316,7 +316,7 @@ int encode_read_from_list(ListReaderState *const state, char *const buf, #define TYPVAL_ENCODE_CONV_FLOAT(tv, flt) \ do { \ const float_T flt_ = (flt); \ - switch (fpclassify(flt_)) { \ + switch (fpclassify((double)flt_)) { \ case FP_NAN: { \ ga_concat(gap, (char_u *) "str2float('nan')"); \ break; \ -- cgit From 6bc54832efcb8c269875dfa4eecd89e1984ead69 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 3 Dec 2017 16:53:29 +0300 Subject: Revert "fix! set lsan options" This reverts commit 62993323494d846190590606d37aff1136421671. --- .travis.yml | 1 - src/nvim/eval/encode.c | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index a32532d21a..2bab1635ad 100644 --- a/.travis.yml +++ b/.travis.yml @@ -39,7 +39,6 @@ env: - ASAN_OPTIONS="detect_leaks=1:check_initialization_order=1:log_path=$LOG_DIR/asan" - TSAN_OPTIONS="log_path=$LOG_DIR/tsan" - UBSAN_OPTIONS="print_stacktrace=1 log_path=$LOG_DIR/ubsan" - - LSAN_OPTIONS="verbosity=1:log_threads=1" # Environment variables for Valgrind. - VALGRIND_LOG="$LOG_DIR/valgrind-%p.log" # Cache marker for third-party dependencies cache. diff --git a/src/nvim/eval/encode.c b/src/nvim/eval/encode.c index 650f11a989..ef647b3ee4 100644 --- a/src/nvim/eval/encode.c +++ b/src/nvim/eval/encode.c @@ -316,7 +316,7 @@ int encode_read_from_list(ListReaderState *const state, char *const buf, #define TYPVAL_ENCODE_CONV_FLOAT(tv, flt) \ do { \ const float_T flt_ = (flt); \ - switch (fpclassify((double)flt_)) { \ + switch (fpclassify(flt_)) { \ case FP_NAN: { \ ga_concat(gap, (char_u *) "str2float('nan')"); \ break; \ -- cgit From d763d2fe7aae1c531c72c1d542cad2b19719929b Mon Sep 17 00:00:00 2001 From: FlorianGit Date: Thu, 9 Nov 2017 21:31:17 +0100 Subject: Viml: Make filter and map handle null list correct filter('v:_null_list, 'v:val') should return v:_null_list and a similar statement should hold for map. Changes after review * Test inserted in legacy test suite has been removed by reverting the commit adding it. * Change the fix to tv_copy the argument before returning. * Readd the two tests on crashes, and modified their expected return value. * Move the test from 'incorrect behaviour' section to 'correct behaviour section' * Add analogous tests for v:_null_dict Always copy list or dictionary to return variable If the type of input is correct (i.e. either a list or a dictionary), this should also be returned. --- src/nvim/eval.c | 4 ++-- test/functional/eval/null_spec.lua | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 577aa67c60..56aedb1b4e 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -8457,11 +8457,13 @@ static void filter_map(typval_T *argvars, typval_T *rettv, int map) int idx = 0; if (argvars[0].v_type == VAR_LIST) { + tv_copy(&argvars[0], rettv); if ((l = argvars[0].vval.v_list) == NULL || (!map && tv_check_lock(l->lv_lock, arg_errmsg, TV_TRANSLATE))) { return; } } else if (argvars[0].v_type == VAR_DICT) { + tv_copy(&argvars[0], rettv); if ((d = argvars[0].vval.v_dict) == NULL || (!map && tv_check_lock(d->dv_lock, arg_errmsg, TV_TRANSLATE))) { return; @@ -8542,8 +8544,6 @@ static void filter_map(typval_T *argvars, typval_T *rettv, int map) did_emsg |= save_did_emsg; } - - tv_copy(&argvars[0], rettv); } static int filter_map_one(typval_T *tv, typval_T *expr, int map, int *remp) diff --git a/test/functional/eval/null_spec.lua b/test/functional/eval/null_spec.lua index 6fd30caec9..b67158eb22 100644 --- a/test/functional/eval/null_spec.lua +++ b/test/functional/eval/null_spec.lua @@ -42,14 +42,6 @@ describe('NULL', function() describe('list', function() -- Incorrect behaviour - -- FIXME map() should not return 0 without error - null_expr_test('does not crash map()', 'map(L, "v:val")', 0, 0) - -- FIXME map() should not return 0 without error - null_expr_test('does not crash filter()', 'filter(L, "1")', 0, 0) - -- FIXME map() should at least return L - null_expr_test('makes map() return v:_null_list', 'map(L, "v:val") is# L', 0, 0) - -- FIXME filter() should at least return L - null_expr_test('makes filter() return v:_null_list', 'map(L, "1") is# L', 0, 0) -- FIXME add() should not return 1 at all null_expr_test('does not crash add()', 'add(L, 0)', 0, 1) null_expr_test('does not crash extend()', 'extend(L, [1])', 'E742: Cannot change value of extend() argument', 0) @@ -111,6 +103,8 @@ describe('NULL', function() null_expr_test('does not crash line()', 'line(L)', 0, 0) null_expr_test('does not crash count()', 'count(L, 1)', 0, 0) null_expr_test('does not crash cursor()', 'cursor(L)', 'E474: Invalid argument', -1) + null_expr_test('does not crash map()', 'map(L, "v:val")', 0, {}) + null_expr_test('does not crash filter()', 'filter(L, "1")', 0, {}) null_expr_test('is empty', 'empty(L)', 0, 1) null_expr_test('does not crash get()', 'get(L, 1, 10)', 0, 10) null_expr_test('has zero length', 'len(L)', 0, 0) @@ -126,6 +120,8 @@ describe('NULL', function() null_expr_test('is equal to itself', 'L == L', 0, 1) null_expr_test('is not not equal to itself', 'L != L', 0, 0) null_expr_test('counts correctly', 'count([L], L)', 0, 1) + null_expr_test('makes map() return v:_null_list', 'map(L, "v:val") is# L', 0, 1) + null_expr_test('makes filter() return v:_null_list', 'filter(L, "1") is# L', 0, 1) end) describe('dict', function() it('does not crash when indexing NULL dict', function() @@ -134,5 +130,9 @@ describe('NULL', function() end) null_expr_test('makes extend error out', 'extend(D, {})', 'E742: Cannot change value of extend() argument', 0) null_expr_test('makes extend do nothing', 'extend({1: 2}, D)', 0, {['1']=2}) + null_expr_test('does not crash map()', 'map(D, "v:val")', 0, {}) + null_expr_test('does not crash filter()', 'filter(D, "1")', 0, {}) + null_expr_test('makes map() return v:_null_dict', 'map(D, "v:val") is# D', 0, 1) + null_expr_test('makes filter() return v:_null_dict', 'filter(D, "1") is# D', 0, 1) end) end) -- cgit From fbdc3ac4efbc97e2965b6083d429beabe261461c Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 3 Dec 2017 20:22:09 +0300 Subject: tests: Fix linter errors --- test/helpers.lua | 1 + test/unit/charset/vim_str2nr_spec.lua | 5 ----- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/test/helpers.lua b/test/helpers.lua index 3ec4aa511f..faf5c8e7f2 100644 --- a/test/helpers.lua +++ b/test/helpers.lua @@ -273,6 +273,7 @@ local deepcopy_funcs = { for k, v in pairs(orig) do copy[deepcopy(k)] = deepcopy(v) end + return copy end, number = id, string = id, diff --git a/test/unit/charset/vim_str2nr_spec.lua b/test/unit/charset/vim_str2nr_spec.lua index a6fe613563..891e6def09 100644 --- a/test/unit/charset/vim_str2nr_spec.lua +++ b/test/unit/charset/vim_str2nr_spec.lua @@ -1,5 +1,4 @@ local helpers = require("test.unit.helpers")(after_each) -local global_helpers = require('test.helpers') local bit = require('bit') local itp = helpers.gen_itp(it) @@ -8,9 +7,6 @@ local child_call_once = helpers.child_call_once local cimport = helpers.cimport local ffi = helpers.ffi -local shallowcopy = global_helpers.shallowcopy -local updated = global_helpers.updated - local lib = cimport('./src/nvim/charset.h') local ARGTYPES @@ -48,7 +44,6 @@ local function argreset(arg, args) end local function test_vim_str2nr(s, what, exp, maxlen) - local comb = {[{}] = true} local bits = {} for k, _ in pairs(exp) do bits[#bits + 1] = k -- cgit From e9990b43c23ef5f9cb10c75d6f0db00aa862db0e Mon Sep 17 00:00:00 2001 From: Filip Szymański Date: Sun, 3 Dec 2017 20:49:01 +0100 Subject: Fix job_control doc --- runtime/doc/job_control.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/doc/job_control.txt b/runtime/doc/job_control.txt index e57015db22..ed5f16902a 100644 --- a/runtime/doc/job_control.txt +++ b/runtime/doc/job_control.txt @@ -58,7 +58,7 @@ Description of what happens: - The first shell is idle, waiting to read commands from its stdin. - The second shell is started with -c which executes the command (a for-loop printing 0 through 9) and then exits. - - `JobHandler()` callback is passed to |jobstart()| to handle various job + - `OnEvent()` callback is passed to |jobstart()| to handle various job events. It displays stdout/stderr data received from the shells. For |on_stdout| and |on_stderr| see |channel-callback|. -- cgit From 3aa24042a8f7d591b8091b437fc9cdb03497bc7a Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Mon, 27 Nov 2017 00:35:09 +0100 Subject: tui: dump termcap info if -V3 ('verbose' >= 3) Get terminal debugging info by starting Nvim with 'verbose' level 3: nvim -V3log This is like Vim's `:set termcap`, which was removed in Nvim (and would be very awkward to restore because of the decoupled UI). --- runtime/doc/eval.txt | 5 --- runtime/doc/options.txt | 1 + runtime/doc/starting.txt | 10 +++--- runtime/doc/vim_diff.txt | 12 ++++--- src/nvim/README.md | 35 ++++++++++++++++++- src/nvim/option.c | 11 +++--- src/nvim/tui/terminfo.c | 87 ++++++++++++++++++++++++++++++++++++++++++++++++ src/nvim/tui/tui.c | 21 ++++++++++-- src/nvim/ui_bridge.c | 3 +- 9 files changed, 161 insertions(+), 24 deletions(-) diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index d2a3a962e6..71551e38e9 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -8889,11 +8889,6 @@ This does NOT work: > value and the global value are changed. Example: > :let &path = &path . ',/usr/local/include' -< This also works for terminal codes in the form t_xx. - But only for alphanumerical names. Example: > - :let &t_k1 = "\[234;" -< When the code does not exist yet it will be created as - a terminal key code, there is no error. :let &{option-name} .= {expr1} For a string option: Append {expr1} to the value. diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index 4fe2e07909..4180ca21f2 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -6424,6 +6424,7 @@ A jump table for the options with a short description can be found at |Q_op|. Currently, these messages are given: >= 1 When the shada file is read or written. >= 2 When a file is ":source"'ed. + >= 3 UI info, terminal capabilities >= 5 Every searched tags file and include file. >= 8 Files for which a group of autocommands is executed. >= 9 Every executed autocommand. diff --git a/runtime/doc/starting.txt b/runtime/doc/starting.txt index 9b33926d04..30c0641ef7 100644 --- a/runtime/doc/starting.txt +++ b/runtime/doc/starting.txt @@ -249,14 +249,14 @@ argument. for reading or writing a ShaDa file. Can be used to find out what is happening upon startup and exit. Example: > - vim -V8 foobar + nvim -V8 -V[N]{filename} - Like -V and set 'verbosefile' to {filename}. The result is - that messages are not displayed but written to the file - {filename}. {filename} must not start with a digit. + Like -V and set 'verbosefile' to {filename}. Messages are not + displayed; instead they are written to the file {filename}. + {filename} must not start with a digit. Example: > - vim -V20vimlog foobar + nvim -V20vimlog < *-D* -D Debugging. Go to debugging mode when executing the first diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index c8155f7a68..45c88f6fe9 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -327,22 +327,26 @@ Ed-compatible mode: ":set noedcompatible" is ignored ":set edcompatible" is an error - *t_xx* *:set-termcap* *termcap-options* *t_AB* *t_Sb* *t_vb* *t_SI* + *t_xx* *termcap-options* *t_AB* *t_Sb* *t_vb* *t_SI* Nvim does not have special `t_XX` options nor keycodes to configure terminal capabilities. Instead Nvim treats the terminal as any other UI. For example, 'guicursor' sets the terminal cursor style if possible. - *'term'* *E529* *E530* *E531* + *:set-termcap* +Start Nvim with 'verbose' level 3 to see the terminal capabilities. > + nvim -V3 +< + *'term'* *E529* *E530* *E531* 'term' reflects the terminal type derived from |$TERM| and other environment checks. For debugging only; not reliable during startup. > :echo &term "builtin_x" means one of the |builtin-terms| was chosen, because the expected terminfo file was not found on the system. - *termcap* + *termcap* Nvim never uses the termcap database, only |terminfo| and |builtin-terms|. - *xterm-8bit* *xterm-8-bit* + *xterm-8bit* *xterm-8-bit* Xterm can be run in a mode where it uses true 8-bit CSI. Supporting this requires autodetection of whether the terminal is in UTF-8 mode or non-UTF-8 mode, as the 8-bit CSI character has to be written differently in each case. diff --git a/src/nvim/README.md b/src/nvim/README.md index 0caf71e2c5..da87a0208e 100644 --- a/src/nvim/README.md +++ b/src/nvim/README.md @@ -32,6 +32,39 @@ The source files use extensions to hint about their purpose. - `*.h.generated.h` - exported functions’ declarations. - `*.c.generated.h` - static functions’ declarations. +TUI debugging +------------- + +### TUI troubleshoot + +Nvim logs its internal terminfo state at 'verbose' level 3. This makes it +possible to see exactly what terminfo values Nvim is using on any system. + + nvim -V3log + +### TUI trace + +The ancient `script` command is still the "state of the art" for tracing +terminal behavior. The libvterm `vterm-dump` utility formats the result for +human-readability. + +Record a Nvim terminal session and format it with `vterm-dump`: + + script foo + ./build/bin/nvim -u NONE + # Exit the script session with CTRL-d + + # Use `vterm-dump` utility to format the result. + ./.deps/usr/bin/vterm-dump foo > bar + +Then you can compare `bar` with another session, to debug TUI behavior. + +### Terminal reference + +- `man terminfo` +- http://bazaar.launchpad.net/~libvterm/libvterm/trunk/view/head:/doc/seqs.txt +- http://invisible-island.net/xterm/ctlseqs/ctlseqs.html + Nvim lifecycle -------------- @@ -39,7 +72,7 @@ Following describes how Nvim processes input. Consider a typical Vim-like editing session: -01. Vim dispays the welcome screen +01. Vim displays the welcome screen 02. User types: `:` 03. Vim enters command-line mode 04. User types: `edit README.txt` diff --git a/src/nvim/option.c b/src/nvim/option.c index 913d27d508..37c4233142 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -4906,15 +4906,14 @@ showoptions ( vimoption_T **items = xmalloc(sizeof(vimoption_T *) * PARAM_COUNT); - /* Highlight title */ - if (all == 2) - MSG_PUTS_TITLE(_("\n--- Terminal codes ---")); - else if (opt_flags & OPT_GLOBAL) + // Highlight title + if (opt_flags & OPT_GLOBAL) { MSG_PUTS_TITLE(_("\n--- Global option values ---")); - else if (opt_flags & OPT_LOCAL) + } else if (opt_flags & OPT_LOCAL) { MSG_PUTS_TITLE(_("\n--- Local option values ---")); - else + } else { MSG_PUTS_TITLE(_("\n--- Options ---")); + } /* * do the loop two times: diff --git a/src/nvim/tui/terminfo.c b/src/nvim/tui/terminfo.c index fdc33f0a77..492c1c5e9c 100644 --- a/src/nvim/tui/terminfo.c +++ b/src/nvim/tui/terminfo.c @@ -9,7 +9,10 @@ #include #include "nvim/log.h" +#include "nvim/globals.h" #include "nvim/memory.h" +#include "nvim/message.h" +#include "nvim/option.h" #include "nvim/tui/terminfo.h" #ifdef INCLUDE_GENERATED_DECLARATIONS @@ -166,3 +169,87 @@ unibi_term *terminfo_from_builtin(const char *term, char **termname) unibi_set_bool(ut, unibi_back_color_erase, false); return ut; } + +/// Dumps termcap info to the messages area. +/// Serves a similar purpose as Vim `:set termcap` (removed in Nvim). +/// +/// @note adapted from unibilium unibi-dump.c +void terminfo_info_msg(const unibi_term *const ut) +{ + if (exiting) { + return; + } + msg_puts_title("\n\n--- Terminal info --- {{{\n"); + + char *term; + get_tty_option("term", &term); + msg_printf_attr(0, "&term: %s\n", term); + msg_printf_attr(0, "Description: %s\n", unibi_get_name(ut)); + const char **a = unibi_get_aliases(ut); + if (*a) { + msg_puts("Aliases: "); + do { + msg_printf_attr(0, "%s%s\n", *a, a[1] ? " | " : ""); + a++; + } while (*a); + } + + msg_puts("Boolean capabilities:\n"); + for (enum unibi_boolean i = unibi_boolean_begin_ + 1; + i < unibi_boolean_end_; i++) { + msg_printf_attr(0, " %-25s %-10s = %s\n", unibi_name_bool(i), + unibi_short_name_bool(i), + unibi_get_bool(ut, i) ? "true" : "false"); + } + + msg_puts("Numeric capabilities:\n"); + for (enum unibi_numeric i = unibi_numeric_begin_ + 1; + i < unibi_numeric_end_; i++) { + int n = unibi_get_num(ut, i); // -1 means "empty" + msg_printf_attr(0, " %-25s %-10s = %hd\n", unibi_name_num(i), + unibi_short_name_num(i), n); + } + + msg_puts("String capabilities:\n"); + for (enum unibi_string i = unibi_string_begin_ + 1; + i < unibi_string_end_; i++) { + const char *s = unibi_get_str(ut, i); + if (s) { + msg_printf_attr(0, " %-25s %-10s = ", unibi_name_str(i), + unibi_short_name_str(i)); + // Most of these strings will contain escape sequences. + msg_outtrans_special((char_u *)s, false); + msg_putchar('\n'); + } + } + + if (unibi_count_ext_bool(ut)) { + msg_puts("Extended boolean capabilities:\n"); + for (size_t i = 0; i < unibi_count_ext_bool(ut); i++) { + msg_printf_attr(0, " %-25s = %s\n", + unibi_get_ext_bool_name(ut, i), + unibi_get_ext_bool(ut, i) ? "true" : "false"); + } + } + + if (unibi_count_ext_num(ut)) { + msg_puts("Extended numeric capabilities:\n"); + for (size_t i = 0; i < unibi_count_ext_num(ut); i++) { + msg_printf_attr(0, " %-25s = %hd\n", + unibi_get_ext_num_name(ut, i), + unibi_get_ext_num(ut, i)); + } + } + + if (unibi_count_ext_str(ut)) { + msg_puts("Extended string capabilities:\n"); + for (size_t i = 0; i < unibi_count_ext_str(ut); i++) { + msg_printf_attr(0, " %-25s = ", unibi_get_ext_str_name(ut, i)); + msg_outtrans_special((char_u *)unibi_get_ext_str(ut, i), false); + msg_putchar('\n'); + } + } + + msg_puts("}}}\n"); + xfree(term); +} diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index 2436295ad4..739f3d18d2 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -354,10 +354,12 @@ static void tui_main(UIBridgeData *bridge, UI *ui) tui_terminal_start(ui); data->stop = false; - // allow the main thread to continue, we are ready to start handling UI - // callbacks + // Allow main thread to continue, we are ready to handle UI callbacks. CONTINUE(bridge); + loop_schedule_deferred(&main_loop, + event_create(show_termcap_event, 1, data->ut)); + while (!data->stop) { loop_poll_events(&tui_loop, -1); // tui_loop.events is never processed } @@ -1061,6 +1063,21 @@ static void tui_flush(UI *ui) flush_buf(ui, true); } +/// Dumps termcap info to the messages area, if 'verbose' >= 3. +static void show_termcap_event(void **argv) +{ + if (p_verbose < 3) { + return; + } + const unibi_term *const ut = argv[0]; + if (!ut) { + abort(); + } + // XXX: (future) if unibi_term is modified (e.g. after a terminal + // query-response) this is a race condition. + terminfo_info_msg(ut); +} + #ifdef UNIX static void suspend_event(void **argv) { diff --git a/src/nvim/ui_bridge.c b/src/nvim/ui_bridge.c index 5585886612..7573fa1653 100644 --- a/src/nvim/ui_bridge.c +++ b/src/nvim/ui_bridge.c @@ -82,6 +82,7 @@ UI *ui_bridge_attach(UI *ui, ui_main_fn ui_main, event_scheduler scheduler) abort(); } + // Suspend the main thread until CONTINUE is called by the UI thread. while (!rv->ready) { uv_cond_wait(&rv->cond, &rv->mutex); } @@ -149,7 +150,7 @@ static void ui_bridge_suspend(UI *b) uv_mutex_lock(&data->mutex); UI_BRIDGE_CALL(b, suspend, 1, b); data->ready = false; - // suspend the main thread until CONTINUE is called by the UI thread + // Suspend the main thread until CONTINUE is called by the UI thread. while (!data->ready) { uv_cond_wait(&data->cond, &data->mutex); } -- cgit From 9d689d639d71ab3d60cb88544619b8cef9510217 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Wed, 29 Nov 2017 03:01:16 +0100 Subject: tui: set descriptions on termcap extensions --- src/nvim/tui/tui.c | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index 739f3d18d2..2cdd1979af 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -1594,11 +1594,13 @@ static void augment_terminfo(TUIData *data, const char *term, || konsole // per commentary in VT102Emulation.cpp || teraterm // per TeraTerm "Supported Control Functions" doco || rxvt) { // per command.C - data->unibi_ext.resize_screen = (int)unibi_add_ext_str(ut, NULL, + data->unibi_ext.resize_screen = (int)unibi_add_ext_str(ut, + "ext.resize_screen", "\x1b[8;%p1%d;%p2%dt"); } if (putty || xterm || rxvt) { - data->unibi_ext.reset_scroll_region = (int)unibi_add_ext_str(ut, NULL, + data->unibi_ext.reset_scroll_region = (int)unibi_add_ext_str(ut, + "ext.reset_scroll_region", "\x1b[r"); } @@ -1656,21 +1658,29 @@ static void augment_terminfo(TUIData *data, const char *term, /// Terminals usually ignore unrecognized private modes, and there is no /// known ambiguity with these. So we just set them unconditionally. - data->unibi_ext.enable_lr_margin = (int)unibi_add_ext_str(ut, NULL, + data->unibi_ext.enable_lr_margin = (int)unibi_add_ext_str(ut, + "ext.enable_lr_margin", "\x1b[?69h"); - data->unibi_ext.disable_lr_margin = (int)unibi_add_ext_str(ut, NULL, + data->unibi_ext.disable_lr_margin = (int)unibi_add_ext_str(ut, + "ext.disable_lr_margin", "\x1b[?69l"); - data->unibi_ext.enable_bracketed_paste = (int)unibi_add_ext_str(ut, NULL, + data->unibi_ext.enable_bracketed_paste = (int)unibi_add_ext_str(ut, + "ext.enable_bpaste", "\x1b[?2004h"); - data->unibi_ext.disable_bracketed_paste = (int)unibi_add_ext_str(ut, NULL, + data->unibi_ext.disable_bracketed_paste = (int)unibi_add_ext_str(ut, + "ext.disable_bpaste", "\x1b[?2004l"); - data->unibi_ext.enable_focus_reporting = (int)unibi_add_ext_str(ut, NULL, + data->unibi_ext.enable_focus_reporting = (int)unibi_add_ext_str(ut, + "ext.enable_focus", rxvt ? "\x1b]777;focus;on\x7" : "\x1b[?1004h"); - data->unibi_ext.disable_focus_reporting = (int)unibi_add_ext_str(ut, NULL, + data->unibi_ext.disable_focus_reporting = (int)unibi_add_ext_str(ut, + "ext.disable_focus", rxvt ? "\x1b]777;focus;off\x7" : "\x1b[?1004l"); - data->unibi_ext.enable_mouse = (int)unibi_add_ext_str(ut, NULL, + data->unibi_ext.enable_mouse = (int)unibi_add_ext_str(ut, + "ext.enable_mouse", "\x1b[?1002h\x1b[?1006h"); - data->unibi_ext.disable_mouse = (int)unibi_add_ext_str(ut, NULL, + data->unibi_ext.disable_mouse = (int)unibi_add_ext_str(ut, + "ext.disable_mouse", "\x1b[?1002l\x1b[?1006l"); } -- cgit From 586dafee2f4824b6cd4e0be80d597fbb810a647e Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Mon, 27 Nov 2017 10:20:06 +0100 Subject: str2specialbuf(): fix comparison regression by 832c158a663c --- src/nvim/message.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/nvim/message.c b/src/nvim/message.c index b90c475ede..806de5e116 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -1307,7 +1307,7 @@ char *str2special_save(const char *const str, const bool replace_spaces, return (char *)ga.ga_data; } -/// Convert character, replacing key one key code with printable representation +/// Convert character, replacing key with printable representation. /// /// @param[in,out] sp String to convert. Is advanced to the next key code. /// @param[in] replace_spaces Convert spaces into , normally used for @@ -1392,7 +1392,7 @@ void str2specialbuf(const char *sp, char *buf, size_t len) while (*sp) { const char *s = str2special(&sp, false, false); const size_t s_len = strlen(s); - if (s_len <= len) { + if (len <= s_len) { break; } memcpy(buf, s, s_len); -- cgit From 5de1eae4f2353de1dc4cdeb3e5bad0a908faf9c8 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Wed, 29 Nov 2017 02:54:19 +0100 Subject: msg_outtrans_special(): `const` some strings --- src/nvim/message.c | 35 ++++++++++++++++------------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/src/nvim/message.c b/src/nvim/message.c index 806de5e116..907a2472c8 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -1237,27 +1237,24 @@ void msg_make(char_u *arg) } } -/* - * Output the string 'str' upto a NUL character. - * Return the number of characters it takes on the screen. - * - * If K_SPECIAL is encountered, then it is taken in conjunction with the - * following character and shown as , etc. Any other character - * which is not printable shown in <> form. - * If 'from' is TRUE (lhs of a mapping), a space is shown as . - * If a character is displayed in one of these special ways, is also - * highlighted (its highlight name is '8' in the p_hl variable). - * Otherwise characters are not highlighted. - * This function is used to show mappings, where we want to see how to type - * the character/string -- webb - */ -int -msg_outtrans_special ( - char_u *strstart, - int from /* TRUE for lhs of a mapping */ +/// Output the string 'str' upto a NUL character. +/// Return the number of characters it takes on the screen. +/// +/// If K_SPECIAL is encountered, then it is taken in conjunction with the +/// following character and shown as , etc. Any other character +/// which is not printable shown in <> form. +/// If 'from' is TRUE (lhs of a mapping), a space is shown as . +/// If a character is displayed in one of these special ways, is also +/// highlighted (its highlight name is '8' in the p_hl variable). +/// Otherwise characters are not highlighted. +/// This function is used to show mappings, where we want to see how to type +/// the character/string -- webb +int msg_outtrans_special( + const char_u *strstart, + int from ///< true for LHS of a mapping ) { - char_u *str = strstart; + const char_u *str = strstart; int retval = 0; int attr; -- cgit From 1cae99b4bf6517d559ff222b968c314198923260 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sat, 2 Dec 2017 18:55:54 +0100 Subject: msg_outtrans_special(): handle NULL This is convenient for terminfo_info_msg(). --- src/nvim/message.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/nvim/message.c b/src/nvim/message.c index 907a2472c8..5c8f0655bf 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -1254,11 +1254,13 @@ int msg_outtrans_special( int from ///< true for LHS of a mapping ) { + if (strstart == NULL) { + return 0; // Do nothing. + } const char_u *str = strstart; int retval = 0; - int attr; + int attr = hl_attr(HLF_8); - attr = hl_attr(HLF_8); while (*str != NUL) { const char *string; // Leading and trailing spaces need to be displayed in <> form. -- cgit From a11751eae864b4373d4dfc52905b682ae6ed84d4 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sat, 2 Dec 2017 20:27:39 +0100 Subject: test: tui_spec: narrower scope for timeout tweaks --- test/functional/terminal/tui_spec.lua | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua index d5f6a21d1d..c33845e6d9 100644 --- a/test/functional/terminal/tui_spec.lua +++ b/test/functional/terminal/tui_spec.lua @@ -21,9 +21,6 @@ describe('tui', function() clear() screen = thelpers.screen_setup(0, '["'..nvim_prog ..'", "-u", "NONE", "-i", "NONE", "--cmd", "set noswapfile noshowcmd noruler undodir=. directory=. viewdir=. backupdir=."]') - -- right now pasting can be really slow in the TUI, especially in ASAN. - -- this will be fixed later but for now we require a high timeout. - screen.timeout = 60000 screen:expect([[ {1: } | {4:~ }| @@ -125,6 +122,9 @@ describe('tui', function() end) it('automatically sends for bracketed paste sequences', function() + -- Pasting can be really slow in the TUI, specially in ASAN. + -- This will be fixed later but for now we require a high timeout. + screen.timeout = 60000 feed_data('i\027[200~') screen:expect([[ {1: } | @@ -158,6 +158,8 @@ describe('tui', function() end) it('can handle arbitrarily long bursts of input', function() + -- Need extra time for this test, specially in ASAN. + screen.timeout = 60000 feed_command('set ruler') local t = {} for i = 1, 3000 do -- cgit From 175174597dfb773ebe967adcae10a7eb568c32c2 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sat, 2 Dec 2017 20:37:43 +0100 Subject: test: macOS 10.13: unibilium cannot find "xterm" terminfo On some macOS versions we can't find the terminfo for whatever reason, so just skip the test if it fails. --- test/functional/terminal/tui_spec.lua | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua index c33845e6d9..79439cc9e9 100644 --- a/test/functional/terminal/tui_spec.lua +++ b/test/functional/terminal/tui_spec.lua @@ -641,6 +641,7 @@ end) describe("tui 'term' option", function() local screen local is_bsd = not not string.find(string.lower(uname()), 'bsd') + local is_macos = not not string.find(string.lower(uname()), 'darwin') local function assert_term(term_envvar, term_expected) clear() @@ -666,8 +667,13 @@ describe("tui 'term' option", function() end) it('gets system-provided term if $TERM is valid', function() - if is_bsd then -- BSD lacks terminfo, we always use builtin there. + if is_bsd then -- BSD lacks terminfo, builtin is always used. assert_term("xterm", "builtin_xterm") + elseif is_macos then + local status, _ = pcall(assert_term, "xterm", "xterm") + if not status then + pending("macOS: unibilium could not find terminfo", function() end) + end else assert_term("xterm", "xterm") end -- cgit From 7f386b175c2e0f7b76a6e21e38bbbff2f6083d0c Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Mon, 4 Dec 2017 22:18:11 +0100 Subject: test: retry(): fix time calculation libuv caches the results of uv.now() until the next loop tick. If a test does not spin the libuv event loop, retry() enters an infinite cycle. --- test/functional/helpers.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/test/functional/helpers.lua b/test/functional/helpers.lua index da334d4ac6..1709427d59 100644 --- a/test/functional/helpers.lua +++ b/test/functional/helpers.lua @@ -261,6 +261,7 @@ local function retry(max, max_ms, fn) if status then return result end + luv.update_time() -- Update cached value of luv.now() (libuv: uv_now()). if (max and tries >= max) or (luv.now() - start_time > timeout) then if type(result) == "string" then result = "\nretry() attempts: "..tostring(tries).."\n"..result -- cgit From 5f288220f93f61b3461814d7b93618fc4d9ab7df Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Mon, 4 Dec 2017 22:28:04 +0100 Subject: test: write_file(): support append-mode --- test/functional/helpers.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/functional/helpers.lua b/test/functional/helpers.lua index 1709427d59..f939567693 100644 --- a/test/functional/helpers.lua +++ b/test/functional/helpers.lua @@ -334,8 +334,8 @@ local function feed_command(...) end -- Dedent the given text and write it to the file name. -local function write_file(name, text, dont_dedent) - local file = io.open(name, 'w') +local function write_file(name, text, no_dedent, append) + local file = io.open(name, (append and 'a' or 'w')) if type(text) == 'table' then -- Byte blob local bytes = text @@ -343,7 +343,7 @@ local function write_file(name, text, dont_dedent) for _, char in ipairs(bytes) do text = ('%s%c'):format(text, char) end - elseif not dont_dedent then + elseif not no_dedent then text = dedent(text) end file:write(text) -- cgit From 837100fcb1c383054bd7c0b41d872377888d862e Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Mon, 4 Dec 2017 22:48:38 +0100 Subject: test/tui: -V3log logs terminfo values --- test/functional/terminal/tui_spec.lua | 52 +++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua index 79439cc9e9..39f4401462 100644 --- a/test/functional/terminal/tui_spec.lua +++ b/test/functional/terminal/tui_spec.lua @@ -4,6 +4,7 @@ local global_helpers = require('test.helpers') local uname = global_helpers.uname local helpers = require('test.functional.helpers')(after_each) local thelpers = require('test.functional.terminal.helpers') +local eq = helpers.eq local feed_data = thelpers.feed_data local feed_command = helpers.feed_command local clear = helpers.clear @@ -11,6 +12,8 @@ local nvim_dir = helpers.nvim_dir local retry = helpers.retry local nvim_prog = helpers.nvim_prog local nvim_set = helpers.nvim_set +local ok = helpers.ok +local read_file = helpers.read_file if helpers.pending_win32(pending) then return end @@ -680,3 +683,52 @@ describe("tui 'term' option", function() end) end) + +-- These tests require `thelpers` because --headless/--embed +-- does not initialize the TUI. +describe("tui", function() + local screen + local logfile = 'Xtest_tui_verbose_log' + after_each(function() + os.remove(logfile) + end) + + -- Runs (child) `nvim` in a TTY (:terminal), to start the builtin TUI. + local function nvim_tui(extra_args) + clear() + -- This is ugly because :term/termopen() forces TERM=xterm-256color. + -- TODO: Revisit this after jobstart/termopen accept `env` dict. + local cmd = string.format( + [=[['sh', '-c', 'LANG=C %s -u NONE -i NONE %s --cmd "%s"']]=], + nvim_prog, + extra_args or "", + nvim_set) + screen = thelpers.screen_setup(0, cmd) + end + + it('-V3log logs terminfo values', function() + nvim_tui('-V3'..logfile) + + -- Wait for TUI to start. + feed_data('Gitext') + screen:expect([[ + text{1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {4:~ }| + {3:-- INSERT --} | + {3:-- TERMINAL --} | + ]]) + + -- Vim flushes the log file on exit. + feed_data('\33:q\n') + + retry(nil, 3000, function() -- Wait for log file to be flushed. + local log = read_file('Xtest_tui_verbose_log') or '' + eq('--- Terminal info --- {{{\n', string.match(log, '--- Terminal.-\n')) + ok(#log > 50) + end) + end) + +end) -- cgit From 2d4abc1caedf67487e100f5cef5eca78da68b3e7 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Mon, 4 Dec 2017 23:31:50 +0100 Subject: tui: flush -V3 ('verbose' >= 3) info ASAP --- src/nvim/tui/tui.c | 3 +++ test/functional/terminal/tui_spec.lua | 3 --- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index 2cdd1979af..9f2ae20fe3 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -1073,9 +1073,12 @@ static void show_termcap_event(void **argv) if (!ut) { abort(); } + verbose_enter(); // XXX: (future) if unibi_term is modified (e.g. after a terminal // query-response) this is a race condition. terminfo_info_msg(ut); + verbose_leave(); + verbose_stop(); // flush now } #ifdef UNIX diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua index 39f4401462..bf3c6bdb3a 100644 --- a/test/functional/terminal/tui_spec.lua +++ b/test/functional/terminal/tui_spec.lua @@ -721,9 +721,6 @@ describe("tui", function() {3:-- TERMINAL --} | ]]) - -- Vim flushes the log file on exit. - feed_data('\33:q\n') - retry(nil, 3000, function() -- Wait for log file to be flushed. local log = read_file('Xtest_tui_verbose_log') or '' eq('--- Terminal info --- {{{\n', string.match(log, '--- Terminal.-\n')) -- cgit From 9714b9f59013348b9d16f21822485a2316e93fd7 Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Tue, 5 Dec 2017 13:16:56 +0100 Subject: tests: cleanup ui/cmdline_spec.lua --- test/functional/ui/cmdline_spec.lua | 245 ++++++++++++++++++------------------ 1 file changed, 125 insertions(+), 120 deletions(-) diff --git a/test/functional/ui/cmdline_spec.lua b/test/functional/ui/cmdline_spec.lua index 0f8302b036..1b4f12a487 100644 --- a/test/functional/ui/cmdline_spec.lua +++ b/test/functional/ui/cmdline_spec.lua @@ -16,6 +16,11 @@ describe('external cmdline', function() cmdline, block = {}, nil screen = Screen.new(25, 5) screen:attach({rgb=true, ext_cmdline=true}) + screen:set_default_attr_ids({ + [1] = {bold = true, foreground = Screen.colors.Blue1}, + [2] = {reverse = true}, + [3] = {bold = true, reverse = true}, + }) screen:set_on_event_handler(function(name, data) if name == "cmdline_show" then local content, pos, firstc, prompt, indent, level = unpack(data) @@ -66,9 +71,9 @@ describe('external cmdline', function() feed(':') screen:expect([[ ^ | - ~ | - ~ | - ~ | + {1:~ }| + {1:~ }| + {1:~ }| | ]], nil, nil, function() eq(1, last_level) @@ -84,9 +89,9 @@ describe('external cmdline', function() feed('sign') screen:expect([[ ^ | - ~ | - ~ | - ~ | + {1:~ }| + {1:~ }| + {1:~ }| | ]], nil, nil, function() eq({{ @@ -101,9 +106,9 @@ describe('external cmdline', function() feed('') screen:expect([[ ^ | - ~ | - ~ | - ~ | + {1:~ }| + {1:~ }| + {1:~ }| | ]], nil, nil, function() eq({{ @@ -118,9 +123,9 @@ describe('external cmdline', function() feed('') screen:expect([[ ^ | - ~ | - ~ | - ~ | + {1:~ }| + {1:~ }| + {1:~ }| | ]], nil, nil, function() eq({{ @@ -135,9 +140,9 @@ describe('external cmdline', function() feed('') screen:expect([[ ^ | - ~ | - ~ | - ~ | + {1:~ }| + {1:~ }| + {1:~ }| | ]], nil, nil, function() eq({}, cmdline) @@ -148,9 +153,9 @@ describe('external cmdline', function() feed(':call input("input", "default")') screen:expect([[ ^ | - ~ | - ~ | - ~ | + {1:~ }| + {1:~ }| + {1:~ }| | ]], nil, nil, function() eq({{ @@ -164,9 +169,9 @@ describe('external cmdline', function() feed('') screen:expect([[ ^ | - ~ | - ~ | - ~ | + {1:~ }| + {1:~ }| + {1:~ }| | ]], nil, nil, function() eq({}, cmdline) @@ -178,9 +183,9 @@ describe('external cmdline', function() feed(':xx') screen:expect([[ ^ | - ~ | - ~ | - ~ | + {1:~ }| + {1:~ }| + {1:~ }| | ]], nil, nil, function() eq({{ @@ -196,9 +201,9 @@ describe('external cmdline', function() feed('=') screen:expect([[ ^ | - ~ | - ~ | - ~ | + {1:~ }| + {1:~ }| + {1:~ }| | ]], nil, nil, function() eq({{ @@ -234,9 +239,9 @@ describe('external cmdline', function() }} screen:expect([[ ^ | - ~ | - ~ | - ~ | + {1:~ }| + {1:~ }| + {1:~ }| | ]], nil, nil, function() eq(expectation, cmdline) @@ -249,9 +254,9 @@ describe('external cmdline', function() -- focus is at external cmdline anyway. screen:expect([[ | - ~ | - ~ | - ~ | + {1:~ }| + {1:~ }| + {1:~ }| ^ | ]], nil, nil, function() eq(expectation, cmdline) @@ -261,9 +266,9 @@ describe('external cmdline', function() feed('') screen:expect([[ | - ~ | - ~ | - ~ | + {1:~ }| + {1:~ }| + {1:~ }| ^ | ]], nil, nil, function() eq({{ @@ -278,9 +283,9 @@ describe('external cmdline', function() feed('') screen:expect([[ ^ | - ~ | - ~ | - ~ | + {1:~ }| + {1:~ }| + {1:~ }| | ]], nil, nil, function() eq({}, cmdline) @@ -291,9 +296,9 @@ describe('external cmdline', function() feed(':function Foo()') screen:expect([[ ^ | - ~ | - ~ | - ~ | + {1:~ }| + {1:~ }| + {1:~ }| | ]], nil, nil, function() eq({{ @@ -309,9 +314,9 @@ describe('external cmdline', function() feed('line1') screen:expect([[ ^ | - ~ | - ~ | - ~ | + {1:~ }| + {1:~ }| + {1:~ }| | ]], nil, nil, function() eq({{{{}, 'function Foo()'}}, @@ -322,9 +327,9 @@ describe('external cmdline', function() command("redraw!") screen:expect([[ | - ~ | - ~ | - ~ | + {1:~ }| + {1:~ }| + {1:~ }| ^ | ]], nil, nil, function() eq({{{{}, 'function Foo()'}}, @@ -335,9 +340,9 @@ describe('external cmdline', function() feed('endfunction') screen:expect([[ ^ | - ~ | - ~ | - ~ | + {1:~ }| + {1:~ }| + {1:~ }| | ]], nil, nil, function() eq(nil, block) @@ -348,9 +353,9 @@ describe('external cmdline', function() feed(':make') screen:expect([[ ^ | - ~ | - ~ | - ~ | + {1:~ }| + {1:~ }| + {1:~ }| | ]], nil, nil, function() eq({{ @@ -365,9 +370,9 @@ describe('external cmdline', function() feed('') screen:expect([[ | - [No Name] | - :make^ | - [Command Line] | + {2:[No Name] }| + {1::}make^ | + {3:[Command Line] }| | ]], nil, nil, function() eq({}, cmdline) @@ -377,9 +382,9 @@ describe('external cmdline', function() feed(':yank') screen:expect([[ | - [No Name] | - :make^ | - [Command Line] | + {2:[No Name] }| + {1::}make^ | + {3:[Command Line] }| | ]], nil, nil, function() eq({nil, { @@ -395,9 +400,9 @@ describe('external cmdline', function() command("redraw!") screen:expect([[ | - [No Name] | - :make | - [Command Line] | + {2:[No Name] }| + {1::}make | + {3:[Command Line] }| ^ | ]], nil, nil, function() eq({nil, { @@ -412,9 +417,9 @@ describe('external cmdline', function() feed("") screen:expect([[ | - [No Name] | - :make^ | - [Command Line] | + {2:[No Name] }| + {1::}make^ | + {3:[Command Line] }| | ]], nil, nil, function() eq({}, cmdline) @@ -423,9 +428,9 @@ describe('external cmdline', function() feed("") screen:expect([[ | - [No Name] | - :make^ | - [Command Line] | + {2:[No Name] }| + {1::}make^ | + {3:[Command Line] }| | ]], nil, nil, function() eq({{ @@ -441,9 +446,9 @@ describe('external cmdline', function() command("redraw!") screen:expect([[ | - ~ | - ~ | - ~ | + {1:~ }| + {1:~ }| + {1:~ }| ^ | ]], nil, nil, function() eq({{ @@ -460,9 +465,9 @@ describe('external cmdline', function() feed(":call inputsecret('secret:')abc123") screen:expect([[ ^ | - ~ | - ~ | - ~ | + {1:~ }| + {1:~ }| + {1:~ }| | ]], nil, nil, function() eq({{ @@ -476,50 +481,50 @@ describe('external cmdline', function() end) it('works with highlighted cmdline', function() - source([[ - highlight RBP1 guibg=Red - highlight RBP2 guibg=Yellow - highlight RBP3 guibg=Green - highlight RBP4 guibg=Blue - let g:NUM_LVLS = 4 - function RainBowParens(cmdline) - let ret = [] - let i = 0 - let lvl = 0 - while i < len(a:cmdline) - if a:cmdline[i] is# '(' - call add(ret, [i, i + 1, 'RBP' . ((lvl % g:NUM_LVLS) + 1)]) - let lvl += 1 - elseif a:cmdline[i] is# ')' - let lvl -= 1 - call add(ret, [i, i + 1, 'RBP' . ((lvl % g:NUM_LVLS) + 1)]) - endif - let i += 1 - endwhile - return ret - endfunction - map :let x = input({'prompt':'>','highlight':'RainBowParens'}) - "map :let x = input({'prompt':'>'}) - ]]) - screen:set_default_attr_ids({ - RBP1={background = Screen.colors.Red}, - RBP2={background = Screen.colors.Yellow}, - RBP3={background = Screen.colors.Green}, - RBP4={background = Screen.colors.Blue}, - EOB={bold = true, foreground = Screen.colors.Blue1}, - ERR={foreground = Screen.colors.Grey100, background = Screen.colors.Red}, - SK={foreground = Screen.colors.Blue}, - PE={bold = true, foreground = Screen.colors.SeaGreen4} - }) - feed('(a(b)a)') - screen:expect([[ - ^ | - {EOB:~ }| - {EOB:~ }| - {EOB:~ }| - | - ]], nil, nil, function() - expect_cmdline(1, '{RBP1:(}a{RBP2:(}b{RBP2:)}a{RBP1:)}') - end) + source([[ + highlight RBP1 guibg=Red + highlight RBP2 guibg=Yellow + highlight RBP3 guibg=Green + highlight RBP4 guibg=Blue + let g:NUM_LVLS = 4 + function RainBowParens(cmdline) + let ret = [] + let i = 0 + let lvl = 0 + while i < len(a:cmdline) + if a:cmdline[i] is# '(' + call add(ret, [i, i + 1, 'RBP' . ((lvl % g:NUM_LVLS) + 1)]) + let lvl += 1 + elseif a:cmdline[i] is# ')' + let lvl -= 1 + call add(ret, [i, i + 1, 'RBP' . ((lvl % g:NUM_LVLS) + 1)]) + endif + let i += 1 + endwhile + return ret + endfunction + map :let x = input({'prompt':'>','highlight':'RainBowParens'}) + "map :let x = input({'prompt':'>'}) + ]]) + screen:set_default_attr_ids({ + RBP1={background = Screen.colors.Red}, + RBP2={background = Screen.colors.Yellow}, + RBP3={background = Screen.colors.Green}, + RBP4={background = Screen.colors.Blue}, + EOB={bold = true, foreground = Screen.colors.Blue1}, + ERR={foreground = Screen.colors.Grey100, background = Screen.colors.Red}, + SK={foreground = Screen.colors.Blue}, + PE={bold = true, foreground = Screen.colors.SeaGreen4} + }) + feed('(a(b)a)') + screen:expect([[ + ^ | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + | + ]], nil, nil, function() + expect_cmdline(1, '{RBP1:(}a{RBP2:(}b{RBP2:)}a{RBP1:)}') + end) end) end) -- cgit From 90dd2b1473cf19a6467838dbae078eea4bdc3c61 Mon Sep 17 00:00:00 2001 From: Florian Larysch Date: Wed, 29 Nov 2017 18:43:02 +0100 Subject: tui: never flush buffers in midst of unibilium output e83845285 fixed an issue where long (true color) escape sequences got interrupted by the cursor visibility toggling caused by buffer flushes. cdfaecb25 introduces a new issue which causes similar problems: While the old buffer flushing code appended the cursor visibility escapes to the buffer before/after flushing, the new code effectively prepends the sequences. Assume the following sequence of events occurs: - A long escape code is issued using unibi_out when the buffer is almost full - out() gets called for a prefix of that escape code, causing the buffer to fill up - flush_buf(ui, false) is called and (correctly) does not insert any cursor toggling escapes - The rest of the escape code is written into the now empty buffer - At some later point, some other part of nvim calls flush_buf(ui, true), which then toggles the cursor, corrupting the escape code This could possibly also be fixed by tracking the state of the buffer (i.e. does it contain a partially output escape code?), but this seems fragile in the same way e83845285 turned out to be. The root cause for all these problems is the mismatch between nvim's (implicit) assumption that the buffer is flushable at any point in time and the non-atomicity of unibilium's character based callback interface. The proper fix (without modifying unibilium) is to ensure nvim's assumption about the buffer state holds at all times. To that end, add a "cork" flag which ensures one unibi_out-call never splits its output across a buffer flush; if an escape code does not fit into the current buffer, flush it without any part of the escape code in it and insert the whole escape code in the emptied buffer. This is a little more complex because it modifies the buffer in place rather than printing into another buffer, checking the remaining space in the terminal buffer and then memcpy'ing it. --- src/nvim/tui/tui.c | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index 2436295ad4..d07eb9a923 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -96,6 +96,7 @@ typedef struct { bool immediate_wrap_after_last_column; bool mouse_enabled; bool busy, is_invisible; + bool cork, overflow; cursorentry_T cursor_shapes[SHAPE_IDX_COUNT]; HlAttrs print_attrs; bool default_attr; @@ -200,6 +201,8 @@ static void terminfo_start(UI *ui) data->default_attr = false; data->is_invisible = true; data->busy = false; + data->cork = false; + data->overflow = false; data->showing_mode = SHAPE_IDX_N; data->unibi_ext.enable_mouse = -1; data->unibi_ext.disable_mouse = -1; @@ -1219,8 +1222,18 @@ static void unibi_goto(UI *ui, int row, int col) } \ if (str) { \ unibi_var_t vars[26 + 26]; \ + size_t orig_pos = data->bufpos; \ + \ memset(&vars, 0, sizeof(vars)); \ + data->cork = true; \ +retry: \ unibi_format(vars, vars + 26, str, data->params, out, ui, NULL, NULL); \ + if (data->overflow) { \ + data->bufpos = orig_pos; \ + flush_buf(ui, true); \ + goto retry; \ + } \ + data->cork = false; \ } \ } while (0) static void unibi_out(UI *ui, int unibi_index) @@ -1239,8 +1252,17 @@ static void out(void *ctx, const char *str, size_t len) TUIData *data = ui->data; size_t available = sizeof(data->buf) - data->bufpos; + if (data->cork && data->overflow) { + return; + } + if (len > available) { - flush_buf(ui, false); + if (data->cork) { + data->overflow = true; + return; + } else { + flush_buf(ui, true); + } } memcpy(data->buf + data->bufpos, str, len); @@ -1696,6 +1718,7 @@ static void flush_buf(UI *ui, bool toggle_cursor) bufs, (unsigned)(bufp - bufs), NULL); uv_run(&data->write_loop, UV_RUN_DEFAULT); data->bufpos = 0; + data->overflow = false; } #if TERMKEY_VERSION_MAJOR > 0 || TERMKEY_VERSION_MINOR > 18 -- cgit From 54087d80f3603165c0bdeab9f14c9aba2ddb6c67 Mon Sep 17 00:00:00 2001 From: Florian Larysch Date: Tue, 5 Dec 2017 21:18:30 +0100 Subject: tui: always hide cursor when flushing The previous commit ensures that we can never flush the buffer in a state where toggling cursor visibility can corrupt other escape codes. Thus, we can remove the workaround added as part of e838452, simplyfing the code and hiding the cursor on more occasions. --- src/nvim/tui/tui.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index d07eb9a923..61d3bde450 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -297,7 +297,7 @@ static void terminfo_stop(UI *ui) unibi_out_ext(ui, data->unibi_ext.disable_bracketed_paste); // Disable focus reporting unibi_out_ext(ui, data->unibi_ext.disable_focus_reporting); - flush_buf(ui, true); + flush_buf(ui); uv_tty_reset_mode(); uv_close((uv_handle_t *)&data->output_handle, NULL); uv_run(&data->write_loop, UV_RUN_DEFAULT); @@ -1061,7 +1061,7 @@ static void tui_flush(UI *ui) cursor_goto(ui, saved_row, saved_col); - flush_buf(ui, true); + flush_buf(ui); } #ifdef UNIX @@ -1230,7 +1230,7 @@ retry: \ unibi_format(vars, vars + 26, str, data->params, out, ui, NULL, NULL); \ if (data->overflow) { \ data->bufpos = orig_pos; \ - flush_buf(ui, true); \ + flush_buf(ui); \ goto retry; \ } \ data->cork = false; \ @@ -1261,7 +1261,7 @@ static void out(void *ctx, const char *str, size_t len) data->overflow = true; return; } else { - flush_buf(ui, true); + flush_buf(ui); } } @@ -1679,7 +1679,7 @@ static void augment_terminfo(TUIData *data, const char *term, "\x1b[?1002l\x1b[?1006l"); } -static void flush_buf(UI *ui, bool toggle_cursor) +static void flush_buf(UI *ui) { uv_write_t req; uv_buf_t bufs[3]; @@ -1690,7 +1690,7 @@ static void flush_buf(UI *ui, bool toggle_cursor) return; } - if (toggle_cursor && !data->is_invisible) { + if (!data->is_invisible) { // cursor is visible. Write a "cursor invisible" command before writing the // buffer. bufp->base = data->invis; @@ -1705,7 +1705,7 @@ static void flush_buf(UI *ui, bool toggle_cursor) bufp++; } - if (toggle_cursor && !data->busy && data->is_invisible) { + if (!data->busy && data->is_invisible) { // not busy and the cursor is invisible. Write a "cursor normal" command // after writing the buffer. bufp->base = data->norm; -- cgit From ba7d6a9e6b3c42f1996d05cec471b4842eeb8b66 Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Tue, 5 Dec 2017 13:27:06 +0100 Subject: ui: fix glitch with both ext_cmdline and cmd_wildmenu --- src/nvim/ex_getln.c | 6 +- test/functional/ui/cmdline_spec.lua | 118 ++++++++++++++++++++++++++++++++++++ test/functional/ui/screen.lua | 4 ++ 3 files changed, 126 insertions(+), 2 deletions(-) diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index c1500e3121..99330c177c 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -3455,8 +3455,10 @@ nextwild ( return FAIL; } - MSG_PUTS("..."); /* show that we are busy */ - ui_flush(); + if (!ui_is_external(kUIWildmenu)) { + MSG_PUTS("..."); // show that we are busy + ui_flush(); + } i = (int)(xp->xp_pattern - ccline.cmdbuff); xp->xp_pattern_len = ccline.cmdpos - i; diff --git a/test/functional/ui/cmdline_spec.lua b/test/functional/ui/cmdline_spec.lua index 1b4f12a487..c9ee61fb1e 100644 --- a/test/functional/ui/cmdline_spec.lua +++ b/test/functional/ui/cmdline_spec.lua @@ -10,6 +10,8 @@ describe('external cmdline', function() local last_level = 0 local cmdline = {} local block = nil + local wild_items = nil + local wild_selected = nil before_each(function() clear() @@ -43,6 +45,12 @@ describe('external cmdline', function() block[#block+1] = data[1] elseif name == "cmdline_block_hide" then block = nil + elseif name == "wildmenu_show" then + wild_items = data[1] + elseif name == "wildmenu_select" then + wild_selected = data[1] + elseif name == "wildmenu_hide" then + wild_items, wild_selected = nil, nil end end) end) @@ -527,4 +535,114 @@ describe('external cmdline', function() expect_cmdline(1, '{RBP1:(}a{RBP2:(}b{RBP2:)}a{RBP1:)}') end) end) + + it('works together with ext_wildmenu', function() + local expected = { + 'define', + 'jump', + 'list', + 'place', + 'undefine', + 'unplace', + } + + command('set wildmode=full') + command('set wildmenu') + screen:set_option('ext_wildmenu', true) + feed(':sign ') + + screen:expect([[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + | + ]], nil, nil, function() + eq({{ + content = { { {}, "sign define"} }, + firstc = ":", + indent = 0, + pos = 11, + prompt = "" + }}, cmdline) + eq(expected, wild_items) + eq(0, wild_selected) + end) + + feed('') + screen:expect([[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + | + ]], nil, nil, function() + eq({{ + content = { { {}, "sign jump"} }, + firstc = ":", + indent = 0, + pos = 9, + prompt = "" + }}, cmdline) + eq(expected, wild_items) + eq(1, wild_selected) + end) + + feed('') + screen:expect([[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + | + ]], nil, nil, function() + eq({{ + content = { { {}, "sign "} }, + firstc = ":", + indent = 0, + pos = 5, + prompt = "" + }}, cmdline) + eq(expected, wild_items) + eq(-1, wild_selected) + end) + + feed('') + screen:expect([[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + | + ]], nil, nil, function() + eq({{ + content = { { {}, "sign define"} }, + firstc = ":", + indent = 0, + pos = 11, + prompt = "" + }}, cmdline) + eq(expected, wild_items) + eq(0, wild_selected) + end) + + feed('a') + screen:expect([[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + | + ]], nil, nil, function() + eq({{ + content = { { {}, "sign definea"} }, + firstc = ":", + indent = 0, + pos = 12, + prompt = "" + }}, cmdline) + eq(nil, wild_items) + eq(nil, wild_selected) + end) + end) end) diff --git a/test/functional/ui/screen.lua b/test/functional/ui/screen.lua index a6b7fb2997..075d8c40d7 100644 --- a/test/functional/ui/screen.lua +++ b/test/functional/ui/screen.lua @@ -176,6 +176,10 @@ function Screen:try_resize(columns, rows) self:sleep(0.1) end +function Screen:set_option(option, value) + uimeths.set_option(option, value) +end + -- Asserts that `expected` eventually matches the screen state. -- -- expected: Expected screen state (string). Each line represents a screen -- cgit From 5cbd3b383c09539fed7a7c41f882996bf3d0f8ad Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Wed, 6 Dec 2017 10:42:26 +0100 Subject: tui: ignore st terminfo cursor shape (Se, Ss) entries closes #7641 --- src/nvim/tui/tui.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index 9f2ae20fe3..99433924f6 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -1480,19 +1480,18 @@ static void patch_terminfo_bugs(TUIData *data, const char *term, } } - // Some terminals cannot be trusted to report DECSCUSR support. So we keep - // blacklist for when we should not trust the reported features. - if (!((vte_version != 0 && vte_version < 3900) || konsole)) { - // Dickey ncurses terminfo has included the Ss and Se capabilities, - // pioneered by tmux, since 2011-07-14. So adding them to terminal types, - // that do actually have such control sequences but lack the correct - // definitions in terminfo, is a fixup, not an augmentation. + // Blacklist of terminals that cannot be trusted to report DECSCUSR support. + if (!(st || (vte_version != 0 && vte_version < 3900) || konsole)) { data->unibi_ext.reset_cursor_style = unibi_find_ext_str(ut, "Se"); data->unibi_ext.set_cursor_style = unibi_find_ext_str(ut, "Ss"); } + + // Dickey ncurses terminfo includes Ss/Se capabilities since 2011-07-14. So + // adding them to terminal types, that have such control sequences but lack + // the correct terminfo entries, is a fixup, not an augmentation. if (-1 == data->unibi_ext.set_cursor_style) { - // The DECSCUSR sequence to change the cursor shape is widely supported by - // several terminal types. https://github.com/gnachman/iTerm2/pull/92 + // DECSCUSR (cursor shape) sequence is widely supported by several terminal + // types. https://github.com/gnachman/iTerm2/pull/92 // xterm extension: vertical bar if (!konsole && ((xterm && !vte_version) // anything claiming xterm compat // per MinTTY 0.4.3-1 release notes from 2009 @@ -1502,6 +1501,7 @@ static void patch_terminfo_bugs(TUIData *data, const char *term, || tmux // per tmux manual page // https://lists.gnu.org/archive/html/screen-devel/2013-03/msg00000.html || screen + || st // #7641 || rxvt // per command.C // per analysis of VT100Terminal.m || iterm || iterm_pretending_xterm -- cgit From b6c268b32a395df20db662e65b7cc8198df5a013 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Wed, 6 Dec 2017 14:14:07 +0100 Subject: build: de-parallelize luarocks dependencies (#7697) ref 6647f3c047b1 closes #7535 --- third-party/cmake/BuildLuarocks.cmake | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/third-party/cmake/BuildLuarocks.cmake b/third-party/cmake/BuildLuarocks.cmake index ef8a8450f1..c7b7f8d837 100644 --- a/third-party/cmake/BuildLuarocks.cmake +++ b/third-party/cmake/BuildLuarocks.cmake @@ -96,8 +96,7 @@ if(USE_BUNDLED_LUAJIT) endif() endif() -# Each target depends on the previous module, this serializes all calls to -# luarocks since it is unhappy to be called in parallel. +# DEPENDS on the previous module, because Luarocks breaks if parallel. add_custom_command(OUTPUT ${HOSTDEPS_LIB_DIR}/luarocks/rocks/mpack COMMAND ${LUAROCKS_BINARY} ARGS build mpack ${LUAROCKS_BUILDARGS} @@ -106,7 +105,7 @@ add_custom_target(mpack DEPENDS ${HOSTDEPS_LIB_DIR}/luarocks/rocks/mpack) list(APPEND THIRD_PARTY_DEPS mpack) - +# DEPENDS on the previous module, because Luarocks breaks if parallel. add_custom_command(OUTPUT ${HOSTDEPS_LIB_DIR}/luarocks/rocks/lpeg COMMAND ${LUAROCKS_BINARY} ARGS build lpeg ${LUAROCKS_BUILDARGS} @@ -116,16 +115,18 @@ add_custom_target(lpeg list(APPEND THIRD_PARTY_DEPS lpeg) +# DEPENDS on the previous module, because Luarocks breaks if parallel. add_custom_command(OUTPUT ${HOSTDEPS_LIB_DIR}/luarocks/rocks/inspect COMMAND ${LUAROCKS_BINARY} ARGS build inspect ${LUAROCKS_BUILDARGS} - DEPENDS mpack) + DEPENDS lpeg) add_custom_target(inspect DEPENDS ${HOSTDEPS_LIB_DIR}/luarocks/rocks/inspect) list(APPEND THIRD_PARTY_DEPS inspect) if(USE_BUNDLED_BUSTED) + # DEPENDS on the previous module, because Luarocks breaks if parallel. add_custom_command(OUTPUT ${HOSTDEPS_LIB_DIR}/luarocks/rocks/penlight/1.3.2-2 COMMAND ${LUAROCKS_BINARY} ARGS build penlight 1.3.2-2 ${LUAROCKS_BUILDARGS} @@ -138,6 +139,7 @@ if(USE_BUNDLED_BUSTED) else() set(BUSTED_EXE "${HOSTDEPS_BIN_DIR}/busted") endif() + # DEPENDS on the previous module, because Luarocks breaks if parallel. add_custom_command(OUTPUT ${BUSTED_EXE} COMMAND ${LUAROCKS_BINARY} ARGS build https://raw.githubusercontent.com/Olivine-Labs/busted/v2.0.rc12-1/busted-2.0.rc12-1.rockspec ${LUAROCKS_BUILDARGS} @@ -145,6 +147,7 @@ if(USE_BUNDLED_BUSTED) add_custom_target(busted DEPENDS ${BUSTED_EXE}) + # DEPENDS on the previous module, because Luarocks breaks if parallel. add_custom_command(OUTPUT ${HOSTDEPS_BIN_DIR}/luacheck COMMAND ${LUAROCKS_BINARY} ARGS build https://raw.githubusercontent.com/mpeterv/luacheck/master/luacheck-scm-1.rockspec ${LUAROCKS_BUILDARGS} @@ -160,6 +163,7 @@ if(USE_BUNDLED_BUSTED) if(USE_BUNDLED_LIBUV) list(APPEND LUV_ARGS LIBUV_DIR=${HOSTDEPS_INSTALL_DIR}) endif() + # DEPENDS on the previous module, because Luarocks breaks if parallel. add_custom_command(OUTPUT ${HOSTDEPS_LIB_DIR}/luarocks/rocks/luv COMMAND ${LUAROCKS_BINARY} ARGS make ${LUAROCKS_BUILDARGS} ${LUV_ARGS} @@ -168,6 +172,7 @@ if(USE_BUNDLED_BUSTED) add_custom_target(luv DEPENDS ${HOSTDEPS_LIB_DIR}/luarocks/rocks/luv) + # DEPENDS on the previous module, because Luarocks breaks if parallel. add_custom_command(OUTPUT ${HOSTDEPS_LIB_DIR}/luarocks/rocks/nvim-client COMMAND ${LUAROCKS_BINARY} ARGS build https://raw.githubusercontent.com/neovim/lua-client/0.0.1-26/nvim-client-0.0.1-26.rockspec ${LUAROCKS_BUILDARGS} -- cgit From afae4b514183c490737a28f2946def717e78a11c Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Thu, 7 Dec 2017 04:21:03 -0500 Subject: ci: Install neovim gem in Appveyor (#7700) ref #7655 --- ci/build.bat | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ci/build.bat b/ci/build.bat index 6eb22176a9..91eca9ef73 100644 --- a/ci/build.bat +++ b/ci/build.bat @@ -33,6 +33,10 @@ set PATH=C:\Python35;C:\Python27;%PATH% python -c "import neovim; print(str(neovim))" || goto :error python3 -c "import neovim; print(str(neovim))" || goto :error +set PATH=C:\Ruby24\bin;%PATH% +cmd /c gem.cmd install neovim || goto :error +where.exe neovim-ruby-host.bat || goto :error + mkdir .deps cd .deps cmake -G "MinGW Makefiles" -DCMAKE_BUILD_TYPE=RelWithDebInfo ..\third-party\ || goto :error -- cgit From ddce5bca03067ce7adf70496a3493cf12c016364 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 10 Dec 2017 00:08:44 +0300 Subject: eval/typval: Add functions useful for hiding list implementation --- src/nvim/eval/typval.h | 104 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) diff --git a/src/nvim/eval/typval.h b/src/nvim/eval/typval.h index c44b85644d..9caf3f7b60 100644 --- a/src/nvim/eval/typval.h +++ b/src/nvim/eval/typval.h @@ -284,6 +284,54 @@ typedef struct list_stack_S { #define TV_DICT_HI2DI(hi) \ ((dictitem_T *)((hi)->hi_key - offsetof(dictitem_T, di_key))) +static inline void tv_list_ref(list_T *const l) + REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; + +/// Increase reference count for a given list +/// +/// Does nothing for NULL lists. +/// +/// @param[in] l List to modify. +static inline void tv_list_ref(list_T *const l) +{ + if (l == NULL) { + return; + } + l->lv_refcount++; +} + +static inline VarLockStatus tv_list_locked(const list_T *const l) + REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; + +/// Get list lock status +/// +/// Returns VAR_FIXED for NULL lists. +/// +/// @param[in] l List to check. +static inline VarLockStatus tv_list_locked(const list_T *const l) +{ + if (l == NULL) { + return VAR_FIXED; + } + return l->lv_lock; +} + +/// Set list lock status +/// +/// May only “set” VAR_FIXED for NULL lists. +/// +/// @param[out] l List to modify. +/// @param[in] lock New lock status. +static inline void tv_list_set_lock(list_T *const l, + const VarLockStatus lock) +{ + if (l == NULL) { + assert(lock == VAR_FIXED); + return; + } + l->lv_lock = lock; +} + static inline long tv_list_len(const list_T *const l) REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; @@ -298,6 +346,22 @@ static inline long tv_list_len(const list_T *const l) return l->lv_len; } +static inline listitem_T *tv_list_first(const list_T *const l) + REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; + +/// Get first list item +/// +/// @param[in] l List to get item from. +/// +/// @return List item or NULL in case of an empty list. +static inline listitem_T *tv_list_first(const list_T *const l) +{ + if (l == NULL) { + return NULL; + } + return l->lv_first; +} + static inline long tv_dict_len(const dict_T *const d) REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; @@ -352,6 +416,46 @@ extern const char *const tv_empty_string; /// Specifies that free_unref_items() function has (not) been entered extern bool tv_in_free_unref_items; +/// Iterate over a list +/// +/// @param modifier Modifier: expected to be const or nothing, volatile should +/// also work if you have any uses for the volatile list. +/// @param[in] l List to iterate over. +/// @param li Name of the variable with current listitem_T entry. +/// @param code Cycle body. +#define _TV_LIST_ITER_MOD(modifier, l, li, code) \ + do { \ + modifier list_T *const l_ = (l); \ + if (l_ != NULL) { \ + for (modifier listitem_T *const li = l_->lv_first; \ + li != NULL; li = li->li_next) { \ + code \ + } \ + } \ + } while (0) + +/// Iterate over a list +/// +/// To be used when you need to modify list or values you iterate over, use +/// #TV_LIST_ITER_CONST if you don’t. +/// +/// @param[in] l List to iterate over. +/// @param li Name of the variable with current listitem_T entry. +/// @param code Cycle body. +#define TV_LIST_ITER(l, li, code) \ + _TV_LIST_ITER_MOD(, l, li, code) + +/// Iterate over a list +/// +/// To be used when you don’t need to modify list or values you iterate over, +/// use #TV_LIST_ITER if you do. +/// +/// @param[in] l List to iterate over. +/// @param li Name of the variable with current listitem_T entry. +/// @param code Cycle body. +#define TV_LIST_ITER_CONST(l, li, code) \ + _TV_LIST_ITER_MOD(const, l, li, code) + /// Iterate over a dictionary /// /// @param[in] d Dictionary to iterate over. -- cgit From 49dd615693c9f6d86a180371abcd09d32ac8aeca Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 10 Dec 2017 00:20:05 +0300 Subject: eval/typval: Add macros useful for hiding list item implementation --- src/nvim/eval/typval.h | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/nvim/eval/typval.h b/src/nvim/eval/typval.h index 9caf3f7b60..b00f43f625 100644 --- a/src/nvim/eval/typval.h +++ b/src/nvim/eval/typval.h @@ -456,6 +456,35 @@ extern bool tv_in_free_unref_items; #define TV_LIST_ITER_CONST(l, li, code) \ _TV_LIST_ITER_MOD(const, l, li, code) +// Below macros are macros to avoid duplicating code for functionally identical +// const and non-const function variants. + +/// Get typval_T out of list item +/// +/// @param[in] li List item to get typval_T from, must not be NULL. +/// +/// @return Pointer to typval_T. +#define TV_LIST_ITEM_TV(li) (&(li)->li_tv) + +/// Get next list item given the current one +/// +/// @param[in] l List to get item from. +/// @param[in] li List item to get typval_T from. +/// +/// @return Pointer to the next item or NULL. +#define TV_LIST_ITEM_NEXT(l, li) ((li)->li_next) + +/// Get previous list item given the current one +/// +/// @param[in] l List to get item from. +/// @param[in] li List item to get typval_T from. +/// +/// @return Pointer to the previous item or NULL. +#define TV_LIST_ITEM_PREV(l, li) ((li)->li_prev) +// List argument is not used currently, but it is a must for lists implemented +// as a pair (size(in list), array) without terminator - basically for lists on +// top of kvec. + /// Iterate over a dictionary /// /// @param[in] d Dictionary to iterate over. -- cgit From 274f32d42e61e6f6c76b9ca499f5b79f256a481a Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 10 Dec 2017 03:39:51 +0300 Subject: *: Start hiding list implementation Most of files, except for eval.c and eval/* were only processed by perl. --- src/nvim/api/private/helpers.c | 2 +- src/nvim/channel.c | 2 +- src/nvim/eval.c | 845 +++++++++++++++++++------------------ src/nvim/eval/decode.c | 16 +- src/nvim/eval/typval.c | 131 +++--- src/nvim/eval/typval.h | 33 +- src/nvim/lua/converter.c | 2 +- src/nvim/ops.c | 2 +- test/functional/eval/null_spec.lua | 33 +- 9 files changed, 557 insertions(+), 509 deletions(-) diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 629873998e..e5a1e150f9 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -798,7 +798,7 @@ bool object_to_vim(Object obj, typval_T *tv, Error *err) tv_list_append(list, li); } - list->lv_refcount++; + tv_list_ref(list); tv->v_type = VAR_LIST; tv->vval.v_list = list; diff --git a/src/nvim/channel.c b/src/nvim/channel.c index 40af470bde..ff8dbd3d33 100644 --- a/src/nvim/channel.c +++ b/src/nvim/channel.c @@ -673,7 +673,7 @@ static void on_channel_event(void **args) argv[1].v_type = VAR_LIST; argv[1].v_lock = VAR_UNLOCKED; argv[1].vval.v_list = ev->received; - argv[1].vval.v_list->lv_refcount++; + tv_list_ref(argv[1].vval.v_list); } else { argv[1].v_type = VAR_NUMBER; argv[1].v_lock = VAR_UNLOCKED; diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 56aedb1b4e..edf016fc41 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -564,8 +564,8 @@ void eval_init(void) dict_T *const msgpack_types_dict = tv_dict_alloc(); for (size_t i = 0; i < ARRAY_SIZE(msgpack_type_names); i++) { list_T *const type_list = tv_list_alloc(); - type_list->lv_lock = VAR_FIXED; - type_list->lv_refcount = 1; + tv_list_set_lock(type_list, VAR_FIXED); + tv_list_ref(type_list); dictitem_T *const di = tv_dict_item_alloc(msgpack_type_names[i]); di->di_flags |= DI_FLAGS_RO|DI_FLAGS_FIX; di->di_tv = (typval_T) { @@ -1014,7 +1014,7 @@ char_u *eval_to_string(char_u *arg, char_u **nextcmd, int convert) ga_init(&ga, (int)sizeof(char), 80); if (tv.vval.v_list != NULL) { tv_list_join(&ga, tv.vval.v_list, "\n"); - if (tv.vval.v_list->lv_len > 0) { + if (tv_list_len(tv.vval.v_list) > 0) { ga_append(&ga, NL); } } @@ -1143,27 +1143,31 @@ list_T *eval_spell_expr(char_u *badword, char_u *expr) return list; } -/* - * "list" is supposed to contain two items: a word and a number. Return the - * word in "pp" and the number as the return value. - * Return -1 if anything isn't right. - * Used to get the good word and score from the eval_spell_expr() result. - */ -int get_spellword(list_T *list, const char **pp) +/// Get spell word from an entry from spellsuggest=expr: +/// +/// Entry in question is supposed to be a list (to be checked by the caller) +/// with two items: a word and a score represented as an unsigned number +/// (whether it actually is unsigned is not checked). +/// +/// Used to get the good word and score from the eval_spell_expr() result. +/// +/// @param[in] list List to get values from. +/// @param[out] ret_word Suggested word. Not initialized if return value is +/// -1. +/// +/// @return -1 in case of error, score otherwise. +int get_spellword(list_T *const list, const char **ret_word) { - listitem_T *li; - - li = list->lv_first; - if (li == NULL) { + if (tv_list_len(list) != 2) { + EMSG(_("E5700: Expression from 'spellsuggest' must yield lists with " + "exactly two values")); return -1; } - *pp = tv_get_string(&li->li_tv); - - li = li->li_next; - if (li == NULL) { + *ret_word = tv_list_find_str(list, 0); + if (*ret_word == NULL) { return -1; } - return tv_get_number(&li->li_tv); + return tv_list_find_nr(list, -1, NULL); } @@ -1504,9 +1508,7 @@ ex_let_vars ( ) { char_u *arg = arg_start; - list_T *l; int i; - listitem_T *item; typval_T ltv; if (*arg != '[') { @@ -1518,13 +1520,12 @@ ex_let_vars ( return OK; } - /* - * ":let [v1, v2] = list" or ":for [v1, v2] in listlist" - */ - if (tv->v_type != VAR_LIST || (l = tv->vval.v_list) == NULL) { + // ":let [v1, v2] = list" or ":for [v1, v2] in listlist" + if (tv->v_type != VAR_LIST) { EMSG(_(e_listreq)); return FAIL; } + list_T *const l = tv->vval.v_list; i = tv_list_len(l); if (semicolon == 0 && var_count < i) { @@ -1535,29 +1536,33 @@ ex_let_vars ( EMSG(_("E688: More targets than List items")); return FAIL; } + // l may actually be NULL, but it should fail with E688 or even earlier if you + // try to do ":let [] = v:_null_list". + assert(l != NULL); - item = l->lv_first; + listitem_T *item = tv_list_first(l); while (*arg != ']') { arg = skipwhite(arg + 1); - arg = ex_let_one(arg, &item->li_tv, TRUE, (char_u *)",;]", nextchars); - item = item->li_next; - if (arg == NULL) + arg = ex_let_one(arg, TV_LIST_ITEM_TV(item), TRUE, (char_u *)",;]", nextchars); + item = TV_LIST_ITEM_NEXT(l, item); + if (arg == NULL) { return FAIL; + } arg = skipwhite(arg); if (*arg == ';') { /* Put the rest of the list (may be empty) in the var after ';'. * Create a new list for this. */ - l = tv_list_alloc(); + list_T *const rest_list = tv_list_alloc(); while (item != NULL) { - tv_list_append_tv(l, &item->li_tv); - item = item->li_next; + tv_list_append_tv(rest_list, TV_LIST_ITEM_TV(item)); + item = TV_LIST_ITEM_NEXT(l, item); } ltv.v_type = VAR_LIST; ltv.v_lock = 0; - ltv.vval.v_list = l; - l->lv_refcount = 1; + ltv.vval.v_list = rest_list; + tv_list_ref(rest_list); arg = ex_let_one(skipwhite(arg + 1), <v, false, (char_u *)"]", nextchars); @@ -2310,7 +2315,7 @@ static char_u *get_lval(char_u *const name, typval_T *const rettv, } } - lp->ll_tv = &lp->ll_li->li_tv; + lp->ll_tv = TV_LIST_ITEM_TV(lp->ll_li); } } @@ -2375,45 +2380,49 @@ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv, int ll_n1 = lp->ll_n1; // Check whether any of the list items is locked - for (listitem_T *ri = rettv->vval.v_list->lv_first; + for (listitem_T *ri = tv_list_first(rettv->vval.v_list); ri != NULL && ll_li != NULL; ) { - if (tv_check_lock(ll_li->li_tv.v_lock, (const char *)lp->ll_name, + if (tv_check_lock(TV_LIST_ITEM_TV(ll_li)->v_lock, + (const char *)lp->ll_name, TV_CSTRING)) { return; } - ri = ri->li_next; + ri = TV_LIST_ITEM_NEXT(rettv->vval.v_list, ri); if (ri == NULL || (!lp->ll_empty2 && lp->ll_n2 == ll_n1)) { break; } - ll_li = ll_li->li_next; + ll_li = TV_LIST_ITEM_NEXT(lp->ll_list, ll_li); ll_n1++; } /* * Assign the List values to the list items. */ - for (ri = rettv->vval.v_list->lv_first; ri != NULL; ) { + for (ri = tv_list_first(rettv->vval.v_list); ri != NULL; ) { if (op != NULL && *op != '=') { - eexe_mod_op(&lp->ll_li->li_tv, &ri->li_tv, (const char *)op); + eexe_mod_op(TV_LIST_ITEM_TV(lp->ll_li), TV_LIST_ITEM_TV(ri), + (const char *)op); } else { - tv_clear(&lp->ll_li->li_tv); - tv_copy(&ri->li_tv, &lp->ll_li->li_tv); + tv_clear(TV_LIST_ITEM_TV(lp->ll_li)); + tv_copy(TV_LIST_ITEM_TV(ri), TV_LIST_ITEM_TV(lp->ll_li)); } - ri = ri->li_next; - if (ri == NULL || (!lp->ll_empty2 && lp->ll_n2 == lp->ll_n1)) + ri = TV_LIST_ITEM_NEXT(rettv->vval.v_list, ri); + if (ri == NULL || (!lp->ll_empty2 && lp->ll_n2 == lp->ll_n1)) { break; - if (lp->ll_li->li_next == NULL) { + } + if (TV_LIST_ITEM_NEXT(lp->ll_list, lp->ll_li) == NULL) { // Need to add an empty item. tv_list_append_number(lp->ll_list, 0); - assert(lp->ll_li->li_next); + assert(TV_LIST_ITEM_NEXT(lp->ll_list, lp->ll_li)); } - lp->ll_li = lp->ll_li->li_next; - ++lp->ll_n1; + lp->ll_li = TV_LIST_ITEM_NEXT(lp->ll_list, lp->ll_li); + lp->ll_n1++; } if (ri != NULL) EMSG(_("E710: List value has more items than target")); else if (lp->ll_empty2 - ? (lp->ll_li != NULL && lp->ll_li->li_next != NULL) + ? (lp->ll_li != NULL + && TV_LIST_ITEM_NEXT(lp->ll_list, lp->ll_li) != NULL) : lp->ll_n1 != lp->ll_n2) EMSG(_("E711: List value has not enough items")); } else { @@ -2514,7 +2523,7 @@ void *eval_for_line(const char_u *arg, bool *errp, char_u **nextcmdp, int skip) * list being used in "tv". */ fi->fi_list = l; tv_list_watch_add(l, &fi->fi_lw); - fi->fi_lw.lw_item = l->lv_first; + fi->fi_lw.lw_item = tv_list_first(l); } } } @@ -2532,21 +2541,18 @@ void *eval_for_line(const char_u *arg, bool *errp, char_u **nextcmdp, int skip) * Return TRUE when a valid item was found, FALSE when at end of list or * something wrong. */ -int next_for_item(void *fi_void, char_u *arg) +bool next_for_item(void *fi_void, char_u *arg) { - forinfo_T *fi = (forinfo_T *)fi_void; - int result; - listitem_T *item; + forinfo_T *fi = (forinfo_T *)fi_void; - item = fi->fi_lw.lw_item; - if (item == NULL) - result = FALSE; - else { - fi->fi_lw.lw_item = item->li_next; - result = (ex_let_vars(arg, &item->li_tv, TRUE, - fi->fi_semicolon, fi->fi_varcount, NULL) == OK); + listitem_T *item = fi->fi_lw.lw_item; + if (item == NULL) { + return false; + } else { + fi->fi_lw.lw_item = TV_LIST_ITEM_NEXT(fi->fi_list, item); + return (ex_let_vars(arg, TV_LIST_ITEM_TV(item), true, + fi->fi_semicolon, fi->fi_varcount, NULL) == OK); } - return result; } // TODO(ZyX-I): move to eval/ex_cmds @@ -2869,9 +2875,9 @@ static int do_unlet_var(lval_T *const lp, char_u *const name_end, int forceit) ret = FAIL; } *name_end = cc; - } else if ((lp->ll_list != NULL - && tv_check_lock(lp->ll_list->lv_lock, (const char *)lp->ll_name, - lp->ll_name_len)) + } else if (tv_check_lock(tv_list_locked(lp->ll_list), + (const char *)lp->ll_name, + lp->ll_name_len) || (lp->ll_dict != NULL && tv_check_lock(lp->ll_dict->dv_lock, (const char *)lp->ll_name, @@ -2883,8 +2889,9 @@ static int do_unlet_var(lval_T *const lp, char_u *const name_end, int forceit) int ll_n1 = lp->ll_n1; while (ll_li != NULL && (lp->ll_empty2 || lp->ll_n2 >= ll_n1)) { - li = ll_li->li_next; - if (tv_check_lock(ll_li->li_tv.v_lock, (const char *)lp->ll_name, + li = TV_LIST_ITEM_NEXT(lp->ll_list, ll_li); + if (tv_check_lock(TV_LIST_ITEM_TV(ll_li)->v_lock, + (const char *)lp->ll_name, lp->ll_name_len)) { return false; } @@ -2892,12 +2899,12 @@ static int do_unlet_var(lval_T *const lp, char_u *const name_end, int forceit) ll_n1++; } - /* Delete a range of List items. */ + // Delete a range of List items. while (lp->ll_li != NULL && (lp->ll_empty2 || lp->ll_n2 >= lp->ll_n1)) { - li = lp->ll_li->li_next; + li = TV_LIST_ITEM_NEXT(lp->ll_list, lp->ll_li); tv_list_item_remove(lp->ll_list, lp->ll_li); lp->ll_li = li; - ++lp->ll_n1; + lp->ll_n1++; } } else { if (lp->ll_list != NULL) { @@ -3043,13 +3050,13 @@ static int do_lock_var(lval_T *lp, char_u *const name_end, const int deep, /* (un)lock a range of List items. */ while (li != NULL && (lp->ll_empty2 || lp->ll_n2 >= lp->ll_n1)) { - tv_item_lock(&li->li_tv, deep, lock); - li = li->li_next; - ++lp->ll_n1; + tv_item_lock(TV_LIST_ITEM_TV(li), deep, lock); + li = TV_LIST_ITEM_NEXT(lp->ll_list, li); + lp->ll_n1++; } } else if (lp->ll_list != NULL) { // (un)lock a List item. - tv_item_lock(&lp->ll_li->li_tv, deep, lock); + tv_item_lock(TV_LIST_ITEM_TV(lp->ll_li), deep, lock); } else { // (un)lock a Dictionary item. tv_item_lock(&lp->ll_di->di_tv, deep, lock); @@ -4511,15 +4518,15 @@ eval_index ( l = tv_list_alloc(); item = tv_list_find(rettv->vval.v_list, n1); while (n1++ <= n2) { - tv_list_append_tv(l, &item->li_tv); - item = item->li_next; + tv_list_append_tv(l, TV_LIST_ITEM_TV(item)); + item = TV_LIST_ITEM_NEXT(l, item); } tv_clear(rettv); rettv->v_type = VAR_LIST; rettv->vval.v_list = l; - l->lv_refcount++; + tv_list_ref(l); } else { - tv_copy(&tv_list_find(rettv->vval.v_list, n1)->li_tv, &var1); + tv_copy(TV_LIST_ITEM_TV(tv_list_find(rettv->vval.v_list, n1)), &var1); tv_clear(rettv); *rettv = var1; } @@ -4877,22 +4884,23 @@ static int get_list_tv(char_u **arg, typval_T *rettv, int evaluate) goto failret; if (evaluate) { item = tv_list_item_alloc(); - item->li_tv = tv; - item->li_tv.v_lock = 0; + *TV_LIST_ITEM_TV(item) = tv; + TV_LIST_ITEM_TV(item)->v_lock = VAR_UNLOCKED; tv_list_append(l, item); } - if (**arg == ']') + if (**arg == ']') { break; + } if (**arg != ',') { - EMSG2(_("E696: Missing comma in List: %s"), *arg); + emsgf(_("E696: Missing comma in List: %s"), *arg); goto failret; } *arg = skipwhite(*arg + 1); } if (**arg != ']') { - EMSG2(_("E697: Missing end of List ']': %s"), *arg); + emsgf(_("E697: Missing end of List ']': %s"), *arg); failret: if (evaluate) { tv_list_free(l); @@ -4904,7 +4912,7 @@ failret: if (evaluate) { rettv->v_type = VAR_LIST; rettv->vval.v_list = l; - ++l->lv_refcount; + tv_list_ref(l); } return OK; @@ -5260,6 +5268,7 @@ static int free_unref_items(int copyID) for (ll = gc_first_list; ll != NULL; ll = ll_next) { ll_next = ll->lv_used_next; if ((ll->lv_copyID & COPYID_MASK) != (copyID & COPYID_MASK) + // FIXME: Abstract away lv_watch. && ll->lv_watch == NULL) { // Free the List and ordinary items it contains, but don't recurse // into Lists and Dictionaries, they will be in the list of dicts @@ -5329,10 +5338,13 @@ bool set_ref_in_list(list_T *l, int copyID, ht_stack_T **ht_stack) // Mark each item in the list. If the item contains a hashtab // it is added to ht_stack, if it contains a list it is added to // list_stack. - for (listitem_T *li = cur_l->lv_first; !abort && li != NULL; - li = li->li_next) { - abort = set_ref_in_item(&li->li_tv, copyID, ht_stack, &list_stack); - } + TV_LIST_ITER(cur_l, li, { + abort = set_ref_in_item(TV_LIST_ITEM_TV(li), copyID, ht_stack, + &list_stack); + if (abort) { + break; + } + }); } if (list_stack == NULL) { @@ -6535,7 +6547,7 @@ static void f_add(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = 1; /* Default: Failed */ if (argvars[0].v_type == VAR_LIST) { if ((l = argvars[0].vval.v_list) != NULL - && !tv_check_lock(l->lv_lock, "add() argument", TV_TRANSLATE)) { + && !tv_check_lock(tv_list_locked(l), "add() argument", TV_TRANSLATE)) { tv_list_append_tv(l, &argvars[1]); tv_copy(&argvars[0], rettv); } @@ -6586,9 +6598,10 @@ static void f_append(typval_T *argvars, typval_T *rettv, FunPtr fptr) && u_save(lnum, lnum + 1) == OK) { if (argvars[1].v_type == VAR_LIST) { l = argvars[1].vval.v_list; - if (l == NULL) + if (l == NULL) { return; - li = l->lv_first; + } + li = tv_list_first(l); } for (;; ) { if (l == NULL) { @@ -6596,7 +6609,7 @@ static void f_append(typval_T *argvars, typval_T *rettv, FunPtr fptr) } else if (li == NULL) { break; // End of list. } else { - tv = &li->li_tv; // Append item from list. + tv = TV_LIST_ITEM_TV(li); // Append item from list. } const char *const line = tv_get_string_chk(tv); if (line == NULL) { // Type error. @@ -6608,7 +6621,7 @@ static void f_append(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (l == NULL) { break; } - li = li->li_next; + li = TV_LIST_ITEM_NEXT(l, li); } appended_lines_mark(lnum, added); @@ -7247,30 +7260,26 @@ static void f_byteidxcomp(typval_T *argvars, typval_T *rettv, FunPtr fptr) int func_call(char_u *name, typval_T *args, partial_T *partial, dict_T *selfdict, typval_T *rettv) { - listitem_T *item; typval_T argv[MAX_FUNC_ARGS + 1]; int argc = 0; int dummy; int r = 0; - for (item = args->vval.v_list->lv_first; item != NULL; - item = item->li_next) { + TV_LIST_ITER(args->vval.v_list, item, { if (argc == MAX_FUNC_ARGS - (partial == NULL ? 0 : partial->pt_argc)) { EMSG(_("E699: Too many arguments")); - break; + goto func_call_skip_call; } - /* Make a copy of each argument. This is needed to be able to set - * v_lock to VAR_FIXED in the copy without changing the original list. - */ - tv_copy(&item->li_tv, &argv[argc++]); - } + // Make a copy of each argument. This is needed to be able to set + // v_lock to VAR_FIXED in the copy without changing the original list. + tv_copy(TV_LIST_ITEM_TV(item), &argv[argc++]); + }); - if (item == NULL) { - r = call_func(name, (int)STRLEN(name), rettv, argc, argv, NULL, - curwin->w_cursor.lnum, curwin->w_cursor.lnum, - &dummy, true, partial, selfdict); - } + r = call_func(name, (int)STRLEN(name), rettv, argc, argv, NULL, + curwin->w_cursor.lnum, curwin->w_cursor.lnum, + &dummy, true, partial, selfdict); +func_call_skip_call: // Free the arguments. while (argc > 0) { tv_clear(&argv[--argc]); @@ -7595,7 +7604,7 @@ static void f_count(typval_T *argvars, typval_T *rettv, FunPtr fptr) long idx; if ((l = argvars[0].vval.v_list) != NULL) { - li = l->lv_first; + li = tv_list_first(l); if (argvars[2].v_type != VAR_UNKNOWN) { bool error = false; @@ -7613,9 +7622,11 @@ static void f_count(typval_T *argvars, typval_T *rettv, FunPtr fptr) li = NULL; } - for (; li != NULL; li = li->li_next) - if (tv_equal(&li->li_tv, &argvars[1], ic, FALSE)) - ++n; + for (; li != NULL; li = TV_LIST_ITEM_NEXT(l, li)) { + if (tv_equal(TV_LIST_ITEM_TV(li), &argvars[1], ic, false)) { + n++; + } + } } } else if (argvars[0].v_type == VAR_DICT) { int todo; @@ -7933,34 +7944,51 @@ static void f_empty(typval_T *argvars, typval_T *rettv, FunPtr fptr) bool n = true; switch (argvars[0].v_type) { - case VAR_STRING: - case VAR_FUNC: - n = argvars[0].vval.v_string == NULL - || *argvars[0].vval.v_string == NUL; - break; - case VAR_PARTIAL: - n = false; - break; - case VAR_NUMBER: - n = argvars[0].vval.v_number == 0; - break; - case VAR_FLOAT: - n = argvars[0].vval.v_float == 0.0; - break; - case VAR_LIST: - n = argvars[0].vval.v_list == NULL - || argvars[0].vval.v_list->lv_first == NULL; - break; - case VAR_DICT: - n = argvars[0].vval.v_dict == NULL - || argvars[0].vval.v_dict->dv_hashtab.ht_used == 0; - break; - case VAR_SPECIAL: - n = argvars[0].vval.v_special != kSpecialVarTrue; - break; - case VAR_UNKNOWN: - EMSG2(_(e_intern2), "f_empty(UNKNOWN)"); - break; + case VAR_STRING: + case VAR_FUNC: { + n = argvars[0].vval.v_string == NULL + || *argvars[0].vval.v_string == NUL; + break; + } + case VAR_PARTIAL: { + n = false; + break; + } + case VAR_NUMBER: { + n = argvars[0].vval.v_number == 0; + break; + } + case VAR_FLOAT: { + n = argvars[0].vval.v_float == 0.0; + break; + } + case VAR_LIST: { + n = (tv_list_len(argvars[0].vval.v_list) == 0); + break; + } + case VAR_DICT: { + n = (tv_dict_len(argvars[0].vval.v_dict) == 0); + break; + } + case VAR_SPECIAL: { + // Using switch to get warning if SpecialVarValue receives more values. + switch (argvars[0].vval.v_special) { + case kSpecialVarTrue: { + n = false; + break; + } + case kSpecialVarFalse: + case kSpecialVarNull: { + n = true; + break; + } + } + break; + } + case VAR_UNKNOWN: { + EMSG2(_(e_intern2), "f_empty(UNKNOWN)"); + break; + } } rettv->vval.v_number = n; @@ -8024,17 +8052,22 @@ static void f_executable(typval_T *argvars, typval_T *rettv, FunPtr fptr) && os_can_exe((const char_u *)name, NULL, false))); } +typedef struct { + const list_T *const l; + const listitem_T *li; +} GetListLineCookie; + static char_u *get_list_line(int c, void *cookie, int indent) { - const listitem_T **const p = (const listitem_T **)cookie; - const listitem_T *item = *p; + GetListLineCookie *const p = (GetListLineCookie *)cookie; + const listitem_T *const item = p->li; if (item == NULL) { return NULL; } char buf[NUMBUFLEN]; - const char *const s = tv_get_string_buf_chk(&item->li_tv, buf); - *p = item->li_next; + const char *const s = tv_get_string_buf_chk(TV_LIST_ITEM_TV(item), buf); + p->li = TV_LIST_ITEM_NEXT(p->l, item); return (char_u *)(s == NULL ? NULL : xstrdup(s)); } @@ -8076,11 +8109,14 @@ static void f_execute(typval_T *argvars, typval_T *rettv, FunPtr fptr) do_cmdline_cmd(tv_get_string(&argvars[0])); } else if (argvars[0].vval.v_list != NULL) { list_T *const list = argvars[0].vval.v_list; - list->lv_refcount++; - listitem_T *const item = list->lv_first; - do_cmdline(NULL, get_list_line, (void *)&item, + tv_list_ref(list); + GetListLineCookie cookie = { + .l = list, + .li = tv_list_first(list), + }; + do_cmdline(NULL, get_list_line, (void *)&cookie, DOCMD_NOWAIT|DOCMD_VERBOSE|DOCMD_REPEAT|DOCMD_KEYTYPED); - list->lv_refcount--; + tv_list_unref(list); } msg_silent = save_msg_silent; emsg_silent = save_emsg_silent; @@ -8262,14 +8298,7 @@ static void f_extend(typval_T *argvars, typval_T *rettv, FunPtr fptr) list_T *const l1 = argvars[0].vval.v_list; list_T *const l2 = argvars[1].vval.v_list; - if (l1 == NULL) { - const bool locked = tv_check_lock(VAR_FIXED, arg_errmsg, TV_TRANSLATE); - (void)locked; - assert(locked == true); - } else if (l2 == NULL) { - // Do nothing - tv_copy(&argvars[0], rettv); - } else if (!tv_check_lock(l1->lv_lock, arg_errmsg, TV_TRANSLATE)) { + if (!tv_check_lock(tv_list_locked(l1), arg_errmsg, TV_TRANSLATE)) { listitem_T *item; if (argvars[2].v_type != VAR_UNKNOWN) { before = (long)tv_get_number_chk(&argvars[2], &error); @@ -8277,7 +8306,7 @@ static void f_extend(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; // Type error; errmsg already given. } - if (before == l1->lv_len) { + if (before == tv_list_len(l1)) { item = NULL; } else { item = tv_list_find(l1, before); @@ -8286,8 +8315,9 @@ static void f_extend(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } } - } else + } else { item = NULL; + } tv_list_extend(l1, l2, item); tv_copy(&argvars[0], rettv); @@ -8459,7 +8489,8 @@ static void filter_map(typval_T *argvars, typval_T *rettv, int map) if (argvars[0].v_type == VAR_LIST) { tv_copy(&argvars[0], rettv); if ((l = argvars[0].vval.v_list) == NULL - || (!map && tv_check_lock(l->lv_lock, arg_errmsg, TV_TRANSLATE))) { + || (!map && tv_check_lock(tv_list_locked(l), arg_errmsg, + TV_TRANSLATE))) { return; } } else if (argvars[0].v_type == VAR_DICT) { @@ -8522,14 +8553,15 @@ static void filter_map(typval_T *argvars, typval_T *rettv, int map) } else { vimvars[VV_KEY].vv_type = VAR_NUMBER; - for (li = l->lv_first; li != NULL; li = nli) { + for (li = tv_list_first(l); li != NULL; li = nli) { if (map - && tv_check_lock(li->li_tv.v_lock, arg_errmsg, TV_TRANSLATE)) { + && tv_check_lock(TV_LIST_ITEM_TV(li)->v_lock, arg_errmsg, + TV_TRANSLATE)) { break; } - nli = li->li_next; + nli = TV_LIST_ITEM_NEXT(l, li); vimvars[VV_KEY].vv_nr = idx; - if (filter_map_one(&li->li_tv, expr, map, &rem) == FAIL + if (filter_map_one(TV_LIST_ITEM_TV(li), expr, map, &rem) == FAIL || did_emsg) break; if (!map && rem) { @@ -8935,7 +8967,7 @@ static void common_function(typval_T *argvars, typval_T *rettv, goto theend; } list = argvars[arg_idx].vval.v_list; - if (list == NULL || list->lv_len == 0) { + if (list == NULL || tv_list_len(list) == 0) { arg_idx = 0; } } @@ -8946,7 +8978,7 @@ static void common_function(typval_T *argvars, typval_T *rettv, // result is a VAR_PARTIAL if (arg_idx > 0 || (arg_pt != NULL && arg_pt->pt_argc > 0)) { const int arg_len = (arg_pt == NULL ? 0 : arg_pt->pt_argc); - const int lv_len = (list == NULL ? 0 : list->lv_len); + const int lv_len = tv_list_len(list); pt->pt_argc = arg_len + lv_len; pt->pt_argv = xmalloc(sizeof(pt->pt_argv[0]) * pt->pt_argc); @@ -8955,11 +8987,9 @@ static void common_function(typval_T *argvars, typval_T *rettv, tv_copy(&arg_pt->pt_argv[i], &pt->pt_argv[i]); } if (lv_len > 0) { - for (listitem_T *li = list->lv_first; - li != NULL; - li = li->li_next) { - tv_copy(&li->li_tv, &pt->pt_argv[i++]); - } + TV_LIST_ITER(list, li, { + tv_copy(TV_LIST_ITEM_TV(li), &pt->pt_argv[i++]); + }); } } @@ -9045,7 +9075,7 @@ static void f_get(typval_T *argvars, typval_T *rettv, FunPtr fptr) li = tv_list_find(l, tv_get_number_chk(&argvars[1], &error)); if (!error && li != NULL) { - tv = &li->li_tv; + tv = TV_LIST_ITEM_TV(li); } } } else if (argvars[0].v_type == VAR_DICT) { @@ -10067,7 +10097,7 @@ static void f_getreg(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (rettv->vval.v_list == NULL) { rettv->vval.v_list = tv_list_alloc(); } - rettv->vval.v_list->lv_refcount++; + tv_list_ref(rettv->vval.v_list); } else { rettv->v_type = VAR_STRING; rettv->vval.v_string = get_reg_contents(regname, arg2 ? kGRegExprSrc : 0); @@ -11024,39 +11054,40 @@ static void f_indent(typval_T *argvars, typval_T *rettv, FunPtr fptr) */ static void f_index(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - list_T *l; - listitem_T *item; long idx = 0; - int ic = FALSE; + bool ic = false; rettv->vval.v_number = -1; if (argvars[0].v_type != VAR_LIST) { EMSG(_(e_listreq)); return; } - l = argvars[0].vval.v_list; + list_T *const l = argvars[0].vval.v_list; if (l != NULL) { - item = l->lv_first; + listitem_T *item = tv_list_first(l); if (argvars[2].v_type != VAR_UNKNOWN) { bool error = false; // Start at specified item. Use the cached index that tv_list_find() // sets, so that a negative number also works. - item = tv_list_find(l, tv_get_number_chk(&argvars[2], &error)); - idx = l->lv_idx; - if (argvars[3].v_type != VAR_UNKNOWN) { - ic = tv_get_number_chk(&argvars[3], &error); - } - if (error) { + idx = tv_list_uidx(l, tv_get_number_chk(&argvars[2], &error)); + if (error || idx == -1) { item = NULL; + } else { + item = tv_list_find(l, idx); + assert(item != NULL); + } + if (argvars[3].v_type != VAR_UNKNOWN) { + ic = !!tv_get_number_chk(&argvars[3], &error); } } - for (; item != NULL; item = item->li_next, ++idx) - if (tv_equal(&item->li_tv, &argvars[1], ic, FALSE)) { + for (; item != NULL; item = TV_LIST_ITEM_NEXT(l, item), idx++) { + if (tv_equal(TV_LIST_ITEM_TV(item), &argvars[1], ic, false)) { rettv->vval.v_number = idx; break; } + } } } @@ -11220,7 +11251,6 @@ static void f_inputdialog(typval_T *argvars, typval_T *rettv, FunPtr fptr) */ static void f_inputlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - listitem_T *li; int selected; int mouse_used; @@ -11235,15 +11265,16 @@ static void f_inputlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) msg_scroll = TRUE; msg_clr_eos(); - for (li = argvars[0].vval.v_list->lv_first; li != NULL; li = li->li_next) { - msg_puts(tv_get_string(&li->li_tv)); + TV_LIST_ITER_CONST(argvars[0].vval.v_list, li, { + msg_puts(tv_get_string(TV_LIST_ITEM_TV(li))); msg_putchar('\n'); - } + }); - /* Ask for choice. */ + // Ask for choice. selected = prompt_for_number(&mouse_used); - if (mouse_used) + if (mouse_used) { selected -= lines_left; + } rettv->vval.v_number = selected; } @@ -11299,9 +11330,8 @@ static void f_insert(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (argvars[0].v_type != VAR_LIST) { EMSG2(_(e_listarg), "insert()"); - } else if ((l = argvars[0].vval.v_list) != NULL - && !tv_check_lock(l->lv_lock, N_("insert() argument"), - TV_TRANSLATE)) { + } else if (!tv_check_lock(tv_list_locked((l = argvars[0].vval.v_list)), + N_("insert() argument"), TV_TRANSLATE)) { long before = 0; if (argvars[2].v_type != VAR_UNKNOWN) { before = tv_get_number_chk(&argvars[2], &error); @@ -11312,7 +11342,7 @@ static void f_insert(typval_T *argvars, typval_T *rettv, FunPtr fptr) } listitem_T *item = NULL; - if (before != l->lv_len) { + if (before != tv_list_len(l)) { item = tv_list_find(l, before); if (item == NULL) { EMSGN(_(e_listidx), before); @@ -11376,7 +11406,7 @@ static void f_islocked(typval_T *argvars, typval_T *rettv, FunPtr fptr) EMSG2(_(e_dictkey), lv.ll_newkey); } else if (lv.ll_list != NULL) { // List item. - rettv->vval.v_number = tv_islocked(&lv.ll_li->li_tv); + rettv->vval.v_number = tv_islocked(TV_LIST_ITEM_TV(lv.ll_li)); } else { // Dictionary item. rettv->vval.v_number = tv_islocked(&lv.ll_di->di_tv); @@ -11413,32 +11443,32 @@ static void dict_list(typval_T *const tv, typval_T *const rettv, switch (what) { case kDictListKeys: { - li->li_tv.v_type = VAR_STRING; - li->li_tv.v_lock = VAR_UNLOCKED; - li->li_tv.vval.v_string = vim_strsave(di->di_key); + TV_LIST_ITEM_TV(li)->v_type = VAR_STRING; + TV_LIST_ITEM_TV(li)->v_lock = VAR_UNLOCKED; + TV_LIST_ITEM_TV(li)->vval.v_string = vim_strsave(di->di_key); break; } case kDictListValues: { - tv_copy(&di->di_tv, &li->li_tv); + tv_copy(&di->di_tv, TV_LIST_ITEM_TV(li)); break; } case kDictListItems: { // items() list_T *const sub_l = tv_list_alloc(); - li->li_tv.v_type = VAR_LIST; - li->li_tv.v_lock = VAR_UNLOCKED; - li->li_tv.vval.v_list = sub_l; - sub_l->lv_refcount++; + TV_LIST_ITEM_TV(li)->v_type = VAR_LIST; + TV_LIST_ITEM_TV(li)->v_lock = VAR_UNLOCKED; + TV_LIST_ITEM_TV(li)->vval.v_list = sub_l; + tv_list_ref(sub_l); listitem_T *sub_li = tv_list_item_alloc(); tv_list_append(sub_l, sub_li); - sub_li->li_tv.v_type = VAR_STRING; - sub_li->li_tv.v_lock = VAR_UNLOCKED; - sub_li->li_tv.vval.v_string = vim_strsave(di->di_key); + TV_LIST_ITEM_TV(sub_li)->v_type = VAR_STRING; + TV_LIST_ITEM_TV(sub_li)->v_lock = VAR_UNLOCKED; + TV_LIST_ITEM_TV(sub_li)->vval.v_string = vim_strsave(di->di_key); sub_li = tv_list_item_alloc(); tv_list_append(sub_l, sub_li); - tv_copy(&di->di_tv, &sub_li->li_tv); + tv_copy(&di->di_tv, TV_LIST_ITEM_TV(sub_li)); break; } } @@ -11536,15 +11566,13 @@ static char **tv_to_argv(typval_T *cmd_tv, const char **cmd, bool *executable) } list_T *argl = cmd_tv->vval.v_list; - int argc = argl->lv_len; + int argc = tv_list_len(argl); if (!argc) { EMSG(_(e_invarg)); // List must have at least one item. return NULL; } - assert(argl->lv_first); - - const char *exe = tv_get_string_chk(&argl->lv_first->li_tv); + const char *exe = tv_get_string_chk(TV_LIST_ITEM_TV(tv_list_first(argl))); if (!exe || !os_can_exe((const char_u *)exe, NULL, true)) { if (exe && executable) { *executable = false; @@ -11559,15 +11587,15 @@ static char **tv_to_argv(typval_T *cmd_tv, const char **cmd, bool *executable) // Build the argument vector int i = 0; char **argv = xcalloc(argc + 1, sizeof(char *)); - for (listitem_T *arg = argl->lv_first; arg != NULL; arg = arg->li_next) { - const char *a = tv_get_string_chk(&arg->li_tv); + TV_LIST_ITER_CONST(argl, arg, { + const char *a = tv_get_string_chk(TV_LIST_ITEM_TV(arg)); if (!a) { // Did emsg in tv_get_string_chk; just deallocate argv. shell_free_argv(argv); return NULL; } argv[i++] = xstrdup(a); - } + }); return argv; } @@ -11695,7 +11723,7 @@ static void f_jobwait(typval_T *argvars, typval_T *rettv, FunPtr fptr) list_T *args = argvars[0].vval.v_list; - Channel **jobs = xcalloc(args->lv_len, sizeof(*jobs)); + Channel **jobs = xcalloc(tv_list_len(args), sizeof(*jobs)); ui_busy_start(); MultiQueue *waiting_jobs = multiqueue_new_parent(loop_on_put, &main_loop); @@ -11704,10 +11732,10 @@ static void f_jobwait(typval_T *argvars, typval_T *rettv, FunPtr fptr) // -1 for jobs that were skipped or timed out. int i = 0; - for (listitem_T *arg = args->lv_first; arg != NULL; arg = arg->li_next, i++) { + TV_LIST_ITER_CONST(args, arg, { Channel *chan = NULL; - if (arg->li_tv.v_type != VAR_NUMBER - || !(chan = find_job(arg->li_tv.vval.v_number, false))) { + if (TV_LIST_ITEM_TV(arg)->v_type != VAR_NUMBER + || !(chan = find_job(TV_LIST_ITEM_TV(arg)->vval.v_number, false))) { jobs[i] = NULL; } else { jobs[i] = chan; @@ -11719,7 +11747,8 @@ static void f_jobwait(typval_T *argvars, typval_T *rettv, FunPtr fptr) multiqueue_replace_parent(chan->events, waiting_jobs); } } - } + i++; + }); int remaining = -1; uint64_t before = 0; @@ -11728,7 +11757,7 @@ static void f_jobwait(typval_T *argvars, typval_T *rettv, FunPtr fptr) before = os_hrtime(); } - for (i = 0; i < args->lv_len; i++) { + for (i = 0; i < tv_list_len(args); i++) { if (remaining == 0) { // timed out break; @@ -11758,7 +11787,7 @@ static void f_jobwait(typval_T *argvars, typval_T *rettv, FunPtr fptr) list_T *rv = tv_list_alloc(); // restore the parent queue for any jobs still alive - for (i = 0; i < args->lv_len; i++) { + for (i = 0; i < tv_list_len(args); i++) { if (jobs[i] == NULL) { tv_list_append_number(rv, -3); continue; @@ -11774,7 +11803,7 @@ static void f_jobwait(typval_T *argvars, typval_T *rettv, FunPtr fptr) multiqueue_free(waiting_jobs); xfree(jobs); ui_busy_stop(); - rv->lv_refcount++; + tv_list_ref(rv); rettv->v_type = VAR_LIST; rettv->vval.v_list = rv; } @@ -11788,9 +11817,6 @@ static void f_join(typval_T *argvars, typval_T *rettv, FunPtr fptr) EMSG(_(e_listreq)); return; } - if (argvars[0].vval.v_list == NULL) { - return; - } const char *const sep = (argvars[1].v_type == VAR_UNKNOWN ? " " : tv_get_string_chk(&argvars[1])); @@ -12200,7 +12226,7 @@ static void find_some_match(typval_T *argvars, typval_T *rettv, int type) if (argvars[0].v_type == VAR_LIST) { if ((l = argvars[0].vval.v_list) == NULL) goto theend; - li = l->lv_first; + li = tv_list_first(l); } else { expr = str = (char_u *)tv_get_string(&argvars[0]); len = (long)STRLEN(str); @@ -12220,11 +12246,11 @@ static void find_some_match(typval_T *argvars, typval_T *rettv, int type) goto theend; } if (l != NULL) { - li = tv_list_find(l, start); + idx = tv_list_uidx(l, start); + li = tv_list_find(l, idx); if (li == NULL) { goto theend; } - idx = l->lv_idx; // Use the cached index. } else { if (start < 0) start = 0; @@ -12260,7 +12286,8 @@ static void find_some_match(typval_T *argvars, typval_T *rettv, int type) break; } xfree(tofree); - tofree = expr = str = (char_u *)encode_tv2echo(&li->li_tv, NULL); + tofree = expr = str = (char_u *)encode_tv2echo(TV_LIST_ITEM_TV(li), + NULL); if (str == NULL) { break; } @@ -12275,8 +12302,8 @@ static void find_some_match(typval_T *argvars, typval_T *rettv, int type) /* Advance to just after the match. */ if (l != NULL) { - li = li->li_next; - ++idx; + li = TV_LIST_ITEM_NEXT(l, li); + idx++; } else { startcol = (colnr_T)(regmatch.startp[0] + (*mb_ptr2len)(regmatch.startp[0]) - str); @@ -12289,18 +12316,22 @@ static void find_some_match(typval_T *argvars, typval_T *rettv, int type) if (match) { if (type == 4) { - listitem_T *li1 = rettv->vval.v_list->lv_first; - listitem_T *li2 = li1->li_next; - listitem_T *li3 = li2->li_next; - listitem_T *li4 = li3->li_next; - xfree(li1->li_tv.vval.v_string); - - int rd = (int)(regmatch.endp[0] - regmatch.startp[0]); - li1->li_tv.vval.v_string = vim_strnsave(regmatch.startp[0], rd); - li3->li_tv.vval.v_number = (varnumber_T)(regmatch.startp[0] - expr); - li4->li_tv.vval.v_number = (varnumber_T)(regmatch.endp[0] - expr); + list_T *const ret_l = rettv->vval.v_list; + listitem_T *li1 = tv_list_first(ret_l); + listitem_T *li2 = TV_LIST_ITEM_NEXT(ret_l, li1); + listitem_T *li3 = TV_LIST_ITEM_NEXT(ret_l, li2); + listitem_T *li4 = TV_LIST_ITEM_NEXT(ret_l, li3); + xfree(TV_LIST_ITEM_TV(li1)->vval.v_string); + + const size_t rd = (size_t)(regmatch.endp[0] - regmatch.startp[0]); + TV_LIST_ITEM_TV(li1)->vval.v_string = xmemdupz( + (const char *)regmatch.startp[0], rd); + TV_LIST_ITEM_TV(li3)->vval.v_number = (varnumber_T)( + regmatch.startp[0] - expr); + TV_LIST_ITEM_TV(li4)->vval.v_number = (varnumber_T)( + regmatch.endp[0] - expr); if (l != NULL) { - li2->li_tv.vval.v_number = (varnumber_T)idx; + TV_LIST_ITEM_TV(li2)->vval.v_number = (varnumber_T)idx; } } else if (type == 3) { int i; @@ -12318,7 +12349,7 @@ static void find_some_match(typval_T *argvars, typval_T *rettv, int type) } else if (type == 2) { // Return matched string. if (l != NULL) { - tv_copy(&li->li_tv, rettv); + tv_copy(TV_LIST_ITEM_TV(li), rettv); } else { rettv->vval.v_string = (char_u *)xmemdupz( (const char *)regmatch.startp[0], @@ -12342,8 +12373,8 @@ static void find_some_match(typval_T *argvars, typval_T *rettv, int type) if (type == 4 && l == NULL) { // matchstrpos() without a list: drop the second item - tv_list_item_remove(rettv->vval.v_list, - rettv->vval.v_list->lv_first->li_next); + list_T *const ret_l = rettv->vval.v_list; + tv_list_item_remove(ret_l, TV_LIST_ITEM_NEXT(ret_l, tv_list_first(ret_l))); } theend: @@ -12539,41 +12570,41 @@ static void max_min(const typval_T *const tv, typval_T *const rettv, const bool domax) FUNC_ATTR_NONNULL_ALL { - varnumber_T n = 0; bool error = false; + rettv->vval.v_number = 0; + varnumber_T n = (domax ? VARNUMBER_MIN : VARNUMBER_MAX); if (tv->v_type == VAR_LIST) { - const list_T *const l = tv->vval.v_list; - if (tv_list_len(l) != 0) { - n = tv_get_number_chk(&l->lv_first->li_tv, &error); - for (const listitem_T *li = l->lv_first->li_next; li != NULL && !error; - li = li->li_next) { - const varnumber_T i = tv_get_number_chk(&li->li_tv, &error); - if (domax ? i > n : i < n) { - n = i; - } - } + if (tv_list_len(tv->vval.v_list) == 0) { + return; } + TV_LIST_ITER_CONST(tv->vval.v_list, li, { + const varnumber_T i = tv_get_number_chk(TV_LIST_ITEM_TV(li), &error); + if (error) { + return; + } + if (domax ? i > n : i < n) { + n = i; + } + }); } else if (tv->v_type == VAR_DICT) { - if (tv->vval.v_dict != NULL) { - bool first = true; - TV_DICT_ITER(tv->vval.v_dict, di, { - const varnumber_T i = tv_get_number_chk(&di->di_tv, &error); - if (error) { - break; - } - if (first) { - n = i; - first = true; - } else if (domax ? i > n : i < n) { - n = i; - } - }); + if (tv_dict_len(tv->vval.v_dict) == 0) { + return; } + TV_DICT_ITER(tv->vval.v_dict, di, { + const varnumber_T i = tv_get_number_chk(&di->di_tv, &error); + if (error) { + return; + } + if (domax ? i > n : i < n) { + n = i; + } + }); } else { EMSG2(_(e_listdictarg), domax ? "max()" : "min()"); + return; } - rettv->vval.v_number = error ? 0 : n; + rettv->vval.v_number = n; } /* @@ -12659,22 +12690,19 @@ static void f_msgpackdump(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } list_T *ret_list = tv_list_alloc_ret(rettv); - const list_T *list = argvars[0].vval.v_list; - if (list == NULL) { - return; - } + list_T *list = argvars[0].vval.v_list; msgpack_packer *lpacker = msgpack_packer_new(ret_list, &encode_list_write); const char *const msg = _("msgpackdump() argument, index %i"); // Assume that translation will not take more then 4 times more space char msgbuf[sizeof("msgpackdump() argument, index ") * 4 + NUMBUFLEN]; int idx = 0; - for (listitem_T *li = list->lv_first; li != NULL; li = li->li_next) { - vim_snprintf(msgbuf, sizeof(msgbuf), (char *) msg, idx); + TV_LIST_ITER(list, li, { + vim_snprintf(msgbuf, sizeof(msgbuf), (char *)msg, idx); idx++; - if (encode_vim_to_msgpack(lpacker, &li->li_tv, msgbuf) == FAIL) { + if (encode_vim_to_msgpack(lpacker, TV_LIST_ITEM_TV(li), msgbuf) == FAIL) { break; } - } + }); msgpack_packer_free(lpacker); } @@ -12688,10 +12716,10 @@ static void f_msgpackparse(typval_T *argvars, typval_T *rettv, FunPtr fptr) } list_T *ret_list = tv_list_alloc_ret(rettv); const list_T *list = argvars[0].vval.v_list; - if (list == NULL || list->lv_first == NULL) { + if (tv_list_len(list) == 0) { return; } - if (list->lv_first->li_tv.v_type != VAR_STRING) { + if (TV_LIST_ITEM_TV(tv_list_first(list))->v_type != VAR_STRING) { EMSG2(_(e_invarg2), "List item is not a string"); return; } @@ -12732,9 +12760,9 @@ static void f_msgpackparse(typval_T *argvars, typval_T *rettv, FunPtr fptr) } if (result == MSGPACK_UNPACK_SUCCESS) { listitem_T *li = tv_list_item_alloc(); - li->li_tv.v_type = VAR_UNKNOWN; + TV_LIST_ITEM_TV(li)->v_type = VAR_UNKNOWN; tv_list_append(ret_list, li); - if (msgpack_to_vim(unpacked.data, &li->li_tv) == FAIL) { + if (msgpack_to_vim(unpacked.data, TV_LIST_ITEM_TV(li)) == FAIL) { EMSG2(_(e_invarg2), "Failed to convert msgpack string"); goto f_msgpackparse_exit; } @@ -13027,9 +13055,9 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) } li = tv_list_item_alloc(); - li->li_tv.v_type = VAR_STRING; - li->li_tv.v_lock = 0; - li->li_tv.vval.v_string = s; + TV_LIST_ITEM_TV(li)->v_type = VAR_STRING; + TV_LIST_ITEM_TV(li)->v_lock = VAR_UNLOCKED; + TV_LIST_ITEM_TV(li)->vval.v_string = s; tv_list_append(rettv->vval.v_list, li); start = p + 1; /* step over newline */ @@ -13106,7 +13134,8 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) */ if (maxline < 0) while (cnt > -maxline) { - tv_list_item_remove(rettv->vval.v_list, rettv->vval.v_list->lv_first); + tv_list_item_remove(rettv->vval.v_list, + tv_list_first(rettv->vval.v_list)); cnt--; } @@ -13123,9 +13152,7 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// @return OK In case of success, FAIL in case of error static int list2proftime(typval_T *arg, proftime_T *tm) FUNC_ATTR_NONNULL_ALL { - if (arg->v_type != VAR_LIST - || arg->vval.v_list == NULL - || arg->vval.v_list->lv_len != 2) { + if (arg->v_type != VAR_LIST || tv_list_len(arg->vval.v_list) != 2) { return FAIL; } @@ -13249,8 +13276,8 @@ static void f_remove(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } else if (argvars[0].v_type != VAR_LIST) { EMSG2(_(e_listdictarg), "remove()"); - } else if ((l = argvars[0].vval.v_list) != NULL - && !tv_check_lock(l->lv_lock, arg_errmsg, TV_TRANSLATE)) { + } else if (!tv_check_lock(tv_list_locked((l = argvars[0].vval.v_list)), + arg_errmsg, TV_TRANSLATE)) { bool error = false; idx = tv_get_number_chk(&argvars[1], &error); @@ -13262,7 +13289,7 @@ static void f_remove(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (argvars[2].v_type == VAR_UNKNOWN) { // Remove one item, return its value. tv_list_remove_items(l, item, item); - *rettv = item->li_tv; + *rettv = *TV_LIST_ITEM_TV(item); xfree(item); } else { // Remove range of items, return list with values. @@ -13274,15 +13301,17 @@ static void f_remove(typval_T *argvars, typval_T *rettv, FunPtr fptr) } else { int cnt = 0; - for (li = item; li != NULL; li = li->li_next) { - ++cnt; - if (li == item2) + for (li = item; li != NULL; li = TV_LIST_ITEM_NEXT(l, li)) { + cnt++; + if (li == item2) { break; + } } if (li == NULL) { // Didn't find "item2" after "item". emsgf(_(e_invrange)); } else { tv_list_remove_items(l, item, item2); + // FIXME: Abstract the below away or move to eval/typval. l = tv_list_alloc_ret(rettv); l->lv_first = item; l->lv_last = item2; @@ -13524,9 +13553,9 @@ static void f_reverse(typval_T *argvars, typval_T *rettv, FunPtr fptr) list_T *l; if (argvars[0].v_type != VAR_LIST) { EMSG2(_(e_listarg), "reverse()"); - } else if ((l = argvars[0].vval.v_list) != NULL - && !tv_check_lock(l->lv_lock, N_("reverse() argument"), - TV_TRANSLATE)) { + } else if (!tv_check_lock(tv_list_locked((l = argvars[0].vval.v_list)), + N_("reverse() argument"), TV_TRANSLATE)) { + // FIXME: Abstract the below away or move to eval/typval. listitem_T *li = l->lv_last; l->lv_first = l->lv_last = NULL; l->lv_len = 0; @@ -13537,7 +13566,7 @@ static void f_reverse(typval_T *argvars, typval_T *rettv, FunPtr fptr) } rettv->vval.v_list = l; rettv->v_type = VAR_LIST; - l->lv_refcount++; + tv_list_ref(l); l->lv_idx = l->lv_len - l->lv_idx - 1; } } @@ -13836,14 +13865,17 @@ static void f_rpcstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) int argsl = 0; if (argvars[1].v_type == VAR_LIST) { args = argvars[1].vval.v_list; - argsl = args->lv_len; + argsl = tv_list_len(args); // Assert that all list items are strings - for (listitem_T *arg = args->lv_first; arg != NULL; arg = arg->li_next) { - if (arg->li_tv.v_type != VAR_STRING) { - EMSG(_(e_invarg)); + int i = 0; + TV_LIST_ITER_CONST(args, arg, { + if (TV_LIST_ITEM_TV(arg)->v_type != VAR_STRING) { + emsgf(_("E5010: List item %d of the second argument is not a string"), + i); return; } - } + i++; + }); } if (argvars[0].vval.v_string == NULL || argvars[0].vval.v_string[0] == NUL) { @@ -13861,9 +13893,9 @@ static void f_rpcstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) int i = 1; // Copy arguments to the vector if (argsl > 0) { - for (listitem_T *arg = args->lv_first; arg != NULL; arg = arg->li_next) { - argv[i++] = xstrdup(tv_get_string(&arg->li_tv)); - } + TV_LIST_ITER_CONST(args, arg, { + argv[i++] = xstrdup(tv_get_string(TV_LIST_ITEM_TV(arg))); + }); } // The last item of argv must be NULL @@ -14292,9 +14324,9 @@ static void f_serverlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) list_T *l = tv_list_alloc_ret(rettv); for (size_t i = 0; i < n; i++) { listitem_T *li = tv_list_item_alloc(); - li->li_tv.v_type = VAR_STRING; - li->li_tv.v_lock = 0; - li->li_tv.vval.v_string = (char_u *)addrs[i]; + TV_LIST_ITEM_TV(li)->v_type = VAR_STRING; + TV_LIST_ITEM_TV(li)->v_lock = VAR_UNLOCKED; + TV_LIST_ITEM_TV(li)->vval.v_string = (char_u *)addrs[i]; tv_list_append(l, li); } xfree(addrs); @@ -14503,7 +14535,7 @@ static void f_setline(typval_T *argvars, typval_T *rettv, FunPtr fptr) const char *line = NULL; if (argvars[1].v_type == VAR_LIST) { l = argvars[1].vval.v_list; - li = l->lv_first; + li = tv_list_first(l); } else { line = tv_get_string_chk(&argvars[1]); } @@ -14515,8 +14547,8 @@ static void f_setline(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (li == NULL) { break; } - line = tv_get_string_chk(&li->li_tv); - li = li->li_next; + line = tv_get_string_chk(TV_LIST_ITEM_TV(li)); + li = TV_LIST_ITEM_NEXT(l, li); } rettv->vval.v_number = 1; /* FAIL */ @@ -14648,8 +14680,6 @@ static void f_setloclist(typval_T *argvars, typval_T *rettv, FunPtr fptr) */ static void f_setmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - list_T *l; - listitem_T *li; dict_T *d; list_T *s = NULL; @@ -14658,15 +14688,17 @@ static void f_setmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) EMSG(_(e_listreq)); return; } + list_T *l; if ((l = argvars[0].vval.v_list) != NULL) { - /* To some extent make sure that we are dealing with a list from - * "getmatches()". */ - li = l->lv_first; - while (li != NULL) { - if (li->li_tv.v_type != VAR_DICT - || (d = li->li_tv.vval.v_dict) == NULL) { - EMSG(_(e_invarg)); + // To some extent make sure that we are dealing with a list from + // "getmatches()". + int i = 0; + TV_LIST_ITER_CONST(l, li, { + if (TV_LIST_ITEM_TV(li)->v_type != VAR_DICT + || (d = TV_LIST_ITEM_TV(li)->vval.v_dict) == NULL) { + emsgf(_("E474: List item %d is either not a dictionary " + "or an empty one"), i); return; } if (!(tv_dict_find(d, S_LEN("group")) != NULL @@ -14674,19 +14706,18 @@ static void f_setmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) || tv_dict_find(d, S_LEN("pos1")) != NULL) && tv_dict_find(d, S_LEN("priority")) != NULL && tv_dict_find(d, S_LEN("id")) != NULL)) { - EMSG(_(e_invarg)); + emsgf(_("E474: List item %d is missing one of the required keys"), i); return; } - li = li->li_next; - } + i++; + }); clear_matches(curwin); - li = l->lv_first; bool match_add_failed = false; - while (li != NULL) { + TV_LIST_ITER_CONST(l, li, { int i = 0; - d = li->li_tv.vval.v_dict; + d = TV_LIST_ITEM_TV(li)->vval.v_dict; dictitem_T *const di = tv_dict_find(d, S_LEN("pattern")); if (di == NULL) { if (s == NULL) { @@ -14704,7 +14735,7 @@ static void f_setmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) } tv_list_append_tv(s, &pos_di->di_tv); - s->lv_refcount++; + tv_list_ref(s); } else { break; } @@ -14739,8 +14770,7 @@ static void f_setmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) tv_list_unref(s); s = NULL; } - li = li->li_next; - } + }); if (!match_add_failed) { rettv->vval.v_number = 0; } @@ -14860,7 +14890,7 @@ static void f_setreg(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (argvars[1].v_type == VAR_LIST) { list_T *ll = argvars[1].vval.v_list; // If the list is NULL handle like an empty list. - int len = ll == NULL ? 0 : ll->lv_len; + const int len = tv_list_len(ll); // First half: use for pointers to result lines; second half: use for // pointers to allocated copies. @@ -14869,11 +14899,9 @@ static void f_setreg(typval_T *argvars, typval_T *rettv, FunPtr fptr) char **allocval = lstval + len + 2; char **curallocval = allocval; - for (listitem_T *li = ll == NULL ? NULL : ll->lv_first; - li != NULL; - li = li->li_next) { + TV_LIST_ITER_CONST(ll, li, { char buf[NUMBUFLEN]; - *curval = tv_get_string_buf_chk(&li->li_tv, buf); + *curval = tv_get_string_buf_chk(TV_LIST_ITEM_TV(li), buf); if (*curval == NULL) { goto free_lstval; } @@ -14885,7 +14913,7 @@ static void f_setreg(typval_T *argvars, typval_T *rettv, FunPtr fptr) curallocval++; } curval++; - } + }); *curval++ = NULL; write_reg_contents_lst(regname, (char_u **)lstval, append, yank_type, @@ -15135,8 +15163,8 @@ static int item_compare(const void *s1, const void *s2, bool keep_zero) sortItem_T *const si1 = (sortItem_T *)s1; sortItem_T *const si2 = (sortItem_T *)s2; - typval_T *const tv1 = &si1->item->li_tv; - typval_T *const tv2 = &si2->item->li_tv; + typval_T *const tv1 = TV_LIST_ITEM_TV(si1->item); + typval_T *const tv2 = TV_LIST_ITEM_TV(si2->item); int res; @@ -15249,8 +15277,8 @@ static int item_compare2(const void *s1, const void *s2, bool keep_zero) // Copy the values. This is needed to be able to set v_lock to VAR_FIXED // in the copy without changing the original list items. - tv_copy(&si1->item->li_tv, &argv[0]); - tv_copy(&si2->item->li_tv, &argv[1]); + tv_copy(TV_LIST_ITEM_TV(si1->item), &argv[0]); + tv_copy(TV_LIST_ITEM_TV(si2->item), &argv[1]); rettv.v_type = VAR_UNKNOWN; // tv_clear() uses this res = call_func((const char_u *)func_name, @@ -15294,8 +15322,6 @@ static int item_compare2_not_keeping_zero(const void *s1, const void *s2) */ static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort) { - list_T *l; - listitem_T *li; sortItem_T *ptrs; long len; long i; @@ -15313,13 +15339,13 @@ static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort) if (argvars[0].v_type != VAR_LIST) { EMSG2(_(e_listarg), sort ? "sort()" : "uniq()"); } else { - l = argvars[0].vval.v_list; - if (l == NULL || tv_check_lock(l->lv_lock, arg_errmsg, TV_TRANSLATE)) { + list_T *const l = argvars[0].vval.v_list; + if (tv_check_lock(tv_list_locked(l), arg_errmsg, TV_TRANSLATE)) { goto theend; } rettv->vval.v_list = l; rettv->v_type = VAR_LIST; - ++l->lv_refcount; + tv_list_ref(l); len = tv_list_len(l); if (len <= 1) { @@ -15391,11 +15417,11 @@ static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort) i = 0; if (sort) { // sort(): ptrs will be the list to sort. - for (li = l->lv_first; li != NULL; li = li->li_next) { + TV_LIST_ITER(l, li, { ptrs[i].item = li; ptrs[i].idx = i; i++; - } + }); info.item_compare_func_err = false; // Test the compare function. @@ -15414,6 +15440,7 @@ static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort) if (!info.item_compare_func_err) { // Clear the list and append the items in the sorted order. + // FIXME: Somehow abstract away or move to eval/typval. l->lv_first = NULL; l->lv_last = NULL; l->lv_idx_item = NULL; @@ -15431,25 +15458,30 @@ static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort) info.item_compare_func_err = false; if (info.item_compare_func != NULL || info.item_compare_partial != NULL) { - item_compare_func_ptr = item_compare2_keeping_zero; + item_compare_func_ptr = item_compare2_keeping_zero; } else { - item_compare_func_ptr = item_compare_keeping_zero; + item_compare_func_ptr = item_compare_keeping_zero; } - for (li = l->lv_first; li != NULL && li->li_next != NULL; li = li->li_next) { - if (item_compare_func_ptr(&li, &li->li_next) == 0) { - ptrs[i++].item = li; - } - if (info.item_compare_func_err) { - EMSG(_("E882: Uniq compare function failed")); - break; + listitem_T *prev_li = NULL; + TV_LIST_ITER(l, li, { + if (prev_li != NULL) { + if (item_compare_func_ptr(prev_li, li) == 0) { + ptrs[i++].item = prev_li; + } + if (info.item_compare_func_err) { + EMSG(_("E882: Uniq compare function failed")); + break; + } } - } + prev_li = li; + }); if (!info.item_compare_func_err) { while (--i >= 0) { + // FIXME: Abstract away. assert(ptrs[i].item->li_next); - li = ptrs[i].item->li_next; + listitem_T *const li = ptrs[i].item->li_next; ptrs[i].item->li_next = li->li_next; if (li->li_next != NULL) { li->li_next->li_prev = ptrs[i].item; @@ -15592,7 +15624,6 @@ static void f_spellsuggest(typval_T *argvars, typval_T *rettv, FunPtr fptr) bool typeerr = false; int maxcount; garray_T ga; - listitem_T *li; bool need_capital = false; tv_list_alloc_ret(rettv); @@ -15618,10 +15649,10 @@ static void f_spellsuggest(typval_T *argvars, typval_T *rettv, FunPtr fptr) for (int i = 0; i < ga.ga_len; i++) { char *p = ((char **)ga.ga_data)[i]; - li = tv_list_item_alloc(); - li->li_tv.v_type = VAR_STRING; - li->li_tv.v_lock = 0; - li->li_tv.vval.v_string = (char_u *)p; + listitem_T *const li = tv_list_item_alloc(); + TV_LIST_ITEM_TV(li)->v_type = VAR_STRING; + TV_LIST_ITEM_TV(li)->v_lock = VAR_LOCKED; + TV_LIST_ITEM_TV(li)->vval.v_string = (char_u *)p; tv_list_append(rettv->vval.v_list, li); } ga_clear(&ga); @@ -15677,7 +15708,7 @@ static void f_split(typval_T *argvars, typval_T *rettv, FunPtr fptr) } else { end = str + strlen(str); } - if (keepempty || end > str || (rettv->vval.v_list->lv_len > 0 + if (keepempty || end > str || (tv_list_len(rettv->vval.v_list) > 0 && *str != NUL && match && end < (const char *)regmatch.endp[0])) { @@ -16405,7 +16436,7 @@ static void get_system_output_as_rettv(typval_T *argvars, typval_T *rettv, keepempty = tv_get_number(&argvars[2]); } rettv->vval.v_list = string_to_list(res, nread, (bool)keepempty); - rettv->vval.v_list->lv_refcount++; + tv_list_ref(rettv->vval.v_list); rettv->v_type = VAR_LIST; xfree(res); @@ -17451,8 +17482,8 @@ static bool write_list(FileDescriptor *const fp, const list_T *const list, const bool binary) { int error = 0; - for (const listitem_T *li = list->lv_first; li != NULL; li = li->li_next) { - const char *const s = tv_get_string_chk(&li->li_tv); + TV_LIST_ITER_CONST(list, li, { + const char *const s = tv_get_string_chk(TV_LIST_ITEM_TV(li)); if (s == NULL) { return false; } @@ -17479,14 +17510,14 @@ static bool write_list(FileDescriptor *const fp, const list_T *const list, } } } - if (!binary || li->li_next != NULL) { + if (!binary || TV_LIST_ITEM_NEXT(list, li) != NULL) { const ptrdiff_t written = file_write(fp, "\n", 1); if (written < 0) { error = (int)written; goto write_list_error; } } - } + }); if ((error = file_flush(fp)) != 0) { goto write_list_error; } @@ -17499,13 +17530,14 @@ write_list_error: /// Initializes a static list with 10 items. void init_static_list(staticList10_T *sl) { + // FIXME: Move to eval/typval. list_T *l = &sl->sl_list; memset(sl, 0, sizeof(staticList10_T)); l->lv_first = &sl->sl_items[0]; l->lv_last = &sl->sl_items[9]; l->lv_refcount = DO_NOT_FREE_CNT; - l->lv_lock = VAR_FIXED; + tv_list_set_lock(l, VAR_FIXED); sl->sl_list.lv_len = 10; for (int i = 0; i < 10; i++) { @@ -17556,9 +17588,9 @@ static char *save_tv_as_string(typval_T *tv, ptrdiff_t *const len, bool endnl) // Pre-calculate the resulting length. *len = 0; list_T *list = tv->vval.v_list; - for (listitem_T *li = list->lv_first; li != NULL; li = li->li_next) { - *len += strlen(tv_get_string(&li->li_tv)) + 1; - } + TV_LIST_ITER_CONST(list, li, { + *len += strlen(tv_get_string(TV_LIST_ITEM_TV(li))) + 1; + }); if (*len == 0) { return NULL; @@ -17566,14 +17598,14 @@ static char *save_tv_as_string(typval_T *tv, ptrdiff_t *const len, bool endnl) char *ret = xmalloc(*len + endnl); char *end = ret; - for (listitem_T *li = list->lv_first; li != NULL; li = li->li_next) { - for (const char *s = tv_get_string(&li->li_tv); *s != NUL; s++) { + TV_LIST_ITER_CONST(list, li, { + for (const char *s = tv_get_string(TV_LIST_ITEM_TV(li)); *s != NUL; s++) { *end++ = (*s == '\n') ? NUL : *s; } - if (endnl || li->li_next != NULL) { + if (endnl || TV_LIST_ITEM_NEXT(list, li) != NULL) { *end++ = '\n'; } - } + }); *end = NUL; *len = end - ret; return ret; @@ -17720,9 +17752,9 @@ pos_T *var2fpos(const typval_T *const tv, const int dollar_lnum, // We accept "$" for the column number: last column. li = tv_list_find(l, 1L); - if (li != NULL && li->li_tv.v_type == VAR_STRING - && li->li_tv.vval.v_string != NULL - && STRCMP(li->li_tv.vval.v_string, "$") == 0) { + if (li != NULL && TV_LIST_ITEM_TV(li)->v_type == VAR_STRING + && TV_LIST_ITEM_TV(li)->vval.v_string != NULL + && STRCMP(TV_LIST_ITEM_TV(li)->vval.v_string, "$") == 0) { pos.col = len + 1; } @@ -17799,17 +17831,18 @@ pos_T *var2fpos(const typval_T *const tv, const int dollar_lnum, */ static int list2fpos(typval_T *arg, pos_T *posp, int *fnump, colnr_T *curswantp) { - list_T *l = arg->vval.v_list; + list_T *l; long i = 0; long n; - /* List must be: [fnum, lnum, col, coladd, curswant], where "fnum" is only - * there when "fnump" isn't NULL; "coladd" and "curswant" are optional. */ + // List must be: [fnum, lnum, col, coladd, curswant], where "fnum" is only + // there when "fnump" isn't NULL; "coladd" and "curswant" are optional. if (arg->v_type != VAR_LIST - || l == NULL - || l->lv_len < (fnump == NULL ? 2 : 3) - || l->lv_len > (fnump == NULL ? 4 : 5)) + || (l = arg->vval.v_list) == NULL + || tv_list_len(l) < (fnump == NULL ? 2 : 3) + || tv_list_len(l) > (fnump == NULL ? 4 : 5)) { return FAIL; + } if (fnump != NULL) { n = tv_list_find_nr(l, i++, NULL); // fnum @@ -18234,7 +18267,7 @@ void set_vim_var_list(const VimVarIndex idx, list_T *const val) vimvars[idx].vv_type = VAR_LIST; vimvars[idx].vv_list = val; if (val != NULL) { - val->lv_refcount++; + tv_list_ref(val); } } @@ -19270,9 +19303,10 @@ int var_item_copy(const vimconv_T *const conv, if (from->vval.v_list == NULL) to->vval.v_list = NULL; else if (copyID != 0 && from->vval.v_list->lv_copyID == copyID) { - /* use the copy made earlier */ + // FIXME: Abstract away. + // Use the copy made earlier. to->vval.v_list = from->vval.v_list->lv_copylist; - ++to->vval.v_list->lv_refcount; + tv_list_ref(to->vval.v_list); } else { to->vval.v_list = tv_list_copy(conv, from->vval.v_list, deep, copyID); } @@ -21153,9 +21187,10 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, v->di_tv.v_type = VAR_LIST; v->di_tv.v_lock = VAR_FIXED; v->di_tv.vval.v_list = &fc->l_varlist; + // FIXME: Abstract away static list. memset(&fc->l_varlist, 0, sizeof(list_T)); fc->l_varlist.lv_refcount = DO_NOT_FREE_CNT; - fc->l_varlist.lv_lock = VAR_FIXED; + tv_list_set_lock(&fc->l_varlist, VAR_FIXED); // Set a:firstline to "firstline" and a:lastline to "lastline". // Set a:name to named arguments. @@ -21204,8 +21239,8 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, if (ai >= 0 && ai < MAX_FUNC_ARGS) { tv_list_append(&fc->l_varlist, &fc->l_listitems[ai]); - fc->l_listitems[ai].li_tv = argvars[i]; - fc->l_listitems[ai].li_tv.v_lock = VAR_FIXED; + *TV_LIST_ITEM_TV(&fc->l_listitems[ai]) = argvars[i]; + TV_LIST_ITEM_TV(&fc->l_listitems[ai])->v_lock = VAR_FIXED; } } @@ -21407,10 +21442,9 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, }); // Make a copy of the a:000 items, since we didn't do that above. - for (listitem_T *li = fc->l_varlist.lv_first; li != NULL; - li = li->li_next) { - tv_copy(&li->li_tv, &li->li_tv); - } + TV_LIST_ITER(&fc->l_varlist, li, { + tv_copy(TV_LIST_ITEM_TV(li), TV_LIST_ITEM_TV(li)); + }); } if (--fp->uf_calls <= 0 && fp->uf_refcount <= 0) { @@ -21437,6 +21471,7 @@ static void funccal_unref(funccall_T *fc, ufunc_T *fp, bool force) return; } + // FIXME: Abstract away static list implementation details. if (--fc->fc_refcount <= 0 && (force || ( fc->l_varlist.lv_refcount == DO_NOT_FREE_CNT && fc->l_vars.dv_refcount == DO_NOT_FREE_CNT @@ -21475,8 +21510,6 @@ free_funccal ( int free_val /* a: vars were allocated */ ) { - listitem_T *li; - for (int i = 0; i < fc->fc_funcs.ga_len; i++) { ufunc_T *fp = ((ufunc_T **)(fc->fc_funcs.ga_data))[i]; @@ -21494,14 +21527,14 @@ free_funccal ( // allocated variables. vars_clear_ext(&fc->l_avars.dv_hashtab, free_val); - /* free all l: variables */ + // Free all l: variables. vars_clear(&fc->l_vars.dv_hashtab); // Free the a:000 variables if they were allocated. if (free_val) { - for (li = fc->l_varlist.lv_first; li != NULL; li = li->li_next) { - tv_clear(&li->li_tv); - } + TV_LIST_ITER(&fc->l_varlist, li, { + tv_clear(TV_LIST_ITEM_TV(li)); + }); } func_ptr_unref(fc->func); @@ -22437,7 +22470,7 @@ typval_T eval_call_provider(char *provider, char *method, list_T *arguments) {.v_type = VAR_UNKNOWN} }; typval_T rettv = {.v_type = VAR_UNKNOWN, .v_lock = 0}; - arguments->lv_refcount++; + tv_list_ref(arguments); int dummy; (void)call_func((const char_u *)func, diff --git a/src/nvim/eval/decode.c b/src/nvim/eval/decode.c index 0933b1bf9c..e3098683a4 100644 --- a/src/nvim/eval/decode.c +++ b/src/nvim/eval/decode.c @@ -61,7 +61,7 @@ static inline void create_special_dict(typval_T *const rettv, type_di->di_tv.v_type = VAR_LIST; type_di->di_tv.v_lock = VAR_UNLOCKED; type_di->di_tv.vval.v_list = (list_T *) eval_msgpack_type_lists[type]; - type_di->di_tv.vval.v_list->lv_refcount++; + tv_list_ref(type_di->di_tv.vval.v_list); tv_dict_add(dict, type_di); dictitem_T *const val_di = tv_dict_item_alloc_len(S_LEN("_VAL")); val_di->di_tv = val; @@ -234,7 +234,7 @@ list_T *decode_create_map_special_dict(typval_T *const ret_tv) FUNC_ATTR_NONNULL_ALL { list_T *const list = tv_list_alloc(); - list->lv_refcount++; + tv_list_ref(list); create_special_dict(ret_tv, kMPMap, ((typval_T) { .v_type = VAR_LIST, .v_lock = VAR_UNLOCKED, @@ -270,7 +270,7 @@ typval_T decode_string(const char *const s, const size_t len, : (bool)hasnul); if (really_hasnul) { list_T *const list = tv_list_alloc(); - list->lv_refcount++; + tv_list_ref(list); typval_T tv; create_special_dict(&tv, binary ? kMPBinary : kMPString, ((typval_T) { .v_type = VAR_LIST, @@ -849,7 +849,7 @@ json_decode_string_cycle_start: } case '[': { list_T *list = tv_list_alloc(); - list->lv_refcount++; + tv_list_ref(list); typval_T tv = { .v_type = VAR_LIST, .v_lock = VAR_UNLOCKED, @@ -970,7 +970,7 @@ int msgpack_to_vim(const msgpack_object mobj, typval_T *const rettv) }; } else { list_T *const list = tv_list_alloc(); - list->lv_refcount++; + tv_list_ref(list); create_special_dict(rettv, kMPInteger, ((typval_T) { .v_type = VAR_LIST, .v_lock = VAR_UNLOCKED, @@ -993,7 +993,7 @@ int msgpack_to_vim(const msgpack_object mobj, typval_T *const rettv) }; } else { list_T *const list = tv_list_alloc(); - list->lv_refcount++; + tv_list_ref(list); create_special_dict(rettv, kMPInteger, ((typval_T) { .v_type = VAR_LIST, .v_lock = VAR_UNLOCKED, @@ -1039,7 +1039,7 @@ int msgpack_to_vim(const msgpack_object mobj, typval_T *const rettv) } case MSGPACK_OBJECT_ARRAY: { list_T *const list = tv_list_alloc(); - list->lv_refcount++; + tv_list_ref(list); *rettv = (typval_T) { .v_type = VAR_LIST, .v_lock = VAR_UNLOCKED, @@ -1110,7 +1110,7 @@ msgpack_to_vim_generic_map: {} } case MSGPACK_OBJECT_EXT: { list_T *const list = tv_list_alloc(); - list->lv_refcount++; + tv_list_ref(list); tv_list_append_number(list, mobj.via.ext.type); list_T *const ext_val_list = tv_list_alloc(); tv_list_append_list(list, ext_val_list); diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 4bc3a85efb..eaf70b41fd 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -66,7 +66,7 @@ listitem_T *tv_list_item_alloc(void) void tv_list_item_free(listitem_T *const item) FUNC_ATTR_NONNULL_ALL { - tv_clear(&item->li_tv); + tv_clear(TV_LIST_ITEM_TV(item)); xfree(item); } @@ -326,7 +326,7 @@ void tv_list_append_tv(list_T *const l, typval_T *const tv) FUNC_ATTR_NONNULL_ALL { listitem_T *const li = tv_list_item_alloc(); - tv_copy(tv, &li->li_tv); + tv_copy(tv, TV_LIST_ITEM_TV(li)); tv_list_append(l, li); } @@ -339,12 +339,12 @@ void tv_list_append_list(list_T *const list, list_T *const itemlist) { listitem_T *const li = tv_list_item_alloc(); - li->li_tv.v_type = VAR_LIST; - li->li_tv.v_lock = VAR_UNLOCKED; - li->li_tv.vval.v_list = itemlist; + TV_LIST_ITEM_TV(li)->v_type = VAR_LIST; + TV_LIST_ITEM_TV(li)->v_lock = VAR_UNLOCKED; + TV_LIST_ITEM_TV(li)->vval.v_list = itemlist; tv_list_append(list, li); if (itemlist != NULL) { - itemlist->lv_refcount++; + tv_list_ref(itemlist); } } @@ -357,9 +357,9 @@ void tv_list_append_dict(list_T *const list, dict_T *const dict) { listitem_T *const li = tv_list_item_alloc(); - li->li_tv.v_type = VAR_DICT; - li->li_tv.v_lock = VAR_UNLOCKED; - li->li_tv.vval.v_dict = dict; + TV_LIST_ITEM_TV(li)->v_type = VAR_DICT; + TV_LIST_ITEM_TV(li)->v_lock = VAR_UNLOCKED; + TV_LIST_ITEM_TV(li)->vval.v_dict = dict; tv_list_append(list, li); if (dict != NULL) { dict->dv_refcount++; @@ -399,9 +399,9 @@ void tv_list_append_allocated_string(list_T *const l, char *const str) listitem_T *const li = tv_list_item_alloc(); tv_list_append(l, li); - li->li_tv.v_type = VAR_STRING; - li->li_tv.v_lock = VAR_UNLOCKED; - li->li_tv.vval.v_string = (char_u *)str; + TV_LIST_ITEM_TV(li)->v_type = VAR_STRING; + TV_LIST_ITEM_TV(li)->v_lock = VAR_UNLOCKED; + TV_LIST_ITEM_TV(li)->vval.v_string = (char_u *)str; } /// Append number to the list @@ -412,9 +412,9 @@ void tv_list_append_allocated_string(list_T *const l, char *const str) void tv_list_append_number(list_T *const l, const varnumber_T n) { listitem_T *const li = tv_list_item_alloc(); - li->li_tv.v_type = VAR_NUMBER; - li->li_tv.v_lock = VAR_UNLOCKED; - li->li_tv.vval.v_number = n; + TV_LIST_ITEM_TV(li)->v_type = VAR_NUMBER; + TV_LIST_ITEM_TV(li)->v_lock = VAR_UNLOCKED; + TV_LIST_ITEM_TV(li)->vval.v_number = n; tv_list_append(l, li); } @@ -439,33 +439,32 @@ list_T *tv_list_copy(const vimconv_T *const conv, list_T *const orig, } list_T *copy = tv_list_alloc(); + tv_list_ref(copy); if (copyID != 0) { // Do this before adding the items, because one of the items may // refer back to this list. orig->lv_copyID = copyID; orig->lv_copylist = copy; } - listitem_T *item; - for (item = orig->lv_first; item != NULL && !got_int; - item = item->li_next) { + TV_LIST_ITER(orig, item, { listitem_T *const ni = tv_list_item_alloc(); if (deep) { - if (var_item_copy(conv, &item->li_tv, &ni->li_tv, deep, copyID) == FAIL) { + if (var_item_copy(conv, TV_LIST_ITEM_TV(item), TV_LIST_ITEM_TV(ni), + deep, copyID) == FAIL) { xfree(ni); - break; + goto tv_list_copy_error; } } else { - tv_copy(&item->li_tv, &ni->li_tv); + tv_copy(TV_LIST_ITEM_TV(item), TV_LIST_ITEM_TV(ni)); } tv_list_append(copy, ni); - } - copy->lv_refcount++; - if (item != NULL) { - tv_list_unref(copy); - copy = NULL; - } + }); return copy; + +tv_list_copy_error: + tv_list_unref(copy); + return NULL; } /// Extend first list with the second @@ -475,17 +474,17 @@ list_T *tv_list_copy(const vimconv_T *const conv, list_T *const orig, /// @param[in] bef If not NULL, extends before this item. void tv_list_extend(list_T *const l1, list_T *const l2, listitem_T *const bef) - FUNC_ATTR_NONNULL_ARG(1, 2) + FUNC_ATTR_NONNULL_ARG(1) { - int todo = l2->lv_len; + int todo = tv_list_len(l2); listitem_T *const befbef = (bef == NULL ? NULL : bef->li_prev); listitem_T *const saved_next = (befbef == NULL ? NULL : befbef->li_next); // We also quit the loop when we have inserted the original item count of // the list, avoid a hang when we extend a list with itself. - for (listitem_T *item = l2->lv_first - ; item != NULL && --todo >= 0 + for (listitem_T *item = tv_list_first(l2) + ; item != NULL && todo-- ; item = (item == befbef ? saved_next : item->li_next)) { - tv_list_insert_tv(l1, &item->li_tv, bef); + tv_list_insert_tv(l1, TV_LIST_ITEM_TV(item), bef); } } @@ -540,13 +539,12 @@ static int list_join_inner(garray_T *const gap, list_T *const l, { size_t sumlen = 0; bool first = true; - listitem_T *item; // Stringify each item in the list. - for (item = l->lv_first; item != NULL && !got_int; item = item->li_next) { + TV_LIST_ITER(l, item, { char *s; size_t len; - s = encode_tv2echo(&item->li_tv, &len); + s = encode_tv2echo(TV_LIST_ITEM_TV(item), &len); if (s == NULL) { return FAIL; } @@ -557,7 +555,7 @@ static int list_join_inner(garray_T *const gap, list_T *const l, p->tofree = p->s = (char_u *)s; line_breakcheck(); - } + }); // Allocate result buffer with its total size, avoid re-allocation and // multiple copy operations. Add 2 for a tailing ']' and NUL. @@ -591,16 +589,16 @@ static int list_join_inner(garray_T *const gap, list_T *const l, /// /// @return OK in case of success, FAIL otherwise. int tv_list_join(garray_T *const gap, list_T *const l, const char *const sep) - FUNC_ATTR_NONNULL_ALL + FUNC_ATTR_NONNULL_ARG(1) { - if (l->lv_len < 1) { + if (!tv_list_len(l)) { return OK; } garray_T join_ga; int retval; - ga_init(&join_ga, (int)sizeof(Join), l->lv_len); + ga_init(&join_ga, (int)sizeof(Join), tv_list_len(l)); retval = list_join_inner(gap, l, sep, &join_ga); #define FREE_JOIN_TOFREE(join) xfree((join)->tofree) @@ -632,11 +630,13 @@ bool tv_list_equal(list_T *const l1, list_T *const l2, const bool ic, return false; } - listitem_T *item1 = l1->lv_first; - listitem_T *item2 = l2->lv_first; + listitem_T *item1 = tv_list_first(l1); + listitem_T *item2 = tv_list_first(l2); for (; item1 != NULL && item2 != NULL - ; item1 = item1->li_next, item2 = item2->li_next) { - if (!tv_equal(&item1->li_tv, &item2->li_tv, ic, recursive)) { + ; item1 = TV_LIST_ITEM_NEXT(l1, item1), + item2 = TV_LIST_ITEM_NEXT(n2, item2)) { + if (!tv_equal(TV_LIST_ITEM_TV(item1), TV_LIST_ITEM_TV(item2), ic, + recursive)) { return false; } } @@ -662,13 +662,8 @@ listitem_T *tv_list_find(list_T *const l, int n) return NULL; } - // Negative index is relative to the end. - if (n < 0) { - n = l->lv_len + n; - } - - // Check for index out of range. - if (n < 0 || n >= l->lv_len) { + n = tv_list_uidx(l, n); + if (n == -1) { return NULL; } @@ -740,7 +735,7 @@ varnumber_T tv_list_find_nr(list_T *const l, const int n, bool *const ret_error) } return -1; } - return tv_get_number_chk(&li->li_tv, ret_error); + return tv_get_number_chk(TV_LIST_ITEM_TV(li), ret_error); } /// Get list item l[n] as a string @@ -757,7 +752,7 @@ const char *tv_list_find_str(list_T *const l, const int n) emsgf(_(e_listidx), (int64_t)n); return NULL; } - return tv_get_string(&li->li_tv); + return tv_get_string(TV_LIST_ITEM_TV(li)); } /// Locate item in a list and return its index @@ -772,15 +767,14 @@ long tv_list_idx_of_item(const list_T *const l, const listitem_T *const item) if (l == NULL) { return -1; } - long idx = 0; - const listitem_T *li; - for (li = l->lv_first; li != NULL && li != item; li = li->li_next) { + int idx = 0; + TV_LIST_ITER_CONST(l, li, { + if (li == item) { + return idx; + } idx++; - } - if (li == NULL) { - return -1; - } - return idx; + }); + return -1; } //{{{1 Dictionaries @@ -1339,7 +1333,7 @@ int tv_dict_add_list(dict_T *const d, const char *const key, item->di_tv.v_lock = VAR_UNLOCKED; item->di_tv.v_type = VAR_LIST; item->di_tv.vval.v_list = list; - list->lv_refcount++; + tv_list_ref(list); if (tv_dict_add(d, item) == FAIL) { tv_dict_item_free(item); return FAIL; @@ -1677,7 +1671,7 @@ list_T *tv_list_alloc_ret(typval_T *const ret_tv) ret_tv->vval.v_list = l; ret_tv->v_type = VAR_LIST; ret_tv->v_lock = VAR_UNLOCKED; - l->lv_refcount++; + tv_list_ref(l); return l; } @@ -2027,7 +2021,7 @@ void tv_copy(typval_T *const from, typval_T *const to) } case VAR_LIST: { if (from->vval.v_list != NULL) { - to->vval.v_list->lv_refcount++; + tv_list_ref(to->vval.v_list); } break; } @@ -2084,9 +2078,9 @@ void tv_item_lock(typval_T *const tv, const int deep, const bool lock) CHANGE_LOCK(lock, l->lv_lock); if (deep < 0 || deep > 1) { // Recursive: lock/unlock the items the List contains. - for (listitem_T *li = l->lv_first; li != NULL; li = li->li_next) { - tv_item_lock(&li->li_tv, deep - 1, lock); - } + TV_LIST_ITER(l, li, { + tv_item_lock(TV_LIST_ITEM_TV(li), deep - 1, lock); + }); } } break; @@ -2122,6 +2116,8 @@ void tv_item_lock(typval_T *const tv, const int deep, const bool lock) /// Check whether VimL value is locked itself or refers to a locked container /// +/// @warning Fixed container is not the same as locked. +/// /// @param[in] tv Value to check. /// /// @return True if value is locked, false otherwise. @@ -2130,8 +2126,7 @@ bool tv_islocked(const typval_T *const tv) { return ((tv->v_lock == VAR_LOCKED) || (tv->v_type == VAR_LIST - && tv->vval.v_list != NULL - && (tv->vval.v_list->lv_lock == VAR_LOCKED)) + && (tv_list_locked(tv->vval.v_list) == VAR_LOCKED)) || (tv->v_type == VAR_DICT && tv->vval.v_dict != NULL && (tv->vval.v_dict->dv_lock == VAR_LOCKED))); diff --git a/src/nvim/eval/typval.h b/src/nvim/eval/typval.h index b00f43f625..2a0c52b4be 100644 --- a/src/nvim/eval/typval.h +++ b/src/nvim/eval/typval.h @@ -6,6 +6,7 @@ #include #include #include +#include #include "nvim/types.h" #include "nvim/hashtab.h" @@ -284,9 +285,6 @@ typedef struct list_stack_S { #define TV_DICT_HI2DI(hi) \ ((dictitem_T *)((hi)->hi_key - offsetof(dictitem_T, di_key))) -static inline void tv_list_ref(list_T *const l) - REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; - /// Increase reference count for a given list /// /// Does nothing for NULL lists. @@ -332,13 +330,13 @@ static inline void tv_list_set_lock(list_T *const l, l->lv_lock = lock; } -static inline long tv_list_len(const list_T *const l) +static inline int tv_list_len(const list_T *const l) REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; /// Get the number of items in a list /// /// @param[in] l List to check. -static inline long tv_list_len(const list_T *const l) +static inline int tv_list_len(const list_T *const l) { if (l == NULL) { return 0; @@ -346,6 +344,29 @@ static inline long tv_list_len(const list_T *const l) return l->lv_len; } +static inline int tv_list_uidx(const list_T *const l, int n) + REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; + +/// Normalize index: that is, return either -1 or non-negative index +/// +/// @param[in] l List to intex. Used to get length. +/// @param[in] n List index, possibly negative. +/// +/// @return -1 or list index in range [0, tv_list_len(l)). +static inline int tv_list_uidx(const list_T *const l, int n) +{ + // Negative index is relative to the end. + if (n < 0) { + n += tv_list_len(l); + } + + // Check for index out of range. + if (n < 0 || n >= tv_list_len(l)) { + return -1; + } + return n; +} + static inline listitem_T *tv_list_first(const list_T *const l) REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; @@ -427,7 +448,7 @@ extern bool tv_in_free_unref_items; do { \ modifier list_T *const l_ = (l); \ if (l_ != NULL) { \ - for (modifier listitem_T *const li = l_->lv_first; \ + for (modifier listitem_T *li = l_->lv_first; \ li != NULL; li = li->li_next) { \ code \ } \ diff --git a/src/nvim/lua/converter.c b/src/nvim/lua/converter.c index cacba3ce87..18134c56e3 100644 --- a/src/nvim/lua/converter.c +++ b/src/nvim/lua/converter.c @@ -306,7 +306,7 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) case kObjectTypeArray: { cur.tv->v_type = VAR_LIST; cur.tv->vval.v_list = tv_list_alloc(); - cur.tv->vval.v_list->lv_refcount++; + tv_list_ref(cur.tv->vval.v_list); if (table_props.maxidx != 0) { cur.container = true; cur.idx = lua_gettop(lstate); diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 432c9a8b47..9f445204d0 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -2568,7 +2568,7 @@ static void do_autocmd_textyankpost(oparg_T *oap, yankreg_T *reg) for (size_t i = 0; i < reg->y_size; i++) { tv_list_append_string(list, (const char *)reg->y_array[i], -1); } - list->lv_lock = VAR_FIXED; + tv_list_set_lock(list, VAR_FIXED); tv_dict_add_list(dict, S_LEN("regcontents"), list); // the register type diff --git a/test/functional/eval/null_spec.lua b/test/functional/eval/null_spec.lua index b67158eb22..992bc6b39f 100644 --- a/test/functional/eval/null_spec.lua +++ b/test/functional/eval/null_spec.lua @@ -42,10 +42,6 @@ describe('NULL', function() describe('list', function() -- Incorrect behaviour - -- FIXME add() should not return 1 at all - null_expr_test('does not crash add()', 'add(L, 0)', 0, 1) - null_expr_test('does not crash extend()', 'extend(L, [1])', 'E742: Cannot change value of extend() argument', 0) - null_expr_test('does not crash extend() (second position)', 'extend([1], L)', 0, {1}) -- FIXME should be accepted by inputlist() null_expr_test('is accepted as an empty list by inputlist()', '[feedkeys("\\n"), inputlist(L)]', 'E686: Argument of inputlist() must be a List', {0, 0}) @@ -53,18 +49,12 @@ describe('NULL', function() null_expr_test('is accepted as an empty list by writefile()', ('[writefile(L, "%s"), readfile("%s")]'):format(tmpfname, tmpfname), 'E484: Can\'t open file ' .. tmpfname, {0, {}}) - -- FIXME should give error message - null_expr_test('does not crash remove()', 'remove(L, 0)', 0, 0) -- FIXME should return 0 null_expr_test('is accepted by setqflist()', 'setqflist(L)', 0, -1) -- FIXME should return 0 null_expr_test('is accepted by setloclist()', 'setloclist(1, L)', 0, -1) -- FIXME should return 0 null_expr_test('is accepted by setmatches()', 'setmatches(L)', 0, -1) - -- FIXME should return empty list or error out - null_expr_test('is accepted by sort()', 'sort(L)', 0, 0) - -- FIXME Should return 1 - null_expr_test('is accepted by sort()', 'sort(L) is L', 0, 0) -- FIXME should not error out null_test('is accepted by :cexpr', 'cexpr L', 'Vim(cexpr):E777: String or List expected') -- FIXME should not error out @@ -80,13 +70,6 @@ describe('NULL', function() -- FIXME Should return 1 null_expr_test('is not locked', 'islocked("v:_null_list")', 0, 0) - -- Crashes - - -- null_expr_test('does not crash setreg', 'setreg("x", L)', 0, 0) - -- null_expr_test('does not crash setline', 'setline(1, L)', 0, 0) - -- null_expr_test('does not crash system()', 'system("cat", L)', 0, '') - -- null_expr_test('does not crash systemlist()', 'systemlist("cat", L)', 0, {}) - -- Correct behaviour null_expr_test('does not crash append()', 'append(1, L)', 0, 0, function() eq({''}, curbufmeths.get_lines(0, -1, false)) @@ -122,6 +105,22 @@ describe('NULL', function() null_expr_test('counts correctly', 'count([L], L)', 0, 1) null_expr_test('makes map() return v:_null_list', 'map(L, "v:val") is# L', 0, 1) null_expr_test('makes filter() return v:_null_list', 'filter(L, "1") is# L', 0, 1) + null_test('is treated by :let as empty list', ':let [l] = L', 'Vim(let):E688: More targets than List items') + -- FIXME fix test results + null_expr_test('does not crash add()', 'add(L, 0)', 0, 1) + null_expr_test('makes insert() error out', 'insert(L, 1)', '', nil) + null_expr_test('does not crash remove()', 'remove(L, 0)', 0, 0) + null_expr_test('makes reverse() error out', 'reverse(L)', '', nil) + null_expr_test('is accepted by sort()', 'sort(L)', 0, 0) + null_expr_test('makes sort() return itself', 'sort(L) is L', 0, 0) + null_expr_test('does not crash extend()', 'extend(L, [1])', 'E742: Cannot change value of extend() argument', 0) + null_expr_test('does not crash extend() (second position)', 'extend([1], L)', 0, {1}) + null_expr_test('makes join() return empty string', 'join(L, "")', 0, '') + null_expr_test('makes msgpackdump() return empty list', 'msgpackdump(L)', 0, {}) + null_expr_test('does not crash system()', 'system("cat", L)', 0, '') + null_expr_test('does not crash setreg', 'setreg("x", L)', 0, 0) + null_expr_test('does not crash systemlist()', 'systemlist("cat", L)', 0, {}) + null_expr_test('does not crash setline', 'setline(1, L)', 0, 0) end) describe('dict', function() it('does not crash when indexing NULL dict', function() -- cgit From 21745d72b8c24c7f19dea5d53384da4875c43e74 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 10 Dec 2017 03:50:52 +0300 Subject: eval: Fix inputlist() --- src/nvim/eval.c | 2 +- test/functional/eval/null_spec.lua | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/nvim/eval.c b/src/nvim/eval.c index edf016fc41..da953999cc 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -11254,7 +11254,7 @@ static void f_inputlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) int selected; int mouse_used; - if (argvars[0].v_type != VAR_LIST || argvars[0].vval.v_list == NULL) { + if (argvars[0].v_type != VAR_LIST) { EMSG2(_(e_listarg), "inputlist()"); return; } diff --git a/test/functional/eval/null_spec.lua b/test/functional/eval/null_spec.lua index 992bc6b39f..20f9c70a41 100644 --- a/test/functional/eval/null_spec.lua +++ b/test/functional/eval/null_spec.lua @@ -42,9 +42,6 @@ describe('NULL', function() describe('list', function() -- Incorrect behaviour - -- FIXME should be accepted by inputlist() - null_expr_test('is accepted as an empty list by inputlist()', - '[feedkeys("\\n"), inputlist(L)]', 'E686: Argument of inputlist() must be a List', {0, 0}) -- FIXME should be accepted by writefile(), return {0, {}} null_expr_test('is accepted as an empty list by writefile()', ('[writefile(L, "%s"), readfile("%s")]'):format(tmpfname, tmpfname), @@ -106,6 +103,7 @@ describe('NULL', function() null_expr_test('makes map() return v:_null_list', 'map(L, "v:val") is# L', 0, 1) null_expr_test('makes filter() return v:_null_list', 'filter(L, "1") is# L', 0, 1) null_test('is treated by :let as empty list', ':let [l] = L', 'Vim(let):E688: More targets than List items') + null_expr_test('is accepted as an empty list by inputlist()', '[feedkeys("\\n"), inputlist(L)]', 0, 0) -- FIXME fix test results null_expr_test('does not crash add()', 'add(L, 0)', 0, 1) null_expr_test('makes insert() error out', 'insert(L, 1)', '', nil) -- cgit From 5c1ddb5078c90f69c7225a7b2e74ccb914dcdd6a Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 10 Dec 2017 03:54:21 +0300 Subject: eval: Fix writefile() --- src/nvim/eval.c | 4 +--- test/functional/eval/null_spec.lua | 7 +++---- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/nvim/eval.c b/src/nvim/eval.c index da953999cc..2f908f70a9 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -17480,6 +17480,7 @@ static void f_winsaveview(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// @return true in case of success, false otherwise. static bool write_list(FileDescriptor *const fp, const list_T *const list, const bool binary) + FUNC_ATTR_NONNULL_ARG(1) { int error = 0; TV_LIST_ITER_CONST(list, li, { @@ -17645,9 +17646,6 @@ static void f_writefile(typval_T *argvars, typval_T *rettv, FunPtr fptr) EMSG2(_(e_listarg), "writefile()"); return; } - if (argvars[0].vval.v_list == NULL) { - return; - } bool binary = false; bool append = false; diff --git a/test/functional/eval/null_spec.lua b/test/functional/eval/null_spec.lua index 20f9c70a41..7452185208 100644 --- a/test/functional/eval/null_spec.lua +++ b/test/functional/eval/null_spec.lua @@ -42,10 +42,6 @@ describe('NULL', function() describe('list', function() -- Incorrect behaviour - -- FIXME should be accepted by writefile(), return {0, {}} - null_expr_test('is accepted as an empty list by writefile()', - ('[writefile(L, "%s"), readfile("%s")]'):format(tmpfname, tmpfname), - 'E484: Can\'t open file ' .. tmpfname, {0, {}}) -- FIXME should return 0 null_expr_test('is accepted by setqflist()', 'setqflist(L)', 0, -1) -- FIXME should return 0 @@ -104,6 +100,9 @@ describe('NULL', function() null_expr_test('makes filter() return v:_null_list', 'filter(L, "1") is# L', 0, 1) null_test('is treated by :let as empty list', ':let [l] = L', 'Vim(let):E688: More targets than List items') null_expr_test('is accepted as an empty list by inputlist()', '[feedkeys("\\n"), inputlist(L)]', 0, 0) + null_expr_test('is accepted as an empty list by writefile()', + ('[writefile(L, "%s"), readfile("%s")]'):format(tmpfname, tmpfname), + 0, {0, {}}) -- FIXME fix test results null_expr_test('does not crash add()', 'add(L, 0)', 0, 1) null_expr_test('makes insert() error out', 'insert(L, 1)', '', nil) -- cgit From 4a5bc6275d09056ff0ccf5a29a878d48bbe58655 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Fri, 8 Dec 2017 10:42:30 -0500 Subject: ci: run oldtests in Appveyor #7705 --- ci/build.bat | 6 ++++++ src/nvim/testdir/Makefile | 20 +++++++++++++++----- src/nvim/testdir/unix.vim | 6 ++++++ 3 files changed, 27 insertions(+), 5 deletions(-) diff --git a/ci/build.bat b/ci/build.bat index 91eca9ef73..25f949b5e4 100644 --- a/ci/build.bat +++ b/ci/build.bat @@ -53,6 +53,12 @@ bin\nvim --version || goto :error :: Functional tests mingw32-make functionaltest VERBOSE=1 || goto :error +:: Old tests +setlocal +set PATH=%PATH%;C:\msys64\usr\bin +mingw32-make -C "%~dp0\..\src\nvim\testdir" VERBOSE=1 +endlocal + if defined USE_GCOV ( C:\msys64\usr\bin\bash -lc "cd /c/projects/neovim; bash <(curl -s https://codecov.io/bash) || echo 'codecov upload failed.'" ) diff --git a/src/nvim/testdir/Makefile b/src/nvim/testdir/Makefile index 111bd172ef..e1faaccb84 100644 --- a/src/nvim/testdir/Makefile +++ b/src/nvim/testdir/Makefile @@ -2,7 +2,11 @@ # Makefile to run all tests for Vim # -NVIM_PRG ?= ../../../build/bin/nvim +ifeq ($(OS),Windows_NT) + NVIM_PRG ?= ../../../build/bin/nvim.exe +else + NVIM_PRG ?= ../../../build/bin/nvim +endif TMPDIR ?= Xtest-tmpdir SCRIPTSOURCE := ../../../runtime @@ -10,12 +14,9 @@ export SHELL := sh export NVIM_PRG := $(NVIM_PRG) export TMPDIR -SCRIPTS ?= \ - test13.out \ +SCRIPTS_DEFAULT = \ test14.out \ - test17.out \ test24.out \ - test32.out \ test37.out \ test40.out \ test42.out \ @@ -27,6 +28,15 @@ SCRIPTS ?= \ test73.out \ test79.out \ +ifneq ($(OS),Windows_NT) + SCRIPTS_DEFAULTS := $(SCRIPTS_DEFAULT) \ + test17.out \ + test32.out \ + +endif + +SCRIPTS ?= $(SCRIPTS_DEFAULT) + # Tests using runtest.vim. # Keep test_alot*.res as the last one, sort the others. NEW_TESTS ?= \ diff --git a/src/nvim/testdir/unix.vim b/src/nvim/testdir/unix.vim index a7daacf8cf..ce2beff7fe 100644 --- a/src/nvim/testdir/unix.vim +++ b/src/nvim/testdir/unix.vim @@ -2,6 +2,12 @@ " Always use "sh", don't use the value of "$SHELL". set shell=sh +if has('win32') + set shellcmdflag=-c shellxquote= shellxescape= shellquote= + let &shellredir = '>%s 2>&1' + set shellslash +endif + " Don't depend on system locale, always use utf-8 set encoding=utf-8 -- cgit From 1f2b35860f755513a58c1d010f082d946c889b47 Mon Sep 17 00:00:00 2001 From: James McCoy Date: Thu, 7 Dec 2017 20:53:02 -0500 Subject: mac: Set $LANG based on the system locale Unix's typical locale-related environment variables aren't always set appropriately on a Mac. Instead of relying on them, query the locale information using Mac specific APIs and then set $LANG appropriately for the rest of nvim. Closes #5873 --- src/nvim/CMakeLists.txt | 3 +++ src/nvim/option.c | 3 +++ src/nvim/os/lang.c | 40 ++++++++++++++++++++++++++++++++++++++++ src/nvim/os/lang.h | 7 +++++++ 4 files changed, 53 insertions(+) create mode 100644 src/nvim/os/lang.c create mode 100644 src/nvim/os/lang.h diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index d3e07326bf..7eb1afa135 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -13,6 +13,9 @@ endif() if(WIN32) # tell MinGW compiler to enable wmain set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -municode") +elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -framework CoreFoundation") + set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -framework CoreFoundation") endif() set(TOUCHES_DIR ${PROJECT_BINARY_DIR}/touches) diff --git a/src/nvim/option.c b/src/nvim/option.c index 37c4233142..f8a05f133d 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -75,6 +75,7 @@ #include "nvim/window.h" #include "nvim/os/os.h" #include "nvim/os/input.h" +#include "nvim/os/lang.h" /* * The options that are local to a window or buffer have "indir" set to one of @@ -784,6 +785,8 @@ void set_init_1(void) didset_options2(); + lang_init(); + // enc_locale() will try to find the encoding of the current locale. // This will be used when 'default' is used as encoding specifier // in 'fileencodings' diff --git a/src/nvim/os/lang.c b/src/nvim/os/lang.c new file mode 100644 index 0000000000..f0bbf4b1cb --- /dev/null +++ b/src/nvim/os/lang.c @@ -0,0 +1,40 @@ +#ifdef __APPLE__ +# define Boolean CFBoolean // Avoid conflict with API's Boolean +# include +# include +# undef Boolean +#endif + +#ifdef HAVE_LOCALE_H +# include +#endif +#include "nvim/os/os.h" + +void lang_init(void) +{ +#ifdef __APPLE__ + if (os_getenv("LANG") == NULL) { + CFLocaleRef cf_locale = CFLocaleCopyCurrent(); + CFTypeRef cf_lang_region = CFLocaleGetValue(cf_locale, + kCFLocaleIdentifier); + CFRetain(cf_lang_region); + CFRelease(cf_locale); + + const char *lang_region = CFStringGetCStringPtr(cf_lang_region, + kCFStringEncodingUTF8); + if (lang_region) { + os_setenv("LANG", lang_region, true); + } else { + char buf[20] = { 0 }; + if (CFStringGetCString(cf_lang_region, buf, 20, + kCFStringEncodingUTF8)) { + os_setenv("LANG", lang_region, true); + } + } + CFRelease(cf_lang_region); +# ifdef HAVE_LOCALE_H + setlocale(LC_ALL, ""); +# endif + } +#endif +} diff --git a/src/nvim/os/lang.h b/src/nvim/os/lang.h new file mode 100644 index 0000000000..f60e064f57 --- /dev/null +++ b/src/nvim/os/lang.h @@ -0,0 +1,7 @@ +#ifndef NVIM_OS_LANG_H +#define NVIM_OS_LANG_H + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "os/lang.h.generated.h" +#endif +#endif // NVIM_OS_LANG_H -- cgit From abe38f7d26d68d7032ea391c039c56c8b87675a5 Mon Sep 17 00:00:00 2001 From: glacambre Date: Sun, 26 Nov 2017 13:29:32 +0100 Subject: window.c: do BufEnter in correct window after closing help #7431 closes #7429 Problem: after a help window was closed, a window was selected and its autocommands triggered. After that, restore_snapshot was called and the focused window changed, confusing the user. Solution: Add function get_snapshot_focus() that returns the window that holds the cursor in a snapshot. Use this function in win_close to make sure the right window is selected before any autocommand is triggered. --- src/nvim/window.c | 29 +++++++++++++++++++++++++++++ test/functional/autocmd/bufenter_spec.lua | 13 +++++++++++++ 2 files changed, 42 insertions(+) diff --git a/src/nvim/window.c b/src/nvim/window.c index 4e4eb297aa..e39569321e 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -1991,6 +1991,14 @@ int win_close(win_T *win, int free_buf) * the screen space. */ wp = win_free_mem(win, &dir, NULL); + if (help_window) { + // Closing the help window moves the cursor back to the original window. + win_T *tmpwp = get_snapshot_focus(SNAP_HELP_IDX); + if (tmpwp != NULL) { + wp = tmpwp; + } + } + /* Make sure curwin isn't invalid. It can cause severe trouble when * printing an error message. For win_equal() curbuf needs to be valid * too. */ @@ -5421,6 +5429,27 @@ static win_T *restore_snapshot_rec(frame_T *sn, frame_T *fr) return wp; } +/// Gets the focused window (the one holding the cursor) of the snapshot. +static win_T *get_snapshot_focus(int idx) +{ + if (curtab->tp_snapshot[idx] == NULL) { + return NULL; + } + + frame_T *sn = curtab->tp_snapshot[idx]; + // This should be equivalent to the recursive algorithm found in + // restore_snapshot as far as traveling nodes go. + while (sn->fr_child != NULL || sn->fr_next != NULL) { + while (sn->fr_child != NULL) { + sn = sn->fr_child; + } + if (sn->fr_next != NULL) { + sn = sn->fr_next; + } + } + + return sn->fr_win; +} /* * Set "win" to be the curwin and "tp" to be the current tab page. diff --git a/test/functional/autocmd/bufenter_spec.lua b/test/functional/autocmd/bufenter_spec.lua index fef9838050..e14ddb3316 100644 --- a/test/functional/autocmd/bufenter_spec.lua +++ b/test/functional/autocmd/bufenter_spec.lua @@ -31,4 +31,17 @@ describe('autocmd BufEnter', function() eq(1, eval("exists('g:dir_bufenter')")) -- Did BufEnter for the directory. eq(2, eval("bufnr('%')")) -- Switched to the dir buffer. end) + + it('triggered by ":split normal|:help|:bw"', function() + command("split normal") + command("wincmd j") + command("helptags runtime/doc") + command("help") + command("wincmd L") + command("autocmd BufEnter normal let g:bufentered = 1") + command("bw") + eq(1, eval('bufnr("%")')) -- The cursor is back to the bottom window + eq(0, eval("exists('g:bufentered')")) -- The autocmd hasn't been triggered + end) + end) -- cgit From dc232b74fb1a7927d6c13bd8c80bb42fc18a859f Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sun, 10 Dec 2017 01:24:54 +0100 Subject: doc: hack to avoid doxygen bug Use `@cond ` to obscure a section from doxygen. doxygen thinks kvec_withinit_t() is a function. That adds noise to the generated API documentation, and also prevents the following function from being noticed. --- runtime/doc/api.txt | 6 +++++- src/nvim/api/vim.c | 6 ++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt index ef8b9c7d47..ecf829d356 100644 --- a/runtime/doc/api.txt +++ b/runtime/doc/api.txt @@ -446,7 +446,11 @@ nvim_get_keymap({mode}) *nvim_get_keymap()* Array of maparg()-like dictionaries describing mappings nvim_get_api_info() *nvim_get_api_info()* - TODO: Documentation + Returns a 2-tuple (Array), where item 0 is the current channel + id and item 1 is the |api-metadata| map (Dictionary). + + Return:~ + 2-tuple [{channel-id}, {api-metadata}] Attributes:~ {async} diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 416e7d22d2..f0db391abe 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -789,6 +789,10 @@ ArrayOf(Dictionary) nvim_get_keymap(String mode) return keymap_array(mode, NULL); } +/// Returns a 2-tuple (Array), where item 0 is the current channel id and item +/// 1 is the |api-metadata| map (Dictionary). +/// +/// @returns 2-tuple [{channel-id}, {api-metadata}] Array nvim_get_api_info(uint64_t channel_id) FUNC_API_SINCE(1) FUNC_API_ASYNC FUNC_API_REMOTE_ONLY { @@ -896,7 +900,9 @@ typedef struct { Object *ret_node_p; } ExprASTConvStackItem; +///@cond DOXYGEN_NOT_A_FUNCTION typedef kvec_withinit_t(ExprASTConvStackItem, 16) ExprASTConvStack; +///@endcond /// Parse a VimL expression /// -- cgit From 9ada97a81027e03d4988cc61b366ff05e2e5b92f Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sun, 10 Dec 2017 01:30:05 +0100 Subject: gen_api_vimdoc.py: require "nvim_" prefix Avoids doxygen bugs (things that aren't functions) and other noise (e.g. `remote_ui_disconnect()` was incorrectly included in api.txt). --- scripts/gen_api_vimdoc.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/scripts/gen_api_vimdoc.py b/scripts/gen_api_vimdoc.py index 51e585a007..4ddf415f1a 100644 --- a/scripts/gen_api_vimdoc.py +++ b/scripts/gen_api_vimdoc.py @@ -45,6 +45,8 @@ if sys.version_info[0] < 3: doc_filename = 'api.txt' # String used to find the start of the generated part of the doc. section_start_token = '*api-global*' +# Required prefix for API function names. +api_func_name_prefix = 'nvim_' # Section name overrides. section_name = { @@ -260,11 +262,11 @@ def parse_parblock(parent, width=62): def parse_source_xml(filename): """Collects API functions. - This returns two strings: - 1. The API functions - 2. The deprecated API functions + Returns two strings: + 1. API functions + 2. Deprecated API functions - The caller decides what to do with the deprecated documentation. + Caller decides what to do with the deprecated documentation. """ global xrefs xrefs = set() @@ -294,9 +296,8 @@ def parse_source_xml(filename): annotations = get_text(get_child(member, 'argsstring')) if annotations and ')' in annotations: annotations = annotations.rsplit(')', 1)[-1].strip() - # XXX: (doxygen 1.8.11) 'argsstring' only includes FUNC_ATTR_* - # attributes if the function signature is non-void. - # Force attributes here for such functions. + # XXX: (doxygen 1.8.11) 'argsstring' only includes attributes of + # non-void functions. Special-case void functions here. if name == 'nvim_get_mode' and len(annotations) == 0: annotations += 'FUNC_API_ASYNC' annotations = filter(None, map(lambda x: annotation_map.get(x), @@ -379,7 +380,7 @@ def parse_source_xml(filename): if 'Deprecated' in xrefs: deprecated_functions.append(func_doc) - else: + elif name.startswith(api_func_name_prefix): functions.append(func_doc) xrefs.clear() -- cgit From ad9c2d3cb97bbc4ba83ce7d23c9df23f9d3d11db Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Tue, 14 Nov 2017 10:47:49 +0100 Subject: doc closes #7622 --- CONTRIBUTING.md | 8 ++--- README.md | 2 +- runtime/doc/api.txt | 91 +++++++++++++++++++++++++++++++++++++++++++----- runtime/doc/autocmd.txt | 27 ++++++-------- runtime/doc/syntax.txt | 12 +------ runtime/doc/ui.txt | 2 +- runtime/doc/various.txt | 19 +++++----- runtime/doc/vim_diff.txt | 3 +- src/nvim/api/buffer.c | 4 +-- src/nvim/tui/tui.c | 2 +- test/README.md | 13 +++---- 11 files changed, 118 insertions(+), 65 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 20bce8ca62..05a97ebf18 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -14,7 +14,7 @@ low-risk/isolated tasks: Developer guidelines -------------------- -- Nvim developers should read `:help dev-help`. +- Nvim developers should read `:help dev`. - External UI developers should read `:help dev-ui`. Reporting problems @@ -24,7 +24,7 @@ Reporting problems - Search [existing issues][github-issues] (including closed!) - Update Neovim to the latest version to see if your problem persists. - Disable plugins incrementally, to narrow down the cause of the issue. -- When reporting a crash, include a stacktrace. +- When reporting a crash, [include a stacktrace](https://github.com/neovim/neovim/wiki/Development-tips#backtrace-linux). - [Bisect][git-bisect] to the cause of a regression, if you are able. This is _extremely_ helpful. - Check `$NVIM_LOG_FILE`, if it exists. - Include `cmake --system-information` for **build** issues. @@ -92,7 +92,7 @@ and [AppVeyor]. - CI builds are compiled with [`-Werror`][gcc-warnings], so compiler warnings will fail the build. - If any tests fail, the build will fail. - See [Building Neovim#running-tests][wiki-run-tests] to run tests locally. + See [test/README.md#running-tests][run-tests] to run tests locally. Passing locally doesn't guarantee passing the CI build, because of the different compilers and platforms tested against. - CI runs [ASan] and other analyzers. @@ -168,7 +168,7 @@ as context, use the `-W` argument as well. [hygiene]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html [style-guide]: http://neovim.io/develop/style-guide.xml [ASan]: http://clang.llvm.org/docs/AddressSanitizer.html -[wiki-run-tests]: https://github.com/neovim/neovim/wiki/Building-Neovim#running-tests +[run-tests]: https://github.com/neovim/neovim/blob/master/test/README.md#running-tests [wiki-faq]: https://github.com/neovim/neovim/wiki/FAQ [review-checklist]: https://github.com/neovim/neovim/wiki/Code-review-checklist [3174]: https://github.com/neovim/neovim/issues/3174 diff --git a/README.md b/README.md index 454137219f..2e1fc90c0c 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ [![PVS-studio Check](https://neovim.io/doc/reports/pvs/badge.svg)](https://neovim.io/doc/reports/pvs) [![Debian CI](https://badges.debian.net/badges/debian/testing/neovim/version.svg)](https://buildd.debian.org/neovim) -[![Downloads](https://img.shields.io/github/downloads/neovim/neovim/total.svg?maxAge=2592000)](https://github.com/neovim/neovim/releases/) +[![Downloads](https://img.shields.io/github/downloads/neovim/neovim/total.svg?maxAge=2592001)](https://github.com/neovim/neovim/releases/) Neovim is a project that seeks to aggressively refactor Vim in order to: diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt index ecf829d356..6c2a3a8632 100644 --- a/runtime/doc/api.txt +++ b/runtime/doc/api.txt @@ -60,8 +60,7 @@ External programs ("clients") can use the metadata to discover the |rpc-api|. API contract *api-contract* The API is made of functions and events. Clients call functions like those -described at |api-global|, and may "attach" in order to receive rich events, -described at |rpc-remote-ui|. +described at |api-global|, and may "attach" to receive rich |ui-events|. As Nvim develops, its API may change only according the following "contract": @@ -481,6 +480,84 @@ nvim_call_atomic({calls}) *nvim_call_atomic()* error ocurred, the values from all preceding calls will still be returned. + *nvim_parse_expression()* +nvim_parse_expression({expr}, {flags}, {highlight}) + Parse a VimL expression + + Attributes:~ + {async} + + Parameters:~ + {expr} Expression to parse. Is always treated as a + single line. + {flags} Flags: - "m" if multiple expressions in a + row are allowed (only the first one will be + parsed), - "E" if EOC tokens are not allowed + (determines whether they will stop parsing + process or be recognized as an + operator/space, though also yielding an + error). - "l" when needing to start parsing + with lvalues for ":let" or ":for". Common + flag sets: - "m" to parse like for ":echo". - + "E" to parse like for "=". - empty + string for ":call". - "lm" to parse for + ":let". + {highlight} If true, return value will also include + "highlight" key containing array of 4-tuples + (arrays) (Integer, Integer, Integer, String), + where first three numbers define the + highlighted region and represent line, + starting column and ending column (latter + exclusive: one should highlight region + [start_col, end_col)). + + Return:~ + AST: top-level dictionary holds keys "error": Dictionary + with error, present only if parser saw some error. + Contains the following keys: "message": String, error + message in printf format, translated. Must contain exactly + one "%.*s". "arg": String, error message argument. "len": + Amount of bytes successfully parsed. With flags equal to + "" that should be equal to the length of expr string. + @note: “Sucessfully parsed” here means “participated in + AST creation”, not “till the first error”. "ast": AST, + either nil or a dictionary with these keys: "type": node + type, one of the value names from ExprASTNodeType + stringified without "kExprNode" prefix. "start": a pair + [line, column] describing where node is “started” where + "line" is always 0 (will not be 0 if you will be using + nvim_parse_viml() on e.g. ":let", but that is not present + yet). Both elements are Integers. "len": “length” of the + node. This and "start" are there for debugging purposes + primary (debugging parser and providing debug + information). "children": a list of nodes described in + top/"ast". There always is zero, one or two children, key + will not be present if node has no children. Maximum + number of children may be found in node_maxchildren array. + Local values (present only for certain nodes): "scope": a + single Integer, specifies scope for "Option" and + "PlainIdentifier" nodes. For "Option" it is one of + ExprOptScope values, for "PlainIdentifier" it is one of + ExprVarScope values. "ident": identifier (without scope, + if any), present for "Option", "PlainIdentifier", + "PlainKey" and "Environment" nodes. "name": Integer, + register name (one character) or -1. Only present for + "Register" nodes. "cmp_type": String, comparison type, one + of the value names from ExprComparisonType, stringified + without "kExprCmp" prefix. Only present for "Comparison" + nodes. "ccs_strategy": String, case comparison strategy, + one of the value names from ExprCaseCompareStrategy, + stringified without "kCCStrategy" prefix. Only present for + "Comparison" nodes. "augmentation": String, augmentation + type for "Assignment" nodes. Is either an empty string, + "Add", "Subtract" or "Concat" for "=", "+=", "-=" or ".=" + respectively. "invert": Boolean, true if result of + comparison needs to be inverted. Only present for + "Comparison" nodes. "ivalue": Integer, integer value for + "Integer" nodes. "fvalue": Float, floating-point value for + "Float" nodes. "svalue": String, value for + "SingleQuotedString" and "DoubleQuotedString" nodes. + nvim__id({obj}) *nvim__id()* Returns object given as argument @@ -721,9 +798,10 @@ nvim_buf_add_highlight({buffer}, {src_id}, {hl_group}, {line}, or -1 for ungrouped highlight {hl_group} Name of the highlight group to use {line} Line to highlight (zero-indexed) - {col_start} Start of range of columns to highlight - {col_end} End of range of columns to highlight, or -1 - to highlight to end of line + {col_start} Start of (byte-indexed) column range to + highlight + {col_end} End of (byte-indexed) column range to + highlight, or -1 to highlight to end of line Return:~ The src_id that was used @@ -957,9 +1035,6 @@ nvim_tabpage_is_valid({tabpage}) *nvim_tabpage_is_valid()* ============================================================================== UI Functions *api-ui* -remote_ui_disconnect() *remote_ui_disconnect()* - TODO: Documentation - nvim_ui_attach({width}, {height}, {options}) *nvim_ui_attach()* TODO: Documentation diff --git a/runtime/doc/autocmd.txt b/runtime/doc/autocmd.txt index 740f44414a..9a04bf2824 100644 --- a/runtime/doc/autocmd.txt +++ b/runtime/doc/autocmd.txt @@ -259,13 +259,12 @@ Name triggered by ~ |BufNew| just after creating a new buffer |SwapExists| detected an existing swap file -|TermOpen| when a terminal buffer is starting -|TermClose| when a terminal buffer ends +|TermOpen| when a terminal job starts +|TermClose| when a terminal job ends Options |FileType| when the 'filetype' option has been set |Syntax| when the 'syntax' option has been set -|TermChanged| after the value of 'term' has changed |OptionSet| after setting any option Startup and exit @@ -933,26 +932,20 @@ TabEnter Just after entering a tab page. |tab-page| TabLeave Just before leaving a tab page. |tab-page| A WinLeave event will have been triggered first. - {Nvim} *TabNew* + *TabNew* TabNew When creating a new tab page. |tab-page| After WinEnter and before TabEnter. - {Nvim} *TabNewEntered* + *TabNewEntered* TabNewEntered After entering a new tab page. |tab-page| After BufEnter. - {Nvim} *TabClosed* + *TabClosed* TabClosed After closing a tab page. can be used for the tab page number. - *TermChanged* -TermChanged After the value of 'term' has changed. Useful - for re-loading the syntax file to update the - colors, fonts and other terminal-dependent - settings. Executed for all loaded buffers. - {Nvim} *TermClose* -TermClose When a terminal buffer ends. - {Nvim} *TermOpen* -TermOpen When a terminal buffer is starting. This can - be used to configure the terminal emulator by - setting buffer variables. |terminal| + *TermClose* +TermClose When a |terminal| job ends. + *TermOpen* +TermOpen When a |terminal| job is starting. Can be + used to configure the terminal buffer. *TermResponse* TermResponse After the response to |t_RV| is received from the terminal. The value of |v:termresponse| diff --git a/runtime/doc/syntax.txt b/runtime/doc/syntax.txt index 85330f3dec..ff9773b136 100644 --- a/runtime/doc/syntax.txt +++ b/runtime/doc/syntax.txt @@ -4709,17 +4709,7 @@ ctermbg={color-nr} *highlight-ctermbg* "cterm". For example, on some systems "cterm=bold ctermfg=3" gives another color, on others you just get color 3. - For an xterm this depends on your resources, and is a bit - unpredictable. See your xterm documentation for the defaults. The - colors for a color-xterm can be changed from the .Xdefaults file. - Unfortunately this means that it's not possible to get the same colors - for each user. - - The MSDOS standard colors are fixed (in a console window), so these - have been used for the names. But the meaning of color names in X11 - are fixed, so these color settings have been used, to make the - highlighting settings portable (complicated, isn't it?). The - following names are recognized, with the color number used: + The following names are recognized, with the color number used: *cterm-colors* NR-16 NR-8 COLOR NAME ~ diff --git a/runtime/doc/ui.txt b/runtime/doc/ui.txt index 685019aed7..abbd063483 100644 --- a/runtime/doc/ui.txt +++ b/runtime/doc/ui.txt @@ -9,7 +9,7 @@ Nvim UI protocol *ui* Type |gO| to see the table of contents. ============================================================================== -Introduction *ui-intro* +UI Events *ui-events* GUIs can be implemented as external processes communicating with Nvim over the RPC API. The UI model consists of a terminal-like grid with a single, diff --git a/runtime/doc/various.txt b/runtime/doc/various.txt index baac72c106..9232cd70c5 100644 --- a/runtime/doc/various.txt +++ b/runtime/doc/various.txt @@ -207,21 +207,18 @@ g8 Print the hex values of the bytes used in the :sh[ell] Removed. |vim-differences| {Nvim} *:terminal* *:te* -:te[rminal][!] [{cmd}] Execute {cmd} with 'shell' in a new |terminal| buffer. - Equivalent to: > - :enew - :call termopen('{cmd}') -< - See |termopen()|. +:te[rminal][!] [{cmd}] Execute {cmd} with 'shell' in a new |terminal-emulator| + buffer. Without {cmd}, start an interactive 'shell'. - Without {cmd}, start an interactive shell. + Type |i| to enter |Terminal-mode|, then keys are sent to + the job running in the terminal. Type to + leave Terminal-mode. |CTRL-\_CTRL-N| - Creating the terminal buffer fails when changes have been - made to the current buffer, unless 'hidden' is set. + Fails if changes have been made to the current buffer, + unless 'hidden' is set. To enter |Terminal-mode| automatically: > - autocmd BufEnter term://* startinsert - autocmd BufLeave term://* stopinsert + autocmd TermOpen * startinsert < *:!cmd* *:!* *E34* :!{cmd} Execute {cmd} with 'shell'. See also |:terminal|. diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index 1a4a66ed89..7061f01316 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -302,7 +302,8 @@ Highlight groups: |hl-ColorColumn|, |hl-CursorColumn| are lower priority than most other groups -The variable name "count" is no fallback for |v:count| anymore. +VimL (Vim script) compatibility: + `count` does not alias to |v:count| ============================================================================== 5. Missing legacy features *nvim-features-missing* diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index 4b6a88e5fa..fdde28f2bb 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -763,8 +763,8 @@ ArrayOf(Integer, 2) nvim_buf_get_mark(Buffer buffer, String name, Error *err) /// or -1 for ungrouped highlight /// @param hl_group Name of the highlight group to use /// @param line Line to highlight (zero-indexed) -/// @param col_start Start of range of columns to highlight -/// @param col_end End of range of columns to highlight, +/// @param col_start Start of (byte-indexed) column range to highlight +/// @param col_end End of (byte-indexed) column range to highlight, /// or -1 to highlight to end of line /// @param[out] err Error details, if any /// @return The src_id that was used diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index 6e2a5cbe67..9ff1acf64a 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -1529,7 +1529,7 @@ static void patch_terminfo_bugs(TUIData *data, const char *term, || iterm || iterm_pretending_xterm || teraterm // per TeraTerm "Supported Control Functions" doco // Some linux-type terminals (such as console-terminal-emulator - // from the nosh toolset) implement implement the xterm extension. + // from the nosh toolset) implement the xterm extension. || (linuxvt && (xterm_version || (vte_version > 0) || colorterm)))) { data->unibi_ext.set_cursor_style = (int)unibi_add_ext_str(ut, "Ss", "\x1b[%p1%d q"); diff --git a/test/README.md b/test/README.md index 1cb814d85b..58aa6a06aa 100644 --- a/test/README.md +++ b/test/README.md @@ -168,16 +168,13 @@ minutes](http://learnxinyminutes.com/docs/lua/). Do not silently skip the test with `if-else`. If a functional test depends on some external factor (e.g. the existence of `md5sum` on `$PATH`), *and* you can't mock or fake the dependency, then skip the test via `pending()` if the - external factor is missing. This ensures that the *total* test-count (success - + fail + error + pending) is the same in all environments. + external factor is missing. This ensures that the *total* test-count + (success + fail + error + pending) is the same in all environments. - *Note:* `pending()` is ignored if it is missing an argument _unless_ it is - [contained in an `it()` - block](https://github.com/neovim/neovim/blob/d21690a66e7eb5ebef18046c7a79ef898966d786/test/functional/ex_cmds/grep_spec.lua#L11). - Provide empty function argument if the `pending()` call is outside of - `it()` + [contained in an `it()` block](https://github.com/neovim/neovim/blob/d21690a66e7eb5ebef18046c7a79ef898966d786/test/functional/ex_cmds/grep_spec.lua#L11). + Provide empty function argument if the `pending()` call is outside of `it()` ([example](https://github.com/neovim/neovim/commit/5c1dc0fbe7388528875aff9d7b5055ad718014de#diff-bf80b24c724b0004e8418102f68b0679R18)). -- Use `make testlint` for using the shipped luacheck program ([supported by - syntastic](https://github.com/scrooloose/syntastic/blob/d6b96c079be137c83009827b543a83aa113cc011/doc/syntastic-checkers.txt#L3546)) +- Use `make testlint` for using the shipped luacheck program ([supported by syntastic](https://github.com/scrooloose/syntastic/blob/d6b96c079be137c83009827b543a83aa113cc011/doc/syntastic-checkers.txt#L3546)) to lint all tests. ### Where tests go -- cgit From 5bd8827431b44b023ae858d903eb543f22cafbdd Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Mon, 4 Dec 2017 09:08:07 +0100 Subject: vim-patch.sh: always use git log, not version.c --- scripts/vim-patch.sh | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/scripts/vim-patch.sh b/scripts/vim-patch.sh index 2875e7d95a..5c24ede13f 100755 --- a/scripts/vim-patch.sh +++ b/scripts/vim-patch.sh @@ -327,6 +327,11 @@ list_vim_patches() { local vim_commits vim_commits="$(cd "${VIM_SOURCE_DIR}" && git log --reverse --format='%H' v8.0.0000..HEAD)" + # Find all "vim-patch:xxx" tokens in the Nvim git log. + local tokens + tokens="$(cd "${NVIM_SOURCE_DIR}" && git log -E --grep='vim-patch:[^ ]+' | grep 'vim-patch')" + tokens="$(for i in $tokens ; do echo "$i" | grep -E 'vim-patch:[^ ]{7}' | sed 's/.*\(vim-patch:[.0-9a-z]\+\).*/\1/' ; done)" + local vim_commit for vim_commit in ${vim_commits}; do local is_missing @@ -334,22 +339,21 @@ list_vim_patches() { # This fails for untagged commits (e.g., runtime file updates) so mask the return status vim_tag="$(cd "${VIM_SOURCE_DIR}" && git describe --tags --exact-match "${vim_commit}" 2>/dev/null)" || true if [[ -n "${vim_tag}" ]]; then - local patch_number="${vim_tag:5}" # Remove prefix like "v7.4." - patch_number="$(echo ${patch_number} | sed 's/^0*//g')" # Remove prefix "0" - # Tagged Vim patch, check version.c: - is_missing="$(sed -n '/static const int included_patches/,/}/p' "${NVIM_SOURCE_DIR}/src/nvim/version.c" | - grep -x -e "[[:space:]]*//[[:space:]]${patch_number} NA.*" -e "[[:space:]]*${patch_number}," >/dev/null && echo "false" || echo "true")" + # Vim version number (not commit hash). + local patch_number="${vim_tag:1}" # "v7.4.0001" => "7.4.0001" + is_missing="$(echo "$tokens" | >/dev/null 2>&1 grep "vim\-patch:${patch_number}" && echo false || echo true)" vim_commit="${vim_tag#v}" - if (cd "${VIM_SOURCE_DIR}" && git --no-pager show --color=never --name-only "v${vim_commit}" 2>/dev/null) | grep -q ^runtime; then - vim_commit="${vim_commit} (+runtime)" + if ! [ "$is_missing" = "false" ] ; then + if (cd "${VIM_SOURCE_DIR}" && git --no-pager show --color=never --name-only "v${vim_commit}" 2>/dev/null) | grep -q ^runtime; then + vim_commit="${vim_commit} (+runtime)" + fi fi else - # Untagged Vim patch (e.g. runtime updates), check the Neovim git log: - is_missing="$(cd "${NVIM_SOURCE_DIR}" && - git log -1 --no-merges --grep="vim\-patch:${vim_commit:0:7}" --pretty=format:false)" + # Untagged Vim patch (e.g. runtime updates). + is_missing="$(echo "$tokens" | >/dev/null 2>&1 grep "vim\-patch:${vim_commit:0:7}" && echo false || echo true)" fi - if [[ ${is_missing} != "false" ]]; then + if ! [ "$is_missing" = "false" ]; then echo " • ${vim_commit}" fi done -- cgit From e9504b7fd8cbf781aa9e68c48a4f3d59c633f269 Mon Sep 17 00:00:00 2001 From: ckelsel Date: Fri, 10 Nov 2017 22:51:03 +0800 Subject: vim-patch: NA vim-patch:8.0.0299 NA vim-patch:8.0.0309 NA vim-patch:8.0.0310 NA vim-patch:8.0.0215 NA Problem: When a Cscope line contains CTRL-L a NULL pointer may be used. (Coverity) Solution: Don't check for an emacs tag in a cscope line. https://github.com/vim/vim/commit/e362c3d2c34f2b7ff38b4c3d2a7ff127d2290e09 vim-patch:8.0.0244 NA Problem: When the user sets t_BE empty after startup to disable bracketed paste, this has no direct effect. Solution: When t_BE is made empty write t_BD. When t_BE is made non-empty write the new value. https://github.com/vim/vim/commit/d9c60648e50a82dcb85b8dffb47f6416c3d56972 Some patches were not properly marked in the commit logs. So they are marked here: vim-patch:8.0.1230 vim-patch:8.0.1229 vim-patch:8.0.0618 vim-patch:8.0.0104 vim-patch:8.0.0405 vim-patch:8.0.0400 vim-patch:8.0.0302 vim-patch:8.0.0288 vim-patch:8.0.0285 vim-patch:8.0.0284 vim-patch:8.0.0281 vim-patch:8.0.0279 vim-patch:8.0.0278 vim-patch:8.0.0277 vim-patch:8.0.0276 vim-patch:8.0.0273 vim-patch:8.0.0272 vim-patch:8.0.0271 vim-patch:8.0.0270 vim-patch:8.0.0269 vim-patch:8.0.0268 vim-patch:8.0.0267 vim-patch:8.0.0260 vim-patch:8.0.0257 vim-patch:8.0.0249 vim-patch:8.0.0248 vim-patch:8.0.0246 vim-patch:8.0.0244 vim-patch:8.0.0241 vim-patch:8.0.0240 vim-patch:8.0.0239 vim-patch:8.0.0232 vim-patch:8.0.0221 vim-patch:8.0.0217 vim-patch:8.0.0215 vim-patch:8.0.0213 vim-patch:8.0.0211 vim-patch:8.0.0203 vim-patch:8.0.0199 vim-patch:8.0.0193 vim-patch:8.0.0192 vim-patch:8.0.0191 vim-patch:8.0.0187 vim-patch:8.0.0183 vim-patch:8.0.0180 vim-patch:8.0.0173 vim-patch:8.0.0171 vim-patch:8.0.0170 vim-patch:8.0.0169 vim-patch:8.0.0166 vim-patch:8.0.0163 vim-patch:8.0.0162 vim-patch:8.0.0161 vim-patch:8.0.0152 vim-patch:8.0.0145 vim-patch:8.0.0144 vim-patch:8.0.0141 vim-patch:8.0.0139 vim-patch:8.0.0138 vim-patch:8.0.0130 vim-patch:8.0.0129 vim-patch:8.0.0123 vim-patch:8.0.0122 vim-patch:8.0.0120 vim-patch:8.0.0117 vim-patch:8.0.0115 vim-patch:8.0.0114 vim-patch:8.0.0113 vim-patch:8.0.0109 vim-patch:8.0.0108 vim-patch:8.0.0107 vim-patch:8.0.0105 vim-patch:8.0.0103 vim-patch:8.0.0098 vim-patch:8.0.0097 vim-patch:8.0.0095 vim-patch:8.0.0094 vim-patch:8.0.0093 vim-patch:8.0.0089 vim-patch:8.0.0087 vim-patch:8.0.0082 vim-patch:8.0.0080 vim-patch:8.0.0077 vim-patch:8.0.0076 vim-patch:8.0.0072 vim-patch:8.0.0071 vim-patch:8.0.0070 vim-patch:8.0.0067 vim-patch:8.0.0065 vim-patch:8.0.0063 vim-patch:8.0.0061 vim-patch:8.0.0059 vim-patch:8.0.0055 vim-patch:8.0.0054 vim-patch:8.0.0051 vim-patch:8.0.0050 vim-patch:8.0.0048 vim-patch:8.0.0045 vim-patch:8.0.0039 vim-patch:8.0.0036 vim-patch:8.0.0030 vim-patch:8.0.0029 vim-patch:8.0.0028 vim-patch:8.0.0027 vim-patch:8.0.0024 vim-patch:8.0.0022 vim-patch:8.0.0021 vim-patch:8.0.0018 vim-patch:8.0.0016 vim-patch:8.0.0015 vim-patch:8.0.0014 vim-patch:8.0.0013 vim-patch:8.0.0011 vim-patch:8.0.0010 vim-patch:8.0.0009 vim-patch:8.0.0007 vim-patch:8.0.0005 --- src/nvim/version.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/nvim/version.c b/src/nvim/version.c index 1a9c9d73e5..e8cf1d0f0b 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -1012,7 +1012,7 @@ static const int included_patches[] = { 247, // 246 NA 245, - // 244, + // 244 NA 243, 242, // 241 NA @@ -1041,7 +1041,7 @@ static const int included_patches[] = { 218, // 217 NA // 216, - // 215, + // 215 NA // 214, // 213 NA // 212, -- cgit From ac4bbf55f6d6b9b252dd90fe800626850022b690 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 10 Dec 2017 22:04:43 +0300 Subject: *: Hide list implementation in other files as well --- src/nvim/api/private/helpers.c | 2 +- src/nvim/edit.c | 16 ++--- src/nvim/eval/decode.c | 28 +++++---- src/nvim/eval/encode.c | 76 +++++++++++----------- src/nvim/eval/encode.h | 13 ++-- src/nvim/eval/typval.h | 16 +++++ src/nvim/eval/typval_encode.c.h | 125 ++++++++++++++++++++++--------------- src/nvim/ex_cmds.c | 14 +++-- src/nvim/ex_getln.c | 43 ++++++------- src/nvim/lua/converter.c | 10 +-- src/nvim/ops.c | 27 ++++---- src/nvim/quickfix.c | 36 ++++++----- src/nvim/regexp.c | 25 +++----- src/nvim/shada.c | 21 +++---- src/nvim/spell.c | 11 ++-- src/nvim/window.c | 50 +++++++-------- test/functional/eval/null_spec.lua | 2 + 17 files changed, 279 insertions(+), 236 deletions(-) diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index e5a1e150f9..4492f8bb93 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -789,7 +789,7 @@ bool object_to_vim(Object obj, typval_T *tv, Error *err) Object item = obj.data.array.items[i]; listitem_T *li = tv_list_item_alloc(); - if (!object_to_vim(item, &li->li_tv, err)) { + if (!object_to_vim(item, TV_LIST_ITEM_TV(li), err)) { // cleanup tv_list_item_free(li); tv_list_free(list); diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 859f98d2ad..2fa4a52387 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -3536,19 +3536,19 @@ theend: /* * Add completions from a list. */ -static void ins_compl_add_list(list_T *list) +static void ins_compl_add_list(list_T *const list) { - listitem_T *li; int dir = compl_direction; - /* Go through the List with matches and add each of them. */ - for (li = list->lv_first; li != NULL; li = li->li_next) { - if (ins_compl_add_tv(&li->li_tv, dir) == OK) - /* if dir was BACKWARD then honor it just once */ + // Go through the List with matches and add each of them. + TV_LIST_ITER(list, li, { + if (ins_compl_add_tv(TV_LIST_ITEM_TV(li), dir) == OK) { + // If dir was BACKWARD then honor it just once. dir = FORWARD; - else if (did_emsg) + } else if (did_emsg) { break; - } + } + }); } /* diff --git a/src/nvim/eval/decode.c b/src/nvim/eval/decode.c index e3098683a4..b2d901462a 100644 --- a/src/nvim/eval/decode.c +++ b/src/nvim/eval/decode.c @@ -120,7 +120,7 @@ static inline int json_decoder_pop(ValuesStackItem obj, last_container = kv_last(*container_stack); } if (last_container.container.v_type == VAR_LIST) { - if (last_container.container.vval.v_list->lv_len != 0 + if (tv_list_len(last_container.container.vval.v_list) != 0 && !obj.didcomma) { EMSG2(_("E474: Expected comma before list item: %s"), val_location); tv_clear(&obj.val); @@ -128,7 +128,7 @@ static inline int json_decoder_pop(ValuesStackItem obj, } assert(last_container.special_val == NULL); listitem_T *obj_li = tv_list_item_alloc(); - obj_li->li_tv = obj.val; + *TV_LIST_ITEM_TV(obj_li) = obj.val; tv_list_append(last_container.container.vval.v_list, obj_li); } else if (last_container.stack_index == kv_size(*stack) - 2) { if (!obj.didcolon) { @@ -155,10 +155,10 @@ static inline int json_decoder_pop(ValuesStackItem obj, list_T *const kv_pair = tv_list_alloc(); tv_list_append_list(last_container.special_val, kv_pair); listitem_T *const key_li = tv_list_item_alloc(); - key_li->li_tv = key.val; + *TV_LIST_ITEM_TV(key_li) = key.val; tv_list_append(kv_pair, key_li); listitem_T *const val_li = tv_list_item_alloc(); - val_li->li_tv = obj.val; + *TV_LIST_ITEM_TV(val_li) = obj.val; tv_list_append(kv_pair, val_li); } } else { @@ -738,8 +738,9 @@ json_decode_string_cycle_start: } else if (last_container.special_val == NULL ? (last_container.container.v_type == VAR_DICT ? (DICT_LEN(last_container.container.vval.v_dict) == 0) - : (last_container.container.vval.v_list->lv_len == 0)) - : (last_container.special_val->lv_len == 0)) { + : (tv_list_len(last_container.container.vval.v_list) + == 0)) + : (tv_list_len(last_container.special_val) == 0)) { emsgf(_("E474: Leading comma: %.*s"), LENP(p, e)); goto json_decode_string_fail; } @@ -1047,9 +1048,10 @@ int msgpack_to_vim(const msgpack_object mobj, typval_T *const rettv) }; for (size_t i = 0; i < mobj.via.array.size; i++) { listitem_T *const li = tv_list_item_alloc(); - li->li_tv.v_type = VAR_UNKNOWN; + TV_LIST_ITEM_TV(li)->v_type = VAR_UNKNOWN; tv_list_append(list, li); - if (msgpack_to_vim(mobj.via.array.ptr[i], &li->li_tv) == FAIL) { + if (msgpack_to_vim(mobj.via.array.ptr[i], TV_LIST_ITEM_TV(li)) + == FAIL) { return FAIL; } } @@ -1094,15 +1096,17 @@ msgpack_to_vim_generic_map: {} list_T *const kv_pair = tv_list_alloc(); tv_list_append_list(list, kv_pair); listitem_T *const key_li = tv_list_item_alloc(); - key_li->li_tv.v_type = VAR_UNKNOWN; + TV_LIST_ITEM_TV(key_li)->v_type = VAR_UNKNOWN; tv_list_append(kv_pair, key_li); listitem_T *const val_li = tv_list_item_alloc(); - val_li->li_tv.v_type = VAR_UNKNOWN; + TV_LIST_ITEM_TV(val_li)->v_type = VAR_UNKNOWN; tv_list_append(kv_pair, val_li); - if (msgpack_to_vim(mobj.via.map.ptr[i].key, &key_li->li_tv) == FAIL) { + if (msgpack_to_vim(mobj.via.map.ptr[i].key, TV_LIST_ITEM_TV(key_li)) + == FAIL) { return FAIL; } - if (msgpack_to_vim(mobj.via.map.ptr[i].val, &val_li->li_tv) == FAIL) { + if (msgpack_to_vim(mobj.via.map.ptr[i].val, TV_LIST_ITEM_TV(val_li)) + == FAIL) { return FAIL; } } diff --git a/src/nvim/eval/encode.c b/src/nvim/eval/encode.c index ef647b3ee4..0e5db84ad5 100644 --- a/src/nvim/eval/encode.c +++ b/src/nvim/eval/encode.c @@ -53,17 +53,18 @@ int encode_list_write(void *const data, const char *const buf, const size_t len) list_T *const list = (list_T *) data; const char *const end = buf + len; const char *line_end = buf; - listitem_T *li = list->lv_last; + listitem_T *li = tv_list_last(list); // Continue the last list element if (li != NULL) { line_end = xmemscan(buf, NL, len); if (line_end != buf) { const size_t line_length = (size_t)(line_end - buf); - char *str = (char *)li->li_tv.vval.v_string; + char *str = (char *)TV_LIST_ITEM_TV(li)->vval.v_string; const size_t li_len = (str == NULL ? 0 : strlen(str)); - li->li_tv.vval.v_string = xrealloc(str, li_len + line_length + 1); - str = (char *)li->li_tv.vval.v_string + li_len; + TV_LIST_ITEM_TV(li)->vval.v_string = xrealloc( + str, li_len + line_length + 1); + str = (char *)TV_LIST_ITEM_TV(li)->vval.v_string + li_len; memcpy(str, buf, line_length); str[line_length] = 0; memchrsub(str, NUL, NL, line_length); @@ -135,21 +136,18 @@ static int conv_error(const char *const msg, const MPConvStack *const mpstack, } case kMPConvPairs: case kMPConvList: { - int idx = 0; - const listitem_T *li; - for (li = v.data.l.list->lv_first; - li != NULL && li->li_next != v.data.l.li; - li = li->li_next) { - idx++; - } + const listitem_T *const li = TV_LIST_ITEM_PREV(v.data.l.list, + v.data.l.li); + int idx = (int)tv_list_idx_of_item(v.data.l.list, li); if (v.type == kMPConvList || li == NULL - || (li->li_tv.v_type != VAR_LIST - && li->li_tv.vval.v_list->lv_len <= 0)) { - vim_snprintf((char *) IObuff, IOSIZE, idx_msg, idx); + || (TV_LIST_ITEM_TV(li)->v_type != VAR_LIST + && tv_list_len(TV_LIST_ITEM_TV(li)->vval.v_list) <= 0)) { + vim_snprintf((char *)IObuff, IOSIZE, idx_msg, idx); ga_concat(&msg_ga, IObuff); } else { - typval_T key_tv = li->li_tv.vval.v_list->lv_first->li_tv; + typval_T key_tv = *TV_LIST_ITEM_TV( + tv_list_first(TV_LIST_ITEM_TV(li)->vval.v_list)); char *const key = encode_tv2echo(&key_tv, NULL); vim_snprintf((char *) IObuff, IOSIZE, key_pair_msg, key, idx); xfree(key); @@ -202,21 +200,17 @@ bool encode_vim_list_to_buf(const list_T *const list, size_t *const ret_len, FUNC_ATTR_NONNULL_ARG(2, 3) FUNC_ATTR_WARN_UNUSED_RESULT { size_t len = 0; - if (list != NULL) { - for (const listitem_T *li = list->lv_first; - li != NULL; - li = li->li_next) { - if (li->li_tv.v_type != VAR_STRING) { - return false; - } - len++; - if (li->li_tv.vval.v_string != 0) { - len += STRLEN(li->li_tv.vval.v_string); - } + TV_LIST_ITER_CONST(list, li, { + if (TV_LIST_ITEM_TV(li)->v_type != VAR_STRING) { + return false; } - if (len) { - len--; + len++; + if (TV_LIST_ITEM_TV(li)->vval.v_string != 0) { + len += STRLEN(TV_LIST_ITEM_TV(li)->vval.v_string); } + }); + if (len) { + len--; } *ret_len = len; if (len == 0) { @@ -253,31 +247,34 @@ int encode_read_from_list(ListReaderState *const state, char *const buf, char *const buf_end = buf + nbuf; char *p = buf; while (p < buf_end) { - assert(state->li_length == 0 || state->li->li_tv.vval.v_string != NULL); + assert(state->li_length == 0 + || TV_LIST_ITEM_TV(state->li)->vval.v_string != NULL); for (size_t i = state->offset; i < state->li_length && p < buf_end; i++) { - assert(state->li->li_tv.vval.v_string != NULL); - const char ch = (char)state->li->li_tv.vval.v_string[state->offset++]; + assert(TV_LIST_ITEM_TV(state->li)->vval.v_string != NULL); + const char ch = (char)( + TV_LIST_ITEM_TV(state->li)->vval.v_string[state->offset++]); *p++ = (char)((char)ch == (char)NL ? (char)NUL : (char)ch); } if (p < buf_end) { - state->li = state->li->li_next; + state->li = TV_LIST_ITEM_NEXT(state->list, state->li); if (state->li == NULL) { *read_bytes = (size_t) (p - buf); return OK; } *p++ = NL; - if (state->li->li_tv.v_type != VAR_STRING) { + if (TV_LIST_ITEM_TV(state->li)->v_type != VAR_STRING) { *read_bytes = (size_t) (p - buf); return FAIL; } state->offset = 0; - state->li_length = (state->li->li_tv.vval.v_string == NULL + state->li_length = (TV_LIST_ITEM_TV(state->li)->vval.v_string == NULL ? 0 - : STRLEN(state->li->li_tv.vval.v_string)); + : STRLEN(TV_LIST_ITEM_TV(state->li)->vval.v_string)); } } *read_bytes = nbuf; - return (state->offset < state->li_length || state->li->li_next != NULL + return ((state->offset < state->li_length + || TV_LIST_ITEM_NEXT(state->list, state->li) != NULL) ? NOTDONE : OK); } @@ -727,12 +724,11 @@ bool encode_check_json_key(const typval_T *const tv) if (val_di->di_tv.vval.v_list == NULL) { return true; } - for (const listitem_T *li = val_di->di_tv.vval.v_list->lv_first; - li != NULL; li = li->li_next) { - if (li->li_tv.v_type != VAR_STRING) { + TV_LIST_ITER_CONST(val_di->di_tv.vval.v_list, li, { + if (TV_LIST_ITEM_TV(li)->v_type != VAR_STRING) { return false; } - } + }); return true; } diff --git a/src/nvim/eval/encode.h b/src/nvim/eval/encode.h index 9bc665253b..ccea245ab3 100644 --- a/src/nvim/eval/encode.h +++ b/src/nvim/eval/encode.h @@ -33,9 +33,10 @@ int encode_vim_to_echo(garray_T *const packer, /// Structure defining state for read_from_list() typedef struct { + const list_T *const list; ///< List being currently read. const listitem_T *li; ///< Item currently read. - size_t offset; ///< Byte offset inside the read item. - size_t li_length; ///< Length of the string inside the read item. + size_t offset; ///< Byte offset inside the read item. + size_t li_length; ///< Length of the string inside the read item. } ListReaderState; /// Initialize ListReaderState structure @@ -43,11 +44,13 @@ static inline ListReaderState encode_init_lrstate(const list_T *const list) FUNC_ATTR_NONNULL_ALL { return (ListReaderState) { - .li = list->lv_first, + .list = list, + .li = tv_list_first(list), .offset = 0, - .li_length = (list->lv_first->li_tv.vval.v_string == NULL + .li_length = (TV_LIST_ITEM_TV(tv_list_first(list))->vval.v_string == NULL ? 0 - : STRLEN(list->lv_first->li_tv.vval.v_string)), + : STRLEN(TV_LIST_ITEM_TV( + tv_list_first(list))->vval.v_string)), }; } diff --git a/src/nvim/eval/typval.h b/src/nvim/eval/typval.h index 2a0c52b4be..a05abd8a53 100644 --- a/src/nvim/eval/typval.h +++ b/src/nvim/eval/typval.h @@ -383,6 +383,22 @@ static inline listitem_T *tv_list_first(const list_T *const l) return l->lv_first; } +static inline listitem_T *tv_list_last(const list_T *const l) + REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; + +/// Get last list item +/// +/// @param[in] l List to get item from. +/// +/// @return List item or NULL in case of an empty list. +static inline listitem_T *tv_list_last(const list_T *const l) +{ + if (l == NULL) { + return NULL; + } + return l->lv_last; +} + static inline long tv_dict_len(const dict_T *const d) REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; diff --git a/src/nvim/eval/typval_encode.c.h b/src/nvim/eval/typval_encode.c.h index a93ad2dbba..800760cf7b 100644 --- a/src/nvim/eval/typval_encode.c.h +++ b/src/nvim/eval/typval_encode.c.h @@ -355,14 +355,14 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( break; } case VAR_LIST: { - if (tv->vval.v_list == NULL || tv->vval.v_list->lv_len == 0) { + if (tv->vval.v_list == NULL || tv_list_len(tv->vval.v_list) == 0) { TYPVAL_ENCODE_CONV_EMPTY_LIST(tv); break; } const int saved_copyID = tv->vval.v_list->lv_copyID; _TYPVAL_ENCODE_DO_CHECK_SELF_REFERENCE(tv->vval.v_list, lv_copyID, copyID, kMPConvList); - TYPVAL_ENCODE_CONV_LIST_START(tv, tv->vval.v_list->lv_len); + TYPVAL_ENCODE_CONV_LIST_START(tv, tv_list_len(tv->vval.v_list)); assert(saved_copyID != copyID && saved_copyID != copyID - 1); _mp_push(*mpstack, ((MPConvStackVal) { .type = kMPConvList, @@ -371,7 +371,7 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( .data = { .l = { .list = tv->vval.v_list, - .li = tv->vval.v_list->lv_first, + .li = tv_list_first(tv->vval.v_list), }, }, })); @@ -440,23 +440,43 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( // bits is not checked), other unsigned and have at most 31 // non-zero bits (number of bits is not checked). if (val_di->di_tv.v_type != VAR_LIST - || (val_list = val_di->di_tv.vval.v_list) == NULL - || val_list->lv_len != 4 - || val_list->lv_first->li_tv.v_type != VAR_NUMBER - || (sign = val_list->lv_first->li_tv.vval.v_number) == 0 - || val_list->lv_first->li_next->li_tv.v_type != VAR_NUMBER - || (highest_bits = - val_list->lv_first->li_next->li_tv.vval.v_number) < 0 - || val_list->lv_last->li_prev->li_tv.v_type != VAR_NUMBER - || (high_bits = - val_list->lv_last->li_prev->li_tv.vval.v_number) < 0 - || val_list->lv_last->li_tv.v_type != VAR_NUMBER - || (low_bits = val_list->lv_last->li_tv.vval.v_number) < 0) { + || tv_list_len(val_list = val_di->di_tv.vval.v_list) != 4) { goto _convert_one_value_regular_dict; } - uint64_t number = ((uint64_t)(((uint64_t)highest_bits) << 62) - | (uint64_t)(((uint64_t)high_bits) << 31) - | (uint64_t)low_bits); + + const listitem_T *const sign_li = tv_list_first(val_list); + if (TV_LIST_ITEM_TV(sign_li)->v_type != VAR_NUMBER + || (sign = TV_LIST_ITEM_TV(sign_li)->vval.v_number) == 0) { + goto _convert_one_value_regular_dict; + } + + const listitem_T *const highest_bits_li = ( + TV_LIST_ITEM_NEXT(val_list, sign_li)); + if (TV_LIST_ITEM_TV(highest_bits_li)->v_type != VAR_NUMBER + || ((highest_bits + = TV_LIST_ITEM_TV(highest_bits_li)->vval.v_number) + < 0)) { + goto _convert_one_value_regular_dict; + } + + const listitem_T *const high_bits_li = ( + TV_LIST_ITEM_NEXT(val_list, highest_bits_li)); + if (TV_LIST_ITEM_TV(high_bits_li)->v_type != VAR_NUMBER + || ((high_bits = TV_LIST_ITEM_TV(high_bits_li)->vval.v_number) + < 0)) { + goto _convert_one_value_regular_dict; + } + + const listitem_T *const low_bits_li = tv_list_last(val_list); + if (TV_LIST_ITEM_TV(low_bits_li)->v_type != VAR_NUMBER + || ((low_bits = TV_LIST_ITEM_TV(low_bits_li)->vval.v_number) + < 0)) { + goto _convert_one_value_regular_dict; + } + + const uint64_t number = ((uint64_t)(((uint64_t)highest_bits) << 62) + | (uint64_t)(((uint64_t)high_bits) << 31) + | (uint64_t)low_bits); if (sign > 0) { TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER(tv, number); } else { @@ -499,8 +519,8 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( _TYPVAL_ENCODE_DO_CHECK_SELF_REFERENCE(val_di->di_tv.vval.v_list, lv_copyID, copyID, kMPConvList); - TYPVAL_ENCODE_CONV_LIST_START(tv, - val_di->di_tv.vval.v_list->lv_len); + TYPVAL_ENCODE_CONV_LIST_START( + tv, tv_list_len(val_di->di_tv.vval.v_list)); assert(saved_copyID != copyID && saved_copyID != copyID - 1); _mp_push(*mpstack, ((MPConvStackVal) { .tv = tv, @@ -509,7 +529,7 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( .data = { .l = { .list = val_di->di_tv.vval.v_list, - .li = val_di->di_tv.vval.v_list->lv_first, + .li = tv_list_first(val_di->di_tv.vval.v_list), }, }, })); @@ -520,22 +540,21 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( goto _convert_one_value_regular_dict; } list_T *const val_list = val_di->di_tv.vval.v_list; - if (val_list == NULL || val_list->lv_len == 0) { + if (val_list == NULL || tv_list_len(val_list) == 0) { TYPVAL_ENCODE_CONV_EMPTY_DICT(tv, TYPVAL_ENCODE_NODICT_VAR); break; } - for (const listitem_T *li = val_list->lv_first; li != NULL; - li = li->li_next) { - if (li->li_tv.v_type != VAR_LIST - || li->li_tv.vval.v_list->lv_len != 2) { + TV_LIST_ITER_CONST(val_list, li, { + if (TV_LIST_ITEM_TV(li)->v_type != VAR_LIST + || tv_list_len(TV_LIST_ITEM_TV(li)->vval.v_list) != 2) { goto _convert_one_value_regular_dict; } - } + }); const int saved_copyID = val_di->di_tv.vval.v_list->lv_copyID; _TYPVAL_ENCODE_DO_CHECK_SELF_REFERENCE(val_list, lv_copyID, copyID, kMPConvPairs); TYPVAL_ENCODE_CONV_DICT_START(tv, TYPVAL_ENCODE_NODICT_VAR, - val_list->lv_len); + tv_list_len(val_list)); assert(saved_copyID != copyID && saved_copyID != copyID - 1); _mp_push(*mpstack, ((MPConvStackVal) { .tv = tv, @@ -544,7 +563,7 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( .data = { .l = { .list = val_list, - .li = val_list->lv_first, + .li = tv_list_first(val_list), }, }, })); @@ -554,18 +573,22 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( const list_T *val_list; varnumber_T type; if (val_di->di_tv.v_type != VAR_LIST - || (val_list = val_di->di_tv.vval.v_list) == NULL - || val_list->lv_len != 2 - || (val_list->lv_first->li_tv.v_type != VAR_NUMBER) - || (type = val_list->lv_first->li_tv.vval.v_number) > INT8_MAX + || tv_list_len((val_list = val_di->di_tv.vval.v_list)) != 2 + || (TV_LIST_ITEM_TV(tv_list_first(val_list))->v_type + != VAR_NUMBER) + || ((type + = TV_LIST_ITEM_TV(tv_list_first(val_list))->vval.v_number) + > INT8_MAX) || type < INT8_MIN - || (val_list->lv_last->li_tv.v_type != VAR_LIST)) { + || (TV_LIST_ITEM_TV(tv_list_last(val_list))->v_type + != VAR_LIST)) { goto _convert_one_value_regular_dict; } size_t len; char *buf; - if (!encode_vim_list_to_buf(val_list->lv_last->li_tv.vval.v_list, - &len, &buf)) { + if (!encode_vim_list_to_buf( + TV_LIST_ITEM_TV(tv_list_last(val_list))->vval.v_list, &len, + &buf)) { goto _convert_one_value_regular_dict; } TYPVAL_ENCODE_CONV_EXT_STRING(tv, buf, len, type); @@ -674,11 +697,13 @@ typval_encode_stop_converting_one_item: cur_mpsv->data.l.list->lv_copyID = cur_mpsv->saved_copyID; TYPVAL_ENCODE_CONV_LIST_END(cur_mpsv->tv); continue; - } else if (cur_mpsv->data.l.li != cur_mpsv->data.l.list->lv_first) { + } else if (cur_mpsv->data.l.li + != tv_list_first(cur_mpsv->data.l.list)) { TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS(cur_mpsv->tv); } - tv = &cur_mpsv->data.l.li->li_tv; - cur_mpsv->data.l.li = cur_mpsv->data.l.li->li_next; + tv = TV_LIST_ITEM_TV(cur_mpsv->data.l.li); + cur_mpsv->data.l.li = TV_LIST_ITEM_NEXT(cur_mpsv->data.l.list, + cur_mpsv->data.l.li); break; } case kMPConvPairs: { @@ -687,24 +712,26 @@ typval_encode_stop_converting_one_item: cur_mpsv->data.l.list->lv_copyID = cur_mpsv->saved_copyID; TYPVAL_ENCODE_CONV_DICT_END(cur_mpsv->tv, TYPVAL_ENCODE_NODICT_VAR); continue; - } else if (cur_mpsv->data.l.li != cur_mpsv->data.l.list->lv_first) { + } else if (cur_mpsv->data.l.li + != tv_list_first(cur_mpsv->data.l.list)) { TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS( cur_mpsv->tv, TYPVAL_ENCODE_NODICT_VAR); } - const list_T *const kv_pair = cur_mpsv->data.l.li->li_tv.vval.v_list; + const list_T *const kv_pair = ( + TV_LIST_ITEM_TV(cur_mpsv->data.l.li)->vval.v_list); TYPVAL_ENCODE_SPECIAL_DICT_KEY_CHECK( - encode_vim_to__error_ret, kv_pair->lv_first->li_tv); - if (_TYPVAL_ENCODE_CONVERT_ONE_VALUE(TYPVAL_ENCODE_FIRST_ARG_NAME, - &mpstack, cur_mpsv, - &kv_pair->lv_first->li_tv, - copyID, - objname) == FAIL) { + encode_vim_to__error_ret, *TV_LIST_ITEM_TV(tv_list_first(kv_pair))); + if (_TYPVAL_ENCODE_CONVERT_ONE_VALUE( + TYPVAL_ENCODE_FIRST_ARG_NAME, &mpstack, cur_mpsv, + TV_LIST_ITEM_TV(tv_list_first(kv_pair)), copyID, objname) + == FAIL) { goto encode_vim_to__error_ret; } TYPVAL_ENCODE_CONV_DICT_AFTER_KEY(cur_mpsv->tv, TYPVAL_ENCODE_NODICT_VAR); - tv = &kv_pair->lv_last->li_tv; - cur_mpsv->data.l.li = cur_mpsv->data.l.li->li_next; + tv = TV_LIST_ITEM_TV(tv_list_last(kv_pair)); + cur_mpsv->data.l.li = TV_LIST_ITEM_NEXT(cur_mpsv->data.l.list, + cur_mpsv->data.l.li); break; } case kMPConvPartial: { diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 8616508d88..4f54d4c88b 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -6334,7 +6334,6 @@ char_u *skip_vimgrep_pat(char_u *p, char_u **s, int *flags) void ex_oldfiles(exarg_T *eap) { list_T *l = get_vim_var_list(VV_OLDFILES); - listitem_T *li; long nr = 0; if (l == NULL) { @@ -6342,19 +6341,22 @@ void ex_oldfiles(exarg_T *eap) } else { msg_start(); msg_scroll = true; - for (li = l->lv_first; li != NULL && !got_int; li = li->li_next) { + TV_LIST_ITER(l, li, { + if (got_int) { + break; + } nr++; - const char *fname = tv_get_string(&li->li_tv); + const char *fname = tv_get_string(TV_LIST_ITEM_TV(li)); if (!message_filtered((char_u *)fname)) { msg_outnum(nr); MSG_PUTS(": "); - msg_outtrans((char_u *)tv_get_string(&li->li_tv)); + msg_outtrans((char_u *)tv_get_string(TV_LIST_ITEM_TV(li))); msg_clr_eos(); msg_putchar('\n'); ui_flush(); // output one line at a time os_breakcheck(); } - } + }); // Assume "got_int" was set to truncate the listing. got_int = false; @@ -6364,7 +6366,7 @@ void ex_oldfiles(exarg_T *eap) quit_more = false; nr = prompt_for_number(false); msg_starthere(); - if (nr > 0 && nr <= l->lv_len) { + if (nr > 0 && nr <= tv_list_len(l)) { const char *const p = tv_list_find_str(l, nr - 1); if (p == NULL) { return; diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 1adc8325f8..fcd230d535 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -2618,20 +2618,20 @@ static bool color_cmdline(CmdlineInfo *colored_ccline) } varnumber_T prev_end = 0; int i = 0; - for (const listitem_T *li = tv.vval.v_list->lv_first; - li != NULL; li = li->li_next, i++) { - if (li->li_tv.v_type != VAR_LIST) { + TV_LIST_ITER_CONST(tv.vval.v_list, li, { + if (TV_LIST_ITEM_TV(li)->v_type != VAR_LIST) { PRINT_ERRMSG(_("E5401: List item %i is not a List"), i); goto color_cmdline_error; } - const list_T *const l = li->li_tv.vval.v_list; + const list_T *const l = TV_LIST_ITEM_TV(li)->vval.v_list; if (tv_list_len(l) != 3) { PRINT_ERRMSG(_("E5402: List item %i has incorrect length: %li /= 3"), i, tv_list_len(l)); goto color_cmdline_error; } bool error = false; - const varnumber_T start = tv_get_number_chk(&l->lv_first->li_tv, &error); + const varnumber_T start = ( + tv_get_number_chk(TV_LIST_ITEM_TV(tv_list_first(l)), &error)); if (error) { goto color_cmdline_error; } else if (!(prev_end <= start && start < colored_ccline->cmdlen)) { @@ -2651,8 +2651,8 @@ static bool color_cmdline(CmdlineInfo *colored_ccline) .attr = 0, })); } - const varnumber_T end = tv_get_number_chk(&l->lv_first->li_next->li_tv, - &error); + const varnumber_T end = tv_get_number_chk( + TV_LIST_ITEM_TV(TV_LIST_ITEM_NEXT(l, tv_list_first(l))), &error); if (error) { goto color_cmdline_error; } else if (!(start < end && end <= colored_ccline->cmdlen)) { @@ -2668,7 +2668,8 @@ static bool color_cmdline(CmdlineInfo *colored_ccline) goto color_cmdline_error; } prev_end = end; - const char *const group = tv_get_string_chk(&l->lv_last->li_tv); + const char *const group = tv_get_string_chk( + TV_LIST_ITEM_TV(tv_list_last(l))); if (group == NULL) { goto color_cmdline_error; } @@ -2679,7 +2680,7 @@ static bool color_cmdline(CmdlineInfo *colored_ccline) .end = end, .attr = attr, })); - } + }); if (prev_end < colored_ccline->cmdlen) { kv_push(ccline_colors->colors, ((CmdlineColorChunk) { .start = prev_end, @@ -5021,24 +5022,24 @@ static int ExpandUserDefined(expand_T *xp, regmatch_T *regmatch, int *num_file, */ static int ExpandUserList(expand_T *xp, int *num_file, char_u ***file) { - list_T *retlist; - listitem_T *li; - garray_T ga; - - retlist = call_user_expand_func((user_expand_func_T)call_func_retlist, xp, - num_file, file); + list_T *const retlist = call_user_expand_func( + (user_expand_func_T)call_func_retlist, xp, num_file, file); if (retlist == NULL) { return FAIL; } + garray_T ga; ga_init(&ga, (int)sizeof(char *), 3); - /* Loop over the items in the list. */ - for (li = retlist->lv_first; li != NULL; li = li->li_next) { - if (li->li_tv.v_type != VAR_STRING || li->li_tv.vval.v_string == NULL) - continue; /* Skip non-string items and empty strings */ + // Loop over the items in the list. + TV_LIST_ITER_CONST(retlist, li, { + if (TV_LIST_ITEM_TV(li)->v_type != VAR_STRING + || TV_LIST_ITEM_TV(li)->vval.v_string == NULL) { + continue; // Skip non-string items and empty strings. + } - GA_APPEND(char_u *, &ga, vim_strsave(li->li_tv.vval.v_string)); - } + GA_APPEND(char *, &ga, xstrdup( + (const char *)TV_LIST_ITEM_TV(li)->vval.v_string)); + }); tv_list_unref(retlist); *file = ga.ga_data; diff --git a/src/nvim/lua/converter.c b/src/nvim/lua/converter.c index 18134c56e3..8303ecdd37 100644 --- a/src/nvim/lua/converter.c +++ b/src/nvim/lua/converter.c @@ -214,9 +214,9 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) list_T *const kv_pair = tv_list_alloc(); tv_list_append_list(cur.tv->vval.v_list, kv_pair); listitem_T *const key = tv_list_item_alloc(); - key->li_tv = decode_string(s, len, kTrue, false, false); + *TV_LIST_ITEM_TV(key) = decode_string(s, len, kTrue, false, false); tv_list_append(kv_pair, key); - if (key->li_tv.v_type == VAR_UNKNOWN) { + if (TV_LIST_ITEM_TV(key)->v_type == VAR_UNKNOWN) { ret = false; tv_list_unref(kv_pair); continue; @@ -224,7 +224,7 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) listitem_T *const val = tv_list_item_alloc(); tv_list_append(kv_pair, val); kv_push(stack, cur); - cur = (TVPopStackItem) { &val->li_tv, false, false, 0 }; + cur = (TVPopStackItem) { TV_LIST_ITEM_TV(val), false, false, 0 }; } else { dictitem_T *const di = tv_dict_item_alloc_len(s, len); if (tv_dict_add(cur.tv->vval.v_dict, di) == FAIL) { @@ -239,7 +239,7 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) } } else { assert(cur.tv->v_type == VAR_LIST); - lua_rawgeti(lstate, -1, cur.tv->vval.v_list->lv_len + 1); + lua_rawgeti(lstate, -1, tv_list_len(cur.tv->vval.v_list) + 1); if (lua_isnil(lstate, -1)) { lua_pop(lstate, 2); continue; @@ -247,7 +247,7 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) listitem_T *const li = tv_list_item_alloc(); tv_list_append(cur.tv->vval.v_list, li); kv_push(stack, cur); - cur = (TVPopStackItem) { &li->li_tv, false, false, 0 }; + cur = (TVPopStackItem) { TV_LIST_ITEM_TV(li), false, false, 0 }; } } assert(!cur.container); diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 9f445204d0..3cc73b2e40 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -4854,9 +4854,8 @@ static void *get_reg_wrap_one_line(char_u *s, int flags) if (!(flags & kGRegList)) { return s; } - list_T *list = tv_list_alloc(); - tv_list_append_string(list, NULL, 0); - list->lv_first->li_tv.vval.v_string = s; + list_T *const list = tv_list_alloc(); + tv_list_append_allocated_string(list, (char *)s); return list; } @@ -5610,12 +5609,13 @@ static bool get_clipboard(int name, yankreg_T **target, bool quiet) list_T *res = result.vval.v_list; list_T *lines = NULL; - if (res->lv_len == 2 && res->lv_first->li_tv.v_type == VAR_LIST) { - lines = res->lv_first->li_tv.vval.v_list; - if (res->lv_last->li_tv.v_type != VAR_STRING) { + if (tv_list_len(res) == 2 + && TV_LIST_ITEM_TV(tv_list_first(res))->v_type == VAR_LIST) { + lines = TV_LIST_ITEM_TV(tv_list_first(res))->vval.v_list; + if (TV_LIST_ITEM_TV(tv_list_last(res))->v_type != VAR_STRING) { goto err; } - char_u *regtype = res->lv_last->li_tv.vval.v_string; + char_u *regtype = TV_LIST_ITEM_TV(tv_list_last(res))->vval.v_string; if (regtype == NULL || strlen((char*)regtype) > 1) { goto err; } @@ -5641,20 +5641,21 @@ static bool get_clipboard(int name, yankreg_T **target, bool quiet) reg->y_type = kMTUnknown; } - reg->y_array = xcalloc((size_t)lines->lv_len, sizeof(uint8_t *)); - reg->y_size = (size_t)lines->lv_len; + reg->y_array = xcalloc((size_t)tv_list_len(lines), sizeof(char_u *)); + reg->y_size = (size_t)tv_list_len(lines); reg->additional_data = NULL; reg->timestamp = 0; // Timestamp is not saved for clipboard registers because clipboard registers // are not saved in the ShaDa file. int i = 0; - for (listitem_T *li = lines->lv_first; li != NULL; li = li->li_next) { - if (li->li_tv.v_type != VAR_STRING) { + TV_LIST_ITER_CONST(lines, li, { + if (TV_LIST_ITEM_TV(li)->v_type != VAR_STRING) { goto err; } - reg->y_array[i++] = (char_u *)xstrdupnul((char *)li->li_tv.vval.v_string); - } + reg->y_array[i++] = (char_u *)xstrdupnul( + (const char *)TV_LIST_ITEM_TV(li)->vval.v_string); + }); if (reg->y_size > 0 && strlen((char*)reg->y_array[reg->y_size-1]) == 0) { // a known-to-be charwise yank might have a final linebreak diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 1fc585f0c9..56d11c21ab 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -156,6 +156,7 @@ typedef struct { FILE *fd; typval_T *tv; char_u *p_str; + list_T *p_list; listitem_T *p_li; buf_T *buf; linenr_T buflnum; @@ -516,17 +517,17 @@ static int qf_get_next_list_line(qfstate_T *state) // Get the next line from the supplied list while (p_li != NULL - && (p_li->li_tv.v_type != VAR_STRING - || p_li->li_tv.vval.v_string == NULL)) { - p_li = p_li->li_next; // Skip non-string items + && (TV_LIST_ITEM_TV(p_li)->v_type != VAR_STRING + || TV_LIST_ITEM_TV(p_li)->vval.v_string == NULL)) { + p_li = TV_LIST_ITEM_NEXT(state->p_list, p_li); // Skip non-string items. } - if (p_li == NULL) { // End of the list + if (p_li == NULL) { // End of the list. state->p_li = NULL; return QF_END_OF_INPUT; } - len = STRLEN(p_li->li_tv.vval.v_string); + len = STRLEN(TV_LIST_ITEM_TV(p_li)->vval.v_string); if (len > IOSIZE - 2) { state->linebuf = qf_grow_linebuf(state, len); } else { @@ -534,9 +535,10 @@ static int qf_get_next_list_line(qfstate_T *state) state->linelen = len; } - STRLCPY(state->linebuf, p_li->li_tv.vval.v_string, state->linelen + 1); + STRLCPY(state->linebuf, TV_LIST_ITEM_TV(p_li)->vval.v_string, + state->linelen + 1); - state->p_li = p_li->li_next; // next item + state->p_li = TV_LIST_ITEM_NEXT(state->p_list, p_li); return QF_OK; } @@ -983,7 +985,7 @@ qf_init_ext( ) { qfstate_T state = { NULL, 0, NULL, 0, NULL, NULL, NULL, NULL, - NULL, 0, 0 }; + NULL, NULL, 0, 0 }; qffields_T fields = { NULL, NULL, 0, 0L, 0, false, NULL, 0, 0, 0 }; qfline_T *old_last = NULL; bool adding = false; @@ -1064,7 +1066,8 @@ qf_init_ext( if (tv->v_type == VAR_STRING) { state.p_str = tv->vval.v_string; } else if (tv->v_type == VAR_LIST) { - state.p_li = tv->vval.v_list->lv_first; + state.p_list = tv->vval.v_list; + state.p_li = tv_list_first(tv->vval.v_list); } state.tv = tv; } @@ -4100,7 +4103,6 @@ int get_errorlist_properties(win_T *wp, dict_T *what, dict_T *retdict) static int qf_add_entries(qf_info_T *qi, list_T *list, char_u *title, int action) { - listitem_T *li; dict_T *d; qfline_T *old_last = NULL; int retval = OK; @@ -4117,13 +4119,15 @@ static int qf_add_entries(qf_info_T *qi, list_T *list, char_u *title, qf_store_title(qi, title); } - for (li = list->lv_first; li != NULL; li = li->li_next) { - if (li->li_tv.v_type != VAR_DICT) - continue; /* Skip non-dict items */ + TV_LIST_ITER_CONST(list, li, { + if (TV_LIST_ITEM_TV(li)->v_type != VAR_DICT) { + continue; // Skip non-dict items. + } - d = li->li_tv.vval.v_dict; - if (d == NULL) + d = TV_LIST_ITEM_TV(li)->vval.v_dict; + if (d == NULL) { continue; + } char *const filename = tv_dict_get_string(d, "filename", true); int bufnum = (int)tv_dict_get_number(d, "bufnr"); @@ -4175,7 +4179,7 @@ static int qf_add_entries(qf_info_T *qi, list_T *list, char_u *title, retval = FAIL; break; } - } + }); if (qi->qf_lists[qi->qf_curlist].qf_index == 0) { // no valid entry diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c index ae611a0005..ffe393f1b0 100644 --- a/src/nvim/regexp.c +++ b/src/nvim/regexp.c @@ -6477,10 +6477,6 @@ static regsubmatch_T rsm; // can only be used when can_f_submatch is true /// vim_regsub_both(). static int fill_submatch_list(int argc, typval_T *argv, int argcount) { - listitem_T *li; - int i; - char_u *s; - if (argcount == 0) { // called function doesn't take an argument return 0; @@ -6490,28 +6486,26 @@ static int fill_submatch_list(int argc, typval_T *argv, int argcount) init_static_list((staticList10_T *)(argv->vval.v_list)); // There are always 10 list items in staticList10_T. - li = argv->vval.v_list->lv_first; - for (i = 0; i < 10; i++) { - s = rsm.sm_match->startp[i]; + listitem_T *li = tv_list_first(argv->vval.v_list); + for (int i = 0; i < 10; i++) { + char_u *s = rsm.sm_match->startp[i]; if (s == NULL || rsm.sm_match->endp[i] == NULL) { s = NULL; } else { s = vim_strnsave(s, (int)(rsm.sm_match->endp[i] - s)); } - li->li_tv.v_type = VAR_STRING; - li->li_tv.vval.v_string = s; - li = li->li_next; + TV_LIST_ITEM_TV(li)->v_type = VAR_STRING; + TV_LIST_ITEM_TV(li)->vval.v_string = s; + li = TV_LIST_ITEM_NEXT(argv->vval.v_list, li); } return 1; } static void clear_submatch_list(staticList10_T *sl) { - int i; - - for (i = 0; i < 10; i++) { - xfree(sl->sl_items[i].li_tv.vval.v_string); - } + TV_LIST_ITER(&sl->sl_list, li, { + xfree(TV_LIST_ITEM_TV(li)->vval.v_string); + }); } /// vim_regsub() - perform substitutions after a vim_regexec() or @@ -6651,6 +6645,7 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, rettv.vval.v_string = NULL; argv[0].v_type = VAR_LIST; argv[0].vval.v_list = &matchList.sl_list; + // FIXME: Abstract away matchList.sl_list.lv_len = 0; if (expr->v_type == VAR_FUNC) { s = expr->vval.v_string; diff --git a/src/nvim/shada.c b/src/nvim/shada.c index 736d6bf162..75f91ce6d8 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -1180,8 +1180,7 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) list_T *oldfiles_list = get_vim_var_list(VV_OLDFILES); const bool force = flags & kShaDaForceit; const bool get_old_files = (flags & (kShaDaGetOldfiles | kShaDaForceit) - && (force || oldfiles_list == NULL - || oldfiles_list->lv_len == 0)); + && (force || tv_list_len(oldfiles_list) == 0)); const bool want_marks = flags & kShaDaWantMarks; const unsigned srni_flags = (unsigned) ( (flags & kShaDaWantInfo @@ -1599,13 +1598,13 @@ static ShaDaWriteResult shada_pack_entry(msgpack_packer *const packer, #define DUMP_ADDITIONAL_ELEMENTS(src, what) \ do { \ if ((src) != NULL) { \ - for (listitem_T *li = (src)->lv_first; li != NULL; li = li->li_next) { \ - if (encode_vim_to_msgpack(spacker, &li->li_tv, \ + TV_LIST_ITER((src), li, { \ + if (encode_vim_to_msgpack(spacker, TV_LIST_ITEM_TV(li), \ _("additional elements of ShaDa " what)) \ == FAIL) { \ goto shada_pack_entry_error; \ } \ - } \ + }); \ } \ } while (0) #define DUMP_ADDITIONAL_DATA(src, what) \ @@ -1648,9 +1647,7 @@ static ShaDaWriteResult shada_pack_entry(msgpack_packer *const packer, const bool is_hist_search = entry.data.history_item.histtype == HIST_SEARCH; const size_t arr_size = 2 + (size_t) is_hist_search + (size_t) ( - entry.data.history_item.additional_elements == NULL - ? 0 - : entry.data.history_item.additional_elements->lv_len); + tv_list_len(entry.data.history_item.additional_elements)); msgpack_pack_array(spacker, arr_size); msgpack_pack_uint8(spacker, entry.data.history_item.histtype); PACK_BIN(cstr_as_string(entry.data.history_item.string)); @@ -1663,9 +1660,7 @@ static ShaDaWriteResult shada_pack_entry(msgpack_packer *const packer, } case kSDItemVariable: { const size_t arr_size = 2 + (size_t) ( - entry.data.global_var.additional_elements == NULL - ? 0 - : entry.data.global_var.additional_elements->lv_len); + tv_list_len(entry.data.global_var.additional_elements)); msgpack_pack_array(spacker, arr_size); const String varname = cstr_as_string(entry.data.global_var.name); PACK_BIN(varname); @@ -1685,9 +1680,7 @@ static ShaDaWriteResult shada_pack_entry(msgpack_packer *const packer, } case kSDItemSubString: { const size_t arr_size = 1 + (size_t) ( - entry.data.sub_string.additional_elements == NULL - ? 0 - : entry.data.sub_string.additional_elements->lv_len); + tv_list_len(entry.data.sub_string.additional_elements)); msgpack_pack_array(spacker, arr_size); PACK_BIN(cstr_as_string(entry.data.sub_string.sub)); DUMP_ADDITIONAL_ELEMENTS(entry.data.sub_string.additional_elements, diff --git a/src/nvim/spell.c b/src/nvim/spell.c index 42c9bcc0ee..6a1b46fb5b 100644 --- a/src/nvim/spell.c +++ b/src/nvim/spell.c @@ -3213,26 +3213,25 @@ spell_find_suggest ( // Find suggestions by evaluating expression "expr". static void spell_suggest_expr(suginfo_T *su, char_u *expr) { - list_T *list; - listitem_T *li; int score; const char *p; // The work is split up in a few parts to avoid having to export // suginfo_T. // First evaluate the expression and get the resulting list. - list = eval_spell_expr(su->su_badword, expr); + list_T *const list = eval_spell_expr(su->su_badword, expr); if (list != NULL) { // Loop over the items in the list. - for (li = list->lv_first; li != NULL; li = li->li_next) - if (li->li_tv.v_type == VAR_LIST) { + TV_LIST_ITER(list, li, { + if (TV_LIST_ITEM_TV(li)->v_type == VAR_LIST) { // Get the word and the score from the items. - score = get_spellword(li->li_tv.vval.v_list, &p); + score = get_spellword(TV_LIST_ITEM_TV(li)->vval.v_list, &p); if (score >= 0 && score <= su->su_maxscore) { add_suggestion(su, &su->su_ga, (const char_u *)p, su->su_badlen, score, 0, true, su->su_sallang, false); } } + }); tv_list_unref(list); } diff --git a/src/nvim/window.c b/src/nvim/window.c index 4e4eb297aa..21d668a2bd 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -5579,30 +5579,26 @@ int match_add(win_T *wp, const char *const grp, const char *const pat, // Set up position matches if (pos_list != NULL) { - linenr_T toplnum = 0; - linenr_T botlnum = 0; - listitem_T *li; - int i; - - for (i = 0, li = pos_list->lv_first; li != NULL && i < MAXPOSMATCH; - i++, li = li->li_next) { - linenr_T lnum = 0; - colnr_T col = 0; - int len = 1; - list_T *subl; - listitem_T *subli; + linenr_T toplnum = 0; + linenr_T botlnum = 0; + + int i = 0; + TV_LIST_ITER(pos_list, li, { + linenr_T lnum = 0; + colnr_T col = 0; + int len = 1; bool error = false; - if (li->li_tv.v_type == VAR_LIST) { - subl = li->li_tv.vval.v_list; + if (TV_LIST_ITEM_TV(li)->v_type == VAR_LIST) { + const list_T *const subl = TV_LIST_ITEM_TV(li)->vval.v_list; if (subl == NULL) { goto fail; } - subli = subl->lv_first; + const listitem_T *subli = tv_list_first(subl); if (subli == NULL) { goto fail; } - lnum = tv_get_number_chk(&subli->li_tv, &error); + lnum = tv_get_number_chk(TV_LIST_ITEM_TV(subli), &error); if (error) { goto fail; } @@ -5611,15 +5607,15 @@ int match_add(win_T *wp, const char *const grp, const char *const pat, continue; } m->pos.pos[i].lnum = lnum; - subli = subli->li_next; + subli = TV_LIST_ITEM_NEXT(subl, subli); if (subli != NULL) { - col = tv_get_number_chk(&subli->li_tv, &error); + col = tv_get_number_chk(TV_LIST_ITEM_TV(subli), &error); if (error) { goto fail; } - subli = subli->li_next; + subli = TV_LIST_ITEM_NEXT(subl, subli); if (subli != NULL) { - len = tv_get_number_chk(&subli->li_tv, &error); + len = tv_get_number_chk(TV_LIST_ITEM_TV(subli), &error); if (error) { goto fail; } @@ -5627,12 +5623,12 @@ int match_add(win_T *wp, const char *const grp, const char *const pat, } m->pos.pos[i].col = col; m->pos.pos[i].len = len; - } else if (li->li_tv.v_type == VAR_NUMBER) { - if (li->li_tv.vval.v_number == 0) { - --i; + } else if (TV_LIST_ITEM_TV(li)->v_type == VAR_NUMBER) { + if (TV_LIST_ITEM_TV(li)->vval.v_number == 0) { + i--; continue; } - m->pos.pos[i].lnum = li->li_tv.vval.v_number; + m->pos.pos[i].lnum = TV_LIST_ITEM_TV(li)->vval.v_number; m->pos.pos[i].col = 0; m->pos.pos[i].len = 0; } else { @@ -5645,7 +5641,11 @@ int match_add(win_T *wp, const char *const grp, const char *const pat, if (botlnum == 0 || lnum >= botlnum) { botlnum = lnum + 1; } - } + i++; + if (i >= MAXPOSMATCH) { + break; + } + }); // Calculate top and bottom lines for redrawing area if (toplnum != 0){ diff --git a/test/functional/eval/null_spec.lua b/test/functional/eval/null_spec.lua index 7452185208..d85f5297d2 100644 --- a/test/functional/eval/null_spec.lua +++ b/test/functional/eval/null_spec.lua @@ -118,6 +118,8 @@ describe('NULL', function() null_expr_test('does not crash setreg', 'setreg("x", L)', 0, 0) null_expr_test('does not crash systemlist()', 'systemlist("cat", L)', 0, {}) null_expr_test('does not crash setline', 'setline(1, L)', 0, 0) + null_test('does not make Neovim crash when v:oldfiles gets assigned to that', ':let v:oldfiles = L|oldfiles', 0) + -- FIXME Add test for complete(, L) end) describe('dict', function() it('does not crash when indexing NULL dict', function() -- cgit From f572bd7e4e15a99cc19244a4411c6a596309f489 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 10 Dec 2017 22:24:11 +0300 Subject: eval,functests: Fix tests and complete() and setline() behaviour --- src/nvim/eval.c | 19 +++++++++---------- test/functional/eval/null_spec.lua | 27 ++++++++++++++++++--------- 2 files changed, 27 insertions(+), 19 deletions(-) diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 2f908f70a9..81dd6434a1 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -7496,7 +7496,7 @@ static void f_complete(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (!undo_allowed()) return; - if (argvars[1].v_type != VAR_LIST || argvars[1].vval.v_list == NULL) { + if (argvars[1].v_type != VAR_LIST) { EMSG(_(e_invarg)); return; } @@ -14540,20 +14540,19 @@ static void f_setline(typval_T *argvars, typval_T *rettv, FunPtr fptr) line = tv_get_string_chk(&argvars[1]); } - /* default result is zero == OK */ + // Default result is zero == OK. for (;; ) { - if (l != NULL) { // List argument, get next string. - if (li == NULL) { - break; - } - line = tv_get_string_chk(TV_LIST_ITEM_TV(li)); - li = TV_LIST_ITEM_NEXT(l, li); + if (li == NULL) { + break; } + line = tv_get_string_chk(TV_LIST_ITEM_TV(li)); + li = TV_LIST_ITEM_NEXT(l, li); - rettv->vval.v_number = 1; /* FAIL */ - if (line == NULL || lnum < 1 || lnum > curbuf->b_ml.ml_line_count + 1) + rettv->vval.v_number = 1; // FAIL + if (line == NULL || lnum < 1 || lnum > curbuf->b_ml.ml_line_count + 1) { break; + } /* When coming here from Insert mode, sync undo, so that this can be * undone separately from what was previously inserted. */ diff --git a/test/functional/eval/null_spec.lua b/test/functional/eval/null_spec.lua index d85f5297d2..3ab9e746a4 100644 --- a/test/functional/eval/null_spec.lua +++ b/test/functional/eval/null_spec.lua @@ -67,6 +67,9 @@ describe('NULL', function() null_expr_test('does not crash append()', 'append(1, L)', 0, 0, function() eq({''}, curbufmeths.get_lines(0, -1, false)) end) + null_expr_test('does not crash setline()', 'setline(1, L)', 0, 0, function() + eq({''}, curbufmeths.get_lines(0, -1, false)) + end) null_expr_test('is identical to itself', 'L is L', 0, 1) null_expr_test('can be sliced', 'L[:]', 0, {}) null_expr_test('can be copied', 'copy(L)', 0, {}) @@ -99,17 +102,20 @@ describe('NULL', function() null_expr_test('makes map() return v:_null_list', 'map(L, "v:val") is# L', 0, 1) null_expr_test('makes filter() return v:_null_list', 'filter(L, "1") is# L', 0, 1) null_test('is treated by :let as empty list', ':let [l] = L', 'Vim(let):E688: More targets than List items') - null_expr_test('is accepted as an empty list by inputlist()', '[feedkeys("\\n"), inputlist(L)]', 0, 0) + null_expr_test('is accepted as an empty list by inputlist()', '[feedkeys("\\n"), inputlist(L)]', + 'Type number and or click with mouse (empty cancels): ', {0, 0}) null_expr_test('is accepted as an empty list by writefile()', ('[writefile(L, "%s"), readfile("%s")]'):format(tmpfname, tmpfname), 0, {0, {}}) - -- FIXME fix test results null_expr_test('does not crash add()', 'add(L, 0)', 0, 1) - null_expr_test('makes insert() error out', 'insert(L, 1)', '', nil) - null_expr_test('does not crash remove()', 'remove(L, 0)', 0, 0) - null_expr_test('makes reverse() error out', 'reverse(L)', '', nil) - null_expr_test('is accepted by sort()', 'sort(L)', 0, 0) - null_expr_test('makes sort() return itself', 'sort(L) is L', 0, 0) + null_expr_test('makes insert() error out', 'insert(L, 1)', + 'E742: Cannot change value of insert() argument', 0) + null_expr_test('does not crash remove()', 'remove(L, 0)', + 'E742: Cannot change value of remove() argument', 0) + null_expr_test('makes reverse() error out', 'reverse(L)', + 'E742: Cannot change value of reverse() argument', 0) + null_expr_test('makes sort() error out', 'sort(L)', + 'E742: Cannot change value of sort() argument', 0) null_expr_test('does not crash extend()', 'extend(L, [1])', 'E742: Cannot change value of extend() argument', 0) null_expr_test('does not crash extend() (second position)', 'extend([1], L)', 0, {1}) null_expr_test('makes join() return empty string', 'join(L, "")', 0, '') @@ -117,9 +123,12 @@ describe('NULL', function() null_expr_test('does not crash system()', 'system("cat", L)', 0, '') null_expr_test('does not crash setreg', 'setreg("x", L)', 0, 0) null_expr_test('does not crash systemlist()', 'systemlist("cat", L)', 0, {}) - null_expr_test('does not crash setline', 'setline(1, L)', 0, 0) null_test('does not make Neovim crash when v:oldfiles gets assigned to that', ':let v:oldfiles = L|oldfiles', 0) - -- FIXME Add test for complete(, L) + null_expr_test('does not make complete() crash or error out', + 'execute(":normal i\\=complete(1, L)[-1]\\n")', + '', '\n', function() + eq({''}, curbufmeths.get_lines(0, -1, false)) + end) end) describe('dict', function() it('does not crash when indexing NULL dict', function() -- cgit From 5008205a3e1eef61396e457e5091eb1341b98278 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 10 Dec 2017 22:28:18 +0300 Subject: eval: Fix setmatches(), setqflist() and setloclist() --- src/nvim/eval.c | 153 ++++++++++++++++++------------------- test/functional/eval/null_spec.lua | 9 +-- 2 files changed, 78 insertions(+), 84 deletions(-) diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 81dd6434a1..c654fe691e 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -14653,8 +14653,8 @@ skip_args: title = (wp ? "setloclist()" : "setqflist()"); } - list_T *l = list_arg->vval.v_list; - if (l && set_errorlist(wp, l, action, (char_u *)title, d) == OK) { + list_T *const l = list_arg->vval.v_list; + if (set_errorlist(wp, l, action, (char_u *)title, d) == OK) { rettv->vval.v_number = 0; } } @@ -14687,92 +14687,89 @@ static void f_setmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) EMSG(_(e_listreq)); return; } - list_T *l; - if ((l = argvars[0].vval.v_list) != NULL) { + list_T *const l = argvars[0].vval.v_list; + // To some extent make sure that we are dealing with a list from + // "getmatches()". + int i = 0; + TV_LIST_ITER_CONST(l, li, { + if (TV_LIST_ITEM_TV(li)->v_type != VAR_DICT + || (d = TV_LIST_ITEM_TV(li)->vval.v_dict) == NULL) { + emsgf(_("E474: List item %d is either not a dictionary " + "or an empty one"), i); + return; + } + if (!(tv_dict_find(d, S_LEN("group")) != NULL + && (tv_dict_find(d, S_LEN("pattern")) != NULL + || tv_dict_find(d, S_LEN("pos1")) != NULL) + && tv_dict_find(d, S_LEN("priority")) != NULL + && tv_dict_find(d, S_LEN("id")) != NULL)) { + emsgf(_("E474: List item %d is missing one of the required keys"), i); + return; + } + i++; + }); - // To some extent make sure that we are dealing with a list from - // "getmatches()". + clear_matches(curwin); + bool match_add_failed = false; + TV_LIST_ITER_CONST(l, li, { int i = 0; - TV_LIST_ITER_CONST(l, li, { - if (TV_LIST_ITEM_TV(li)->v_type != VAR_DICT - || (d = TV_LIST_ITEM_TV(li)->vval.v_dict) == NULL) { - emsgf(_("E474: List item %d is either not a dictionary " - "or an empty one"), i); - return; - } - if (!(tv_dict_find(d, S_LEN("group")) != NULL - && (tv_dict_find(d, S_LEN("pattern")) != NULL - || tv_dict_find(d, S_LEN("pos1")) != NULL) - && tv_dict_find(d, S_LEN("priority")) != NULL - && tv_dict_find(d, S_LEN("id")) != NULL)) { - emsgf(_("E474: List item %d is missing one of the required keys"), i); - return; - } - i++; - }); - clear_matches(curwin); - bool match_add_failed = false; - TV_LIST_ITER_CONST(l, li, { - int i = 0; - - d = TV_LIST_ITEM_TV(li)->vval.v_dict; - dictitem_T *const di = tv_dict_find(d, S_LEN("pattern")); - if (di == NULL) { - if (s == NULL) { - s = tv_list_alloc(); - } - - // match from matchaddpos() - for (i = 1; i < 9; i++) { - char buf[5]; - snprintf(buf, sizeof(buf), "pos%d", i); - dictitem_T *const pos_di = tv_dict_find(d, buf, -1); - if (pos_di != NULL) { - if (pos_di->di_tv.v_type != VAR_LIST) { - return; - } + d = TV_LIST_ITEM_TV(li)->vval.v_dict; + dictitem_T *const di = tv_dict_find(d, S_LEN("pattern")); + if (di == NULL) { + if (s == NULL) { + s = tv_list_alloc(); + } - tv_list_append_tv(s, &pos_di->di_tv); - tv_list_ref(s); - } else { - break; + // match from matchaddpos() + for (i = 1; i < 9; i++) { + char buf[5]; + snprintf(buf, sizeof(buf), "pos%d", i); + dictitem_T *const pos_di = tv_dict_find(d, buf, -1); + if (pos_di != NULL) { + if (pos_di->di_tv.v_type != VAR_LIST) { + return; } + + tv_list_append_tv(s, &pos_di->di_tv); + tv_list_ref(s); + } else { + break; } } + } - // Note: there are three number buffers involved: - // - group_buf below. - // - numbuf in tv_dict_get_string(). - // - mybuf in tv_get_string(). - // - // If you change this code make sure that buffers will not get - // accidentally reused. - char group_buf[NUMBUFLEN]; - const char *const group = tv_dict_get_string_buf(d, "group", group_buf); - const int priority = (int)tv_dict_get_number(d, "priority"); - const int id = (int)tv_dict_get_number(d, "id"); - dictitem_T *const conceal_di = tv_dict_find(d, S_LEN("conceal")); - const char *const conceal = (conceal_di != NULL - ? tv_get_string(&conceal_di->di_tv) - : NULL); - if (i == 0) { - if (match_add(curwin, group, - tv_dict_get_string(d, "pattern", false), - priority, id, NULL, conceal) != id) { - match_add_failed = true; - } - } else { - if (match_add(curwin, group, NULL, priority, id, s, conceal) != id) { - match_add_failed = true; - } - tv_list_unref(s); - s = NULL; + // Note: there are three number buffers involved: + // - group_buf below. + // - numbuf in tv_dict_get_string(). + // - mybuf in tv_get_string(). + // + // If you change this code make sure that buffers will not get + // accidentally reused. + char group_buf[NUMBUFLEN]; + const char *const group = tv_dict_get_string_buf(d, "group", group_buf); + const int priority = (int)tv_dict_get_number(d, "priority"); + const int id = (int)tv_dict_get_number(d, "id"); + dictitem_T *const conceal_di = tv_dict_find(d, S_LEN("conceal")); + const char *const conceal = (conceal_di != NULL + ? tv_get_string(&conceal_di->di_tv) + : NULL); + if (i == 0) { + if (match_add(curwin, group, + tv_dict_get_string(d, "pattern", false), + priority, id, NULL, conceal) != id) { + match_add_failed = true; } - }); - if (!match_add_failed) { - rettv->vval.v_number = 0; + } else { + if (match_add(curwin, group, NULL, priority, id, s, conceal) != id) { + match_add_failed = true; + } + tv_list_unref(s); + s = NULL; } + }); + if (!match_add_failed) { + rettv->vval.v_number = 0; } } diff --git a/test/functional/eval/null_spec.lua b/test/functional/eval/null_spec.lua index 3ab9e746a4..30c6d3d450 100644 --- a/test/functional/eval/null_spec.lua +++ b/test/functional/eval/null_spec.lua @@ -42,12 +42,6 @@ describe('NULL', function() describe('list', function() -- Incorrect behaviour - -- FIXME should return 0 - null_expr_test('is accepted by setqflist()', 'setqflist(L)', 0, -1) - -- FIXME should return 0 - null_expr_test('is accepted by setloclist()', 'setloclist(1, L)', 0, -1) - -- FIXME should return 0 - null_expr_test('is accepted by setmatches()', 'setmatches(L)', 0, -1) -- FIXME should not error out null_test('is accepted by :cexpr', 'cexpr L', 'Vim(cexpr):E777: String or List expected') -- FIXME should not error out @@ -129,6 +123,9 @@ describe('NULL', function() '', '\n', function() eq({''}, curbufmeths.get_lines(0, -1, false)) end) + null_expr_test('is accepted by setmatches()', 'setmatches(L)', 0, 0) + null_expr_test('is accepted by setqflist()', 'setqflist(L)', 0, 0) + null_expr_test('is accepted by setloclist()', 'setloclist(1, L)', 0, 0) end) describe('dict', function() it('does not crash when indexing NULL dict', function() -- cgit From 83f77c80c084a0390b5a6efd364b33620c9f3d10 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 10 Dec 2017 22:33:05 +0300 Subject: quickfix: Fix :cexpr and :lexpr --- src/nvim/quickfix.c | 2 +- test/functional/eval/null_spec.lua | 11 +++-------- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 56d11c21ab..a4dc9c68ce 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -4403,7 +4403,7 @@ void ex_cexpr(exarg_T *eap) typval_T tv; if (eval0(eap->arg, &tv, NULL, true) != FAIL) { if ((tv.v_type == VAR_STRING && tv.vval.v_string != NULL) - || (tv.v_type == VAR_LIST && tv.vval.v_list != NULL)) { + || tv.v_type == VAR_LIST) { if (qf_init_ext(qi, NULL, NULL, &tv, p_efm, (eap->cmdidx != CMD_caddexpr && eap->cmdidx != CMD_laddexpr), diff --git a/test/functional/eval/null_spec.lua b/test/functional/eval/null_spec.lua index 30c6d3d450..3f3217649b 100644 --- a/test/functional/eval/null_spec.lua +++ b/test/functional/eval/null_spec.lua @@ -40,14 +40,6 @@ describe('NULL', function() end) end describe('list', function() - -- Incorrect behaviour - - -- FIXME should not error out - null_test('is accepted by :cexpr', 'cexpr L', 'Vim(cexpr):E777: String or List expected') - -- FIXME should not error out - null_test('is accepted by :lexpr', 'lexpr L', 'Vim(lexpr):E777: String or List expected') - null_test('is accepted by :for', 'for x in L|throw x|endfor', 0) - -- Subjectable behaviour -- FIXME Should return 1 @@ -58,6 +50,7 @@ describe('NULL', function() null_expr_test('is not locked', 'islocked("v:_null_list")', 0, 0) -- Correct behaviour + null_test('is accepted by :for', 'for x in L|throw x|endfor', 0) null_expr_test('does not crash append()', 'append(1, L)', 0, 0, function() eq({''}, curbufmeths.get_lines(0, -1, false)) end) @@ -126,6 +119,8 @@ describe('NULL', function() null_expr_test('is accepted by setmatches()', 'setmatches(L)', 0, 0) null_expr_test('is accepted by setqflist()', 'setqflist(L)', 0, 0) null_expr_test('is accepted by setloclist()', 'setloclist(1, L)', 0, 0) + null_test('is accepted by :cexpr', 'cexpr L', 0) + null_test('is accepted by :lexpr', 'lexpr L', 0) end) describe('dict', function() it('does not crash when indexing NULL dict', function() -- cgit From 0b03ac2cb2399a850bb71d8c0a76d0893bf503cb Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 10 Dec 2017 22:34:32 +0300 Subject: functests: Mark islocked("v:_null_list") behaviour correct It is the same for other VAR_FIXED lists. --- test/functional/eval/null_spec.lua | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/functional/eval/null_spec.lua b/test/functional/eval/null_spec.lua index 3f3217649b..1c1dd6fd79 100644 --- a/test/functional/eval/null_spec.lua +++ b/test/functional/eval/null_spec.lua @@ -46,10 +46,9 @@ describe('NULL', function() null_expr_test('is equal to empty list', 'L == []', 0, 0) -- FIXME Should return 1 null_expr_test('is equal to empty list (reverse order)', '[] == L', 0, 0) - -- FIXME Should return 1 - null_expr_test('is not locked', 'islocked("v:_null_list")', 0, 0) -- Correct behaviour + null_expr_test('is not locked', 'islocked("v:_null_list")', 0, 0) null_test('is accepted by :for', 'for x in L|throw x|endfor', 0) null_expr_test('does not crash append()', 'append(1, L)', 0, 0, function() eq({''}, curbufmeths.get_lines(0, -1, false)) -- cgit From 7572d5ac5a09c18651843c026ebc92bffafe348f Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 10 Dec 2017 22:41:00 +0300 Subject: eval/encode: Fix crash in json_encode test suite --- src/nvim/eval/encode.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/nvim/eval/encode.c b/src/nvim/eval/encode.c index 0e5db84ad5..50ddb1f38c 100644 --- a/src/nvim/eval/encode.c +++ b/src/nvim/eval/encode.c @@ -136,8 +136,10 @@ static int conv_error(const char *const msg, const MPConvStack *const mpstack, } case kMPConvPairs: case kMPConvList: { - const listitem_T *const li = TV_LIST_ITEM_PREV(v.data.l.list, - v.data.l.li); + const listitem_T *const li = (v.data.l.li == NULL + ? tv_list_last(v.data.l.list) + : TV_LIST_ITEM_PREV(v.data.l.list, + v.data.l.li)); int idx = (int)tv_list_idx_of_item(v.data.l.list, li); if (v.type == kMPConvList || li == NULL -- cgit From d11884db497114bc8ac5e33964ed81165f8a50fe Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 10 Dec 2017 23:02:19 +0300 Subject: eval: Fix uniq() crash in legacy test 055 --- src/nvim/eval.c | 8 ++++++-- test/functional/eval/null_spec.lua | 2 ++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/nvim/eval.c b/src/nvim/eval.c index c654fe691e..064f2c072c 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -15232,6 +15232,8 @@ item_compare_end: // When the result would be zero, compare the item indexes. Makes the // sort stable. if (res == 0 && !keep_zero) { + // WARNING: When using uniq si1 and si2 are actually listitem_T **, no + // indexes are there. res = si1->idx > si2->idx ? 1 : -1; } return res; @@ -15297,6 +15299,8 @@ static int item_compare2(const void *s1, const void *s2, bool keep_zero) // When the result would be zero, compare the pointers themselves. Makes // the sort stable. if (res == 0 && !keep_zero) { + // WARNING: When using uniq si1 and si2 are actually listitem_T **, no + // indexes are there. res = si1->idx > si2->idx ? 1 : -1; } @@ -15337,7 +15341,7 @@ static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort) } else { list_T *const l = argvars[0].vval.v_list; if (tv_check_lock(tv_list_locked(l), arg_errmsg, TV_TRANSLATE)) { - goto theend; + goto theend; } rettv->vval.v_list = l; rettv->v_type = VAR_LIST; @@ -15462,7 +15466,7 @@ static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort) listitem_T *prev_li = NULL; TV_LIST_ITER(l, li, { if (prev_li != NULL) { - if (item_compare_func_ptr(prev_li, li) == 0) { + if (item_compare_func_ptr(&prev_li, &li) == 0) { ptrs[i++].item = prev_li; } if (info.item_compare_func_err) { diff --git a/test/functional/eval/null_spec.lua b/test/functional/eval/null_spec.lua index 1c1dd6fd79..64be8fcd11 100644 --- a/test/functional/eval/null_spec.lua +++ b/test/functional/eval/null_spec.lua @@ -102,6 +102,8 @@ describe('NULL', function() 'E742: Cannot change value of reverse() argument', 0) null_expr_test('makes sort() error out', 'sort(L)', 'E742: Cannot change value of sort() argument', 0) + null_expr_test('makes uniq() error out', 'uniq(L)', + 'E742: Cannot change value of uniq() argument', 0) null_expr_test('does not crash extend()', 'extend(L, [1])', 'E742: Cannot change value of extend() argument', 0) null_expr_test('does not crash extend() (second position)', 'extend([1], L)', 0, {1}) null_expr_test('makes join() return empty string', 'join(L, "")', 0, '') -- cgit From 622d355ab46b4d9282de9db6f25701882dd8f4ab Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 10 Dec 2017 23:16:00 +0300 Subject: functests: Add some more NULL tests --- test/functional/eval/null_spec.lua | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/functional/eval/null_spec.lua b/test/functional/eval/null_spec.lua index 64be8fcd11..14b2d964d5 100644 --- a/test/functional/eval/null_spec.lua +++ b/test/functional/eval/null_spec.lua @@ -40,6 +40,11 @@ describe('NULL', function() end) end describe('list', function() + -- Incorrect behaviour + -- FIXME Should error out with different message + null_test('makes :unlet act as if it is not a list', ':unlet L[0]', + 'Vim(unlet):E689: Can only index a List or Dictionary') + -- Subjectable behaviour -- FIXME Should return 1 @@ -48,6 +53,9 @@ describe('NULL', function() null_expr_test('is equal to empty list (reverse order)', '[] == L', 0, 0) -- Correct behaviour + null_expr_test('can be indexed with error message for empty list', 'L[0]', + 'E684: list index out of range: 0\nE15: Invalid expression: L[0]', nil) + null_expr_test('can be splice-indexed', 'L[:]', 0, {}) null_expr_test('is not locked', 'islocked("v:_null_list")', 0, 0) null_test('is accepted by :for', 'for x in L|throw x|endfor', 0) null_expr_test('does not crash append()', 'append(1, L)', 0, 0, function() -- cgit From fe55f37083b0bef07aa9ac78eb2727c244fdafd3 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 10 Dec 2017 23:18:24 +0300 Subject: eval: Still check for NULL when doing :unlet --- src/nvim/eval.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 064f2c072c..6271603a1d 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -2875,9 +2875,12 @@ static int do_unlet_var(lval_T *const lp, char_u *const name_end, int forceit) ret = FAIL; } *name_end = cc; - } else if (tv_check_lock(tv_list_locked(lp->ll_list), - (const char *)lp->ll_name, - lp->ll_name_len) + } else if ((lp->ll_list != NULL + // ll_list is not NULL when lvalue is not in a list, NULL lists + // yield E689. + && tv_check_lock(tv_list_locked(lp->ll_list), + (const char *)lp->ll_name, + lp->ll_name_len)) || (lp->ll_dict != NULL && tv_check_lock(lp->ll_dict->dv_lock, (const char *)lp->ll_name, -- cgit From ceb45a08858837319c8ea67b1aaeceaeb24c8510 Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 11 Dec 2017 01:43:36 +0300 Subject: *: Fix test failures --- src/nvim/eval.c | 10 ++++++---- src/nvim/eval/encode.c | 4 +++- src/nvim/ex_getln.c | 1 + test/functional/eval/msgpack_functions_spec.lua | 2 +- test/functional/legacy/063_match_and_matchadd_spec.lua | 6 ++++-- 5 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 6271603a1d..b45cfcb427 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -14545,12 +14545,14 @@ static void f_setline(typval_T *argvars, typval_T *rettv, FunPtr fptr) // Default result is zero == OK. for (;; ) { + if (argvars[1].v_type == VAR_LIST) { // List argument, get next string. - if (li == NULL) { - break; + if (li == NULL) { + break; + } + line = tv_get_string_chk(TV_LIST_ITEM_TV(li)); + li = TV_LIST_ITEM_NEXT(l, li); } - line = tv_get_string_chk(TV_LIST_ITEM_TV(li)); - li = TV_LIST_ITEM_NEXT(l, li); rettv->vval.v_number = 1; // FAIL if (line == NULL || lnum < 1 || lnum > curbuf->b_ml.ml_line_count + 1) { diff --git a/src/nvim/eval/encode.c b/src/nvim/eval/encode.c index 50ddb1f38c..85fd4d1578 100644 --- a/src/nvim/eval/encode.c +++ b/src/nvim/eval/encode.c @@ -140,7 +140,9 @@ static int conv_error(const char *const msg, const MPConvStack *const mpstack, ? tv_list_last(v.data.l.list) : TV_LIST_ITEM_PREV(v.data.l.list, v.data.l.li)); - int idx = (int)tv_list_idx_of_item(v.data.l.list, li); + int idx = (li == NULL + ? 0 + : (int)tv_list_idx_of_item(v.data.l.list, li)); if (v.type == kMPConvList || li == NULL || (TV_LIST_ITEM_TV(li)->v_type != VAR_LIST diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index fcd230d535..a5a8804e1c 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -2680,6 +2680,7 @@ static bool color_cmdline(CmdlineInfo *colored_ccline) .end = end, .attr = attr, })); + i++; }); if (prev_end < colored_ccline->cmdlen) { kv_push(ccline_colors->colors, ((CmdlineColorChunk) { diff --git a/test/functional/eval/msgpack_functions_spec.lua b/test/functional/eval/msgpack_functions_spec.lua index b241635dfe..258d6ee059 100644 --- a/test/functional/eval/msgpack_functions_spec.lua +++ b/test/functional/eval/msgpack_functions_spec.lua @@ -628,7 +628,7 @@ describe('msgpackdump() function', function() it('fails to dump a recursive (key) map in a special dict', function() command('let todump = {"_TYPE": v:msgpack_types.map, "_VAL": []}') command('call add(todump._VAL, [todump, 0])') - eq('Vim(call):E5005: Unable to dump msgpackdump() argument, index 0: container references itself in index 1', + eq('Vim(call):E5005: Unable to dump msgpackdump() argument, index 0: container references itself in index 0', exc_exec('call msgpackdump([todump])')) end) diff --git a/test/functional/legacy/063_match_and_matchadd_spec.lua b/test/functional/legacy/063_match_and_matchadd_spec.lua index a505a2db30..518d79861b 100644 --- a/test/functional/legacy/063_match_and_matchadd_spec.lua +++ b/test/functional/legacy/063_match_and_matchadd_spec.lua @@ -114,9 +114,11 @@ describe('063: Test for ":match", "matchadd()" and related functions', function( command("call clearmatches()") eq('\nE714: List required', redir_exec("let rf1 = setmatches(0)")) eq(-1, eval('rf1')) - eq('\nE474: Invalid argument', redir_exec("let rf2 = setmatches([0])")) + eq('\nE474: List item 0 is either not a dictionary or an empty one', + redir_exec("let rf2 = setmatches([0])")) eq(-1, eval('rf2')) - eq('\nE474: Invalid argument', redir_exec("let rf3 = setmatches([{'wrong key': 'wrong value'}])")) + eq('\nE474: List item 0 is missing one of the required keys', + redir_exec("let rf3 = setmatches([{'wrong key': 'wrong value'}])")) eq(-1, eval('rf3')) -- Check that "matchaddpos()" positions matches correctly -- cgit From 56b49955b70aa803f9fb42e48a147f1881ecb33c Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Mon, 11 Dec 2017 00:49:44 +0100 Subject: vim-patch.sh: introduce `-L` --- scripts/vim-patch.sh | 35 +++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/scripts/vim-patch.sh b/scripts/vim-patch.sh index 5c24ede13f..bac5657cc6 100755 --- a/scripts/vim-patch.sh +++ b/scripts/vim-patch.sh @@ -21,7 +21,8 @@ usage() { echo echo "Options:" echo " -h Show this message and exit." - echo " -l Show list of Vim patches missing from Neovim." + echo " -l Show list of missing Vim patches." + echo " -L Print missing Vim patches in machine-readable form." echo " -p {vim-revision} Download and generate the specified Vim patch." echo " vim-revision can be a version number '8.0.xxx'" echo " or a valid Git ref (hash, tag, etc.)." @@ -318,11 +319,8 @@ submit_pr() { done } +# Prints a newline-delimited list of Vim commits, for use by scripts. list_vim_patches() { - get_vim_sources - - printf "\nVim patches missing from Neovim:\n" - # Get missing Vim commits local vim_commits vim_commits="$(cd "${VIM_SOURCE_DIR}" && git log --reverse --format='%H' v8.0.0000..HEAD)" @@ -343,18 +341,27 @@ list_vim_patches() { local patch_number="${vim_tag:1}" # "v7.4.0001" => "7.4.0001" is_missing="$(echo "$tokens" | >/dev/null 2>&1 grep "vim\-patch:${patch_number}" && echo false || echo true)" vim_commit="${vim_tag#v}" - if ! [ "$is_missing" = "false" ] ; then - if (cd "${VIM_SOURCE_DIR}" && git --no-pager show --color=never --name-only "v${vim_commit}" 2>/dev/null) | grep -q ^runtime; then - vim_commit="${vim_commit} (+runtime)" - fi - fi else # Untagged Vim patch (e.g. runtime updates). is_missing="$(echo "$tokens" | >/dev/null 2>&1 grep "vim\-patch:${vim_commit:0:7}" && echo false || echo true)" fi if ! [ "$is_missing" = "false" ]; then - echo " • ${vim_commit}" + echo "${vim_commit}" + fi + done +} + +# Prints a human-formatted list of Vim commits, with instructional messages. +show_vim_patches() { + get_vim_sources + printf "\nVim patches missing from Neovim:\n" + + list_vim_patches | while read vim_commit; do + if (cd "${VIM_SOURCE_DIR}" && git --no-pager show --color=never --name-only "v${vim_commit}" 2>/dev/null) | grep -q ^runtime; then + printf " • ${vim_commit} (+runtime)\n" + else + printf " • ${vim_commit}\n" fi done @@ -465,13 +472,17 @@ review_pr() { clean_files } -while getopts "hlp:P:g:r:s" opt; do +while getopts "hlLp:P:g:r:s" opt; do case ${opt} in h) usage exit 0 ;; l) + show_vim_patches + exit 0 + ;; + L) list_vim_patches exit 0 ;; -- cgit From 23fb833ea75107a3349dfb0be2eeccc06111399c Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Mon, 11 Dec 2017 00:54:25 +0100 Subject: vim-patch.sh: remove version.c in generated patch Vim patch tracking is now driven completely by `vim-patch:xxx` tokens in the VCS logs. version.c will be auto-generated, if it is used at all. --- scripts/vim-patch.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/vim-patch.sh b/scripts/vim-patch.sh index bac5657cc6..04bc16dd25 100755 --- a/scripts/vim-patch.sh +++ b/scripts/vim-patch.sh @@ -148,6 +148,10 @@ preprocess_patch() { local na_src_testdir='Make_amiga.mak\|Make_dos.mak\|Make_ming.mak\|Make_vms.mms' 2>/dev/null $nvim --cmd 'set dir=/tmp' +'g@^diff --git a/src/testdir/\<\%('${na_src_testdir}'\)\>@norm! d/\v(^diff)|%$ ' +w +q "$file" + # Remove version.c #7555 + local na_po='version.c' + 2>/dev/null $nvim --cmd 'set dir=/tmp' +'g@^diff --git a/src/\<\%('${na_po}'\)\>@norm! d/\v(^diff)|%$ ' +w +q "$file" + # Remove some *.po files. #5622 local na_po='sjiscorr.c\|ja.sjis.po\|ko.po\|pl.cp1250.po\|pl.po\|ru.cp1251.po\|uk.cp1251.po\|zh_CN.cp936.po\|zh_CN.po\|zh_TW.po' 2>/dev/null $nvim --cmd 'set dir=/tmp' +'g@^diff --git a/src/po/\<\%('${na_po}'\)\>@norm! d/\v(^diff)|%$ ' +w +q "$file" -- cgit From d46e37cb4c71f39312233799d7f28eb86dceef60 Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 11 Dec 2017 10:12:59 +0300 Subject: *: Finish hiding list implementation --- src/nvim/eval.c | 104 +++++++--------------------------------- src/nvim/eval/typval.c | 90 +++++++++++++++++++++++++++++++++- src/nvim/eval/typval.h | 75 ++++++++++++++++++++++++++++- src/nvim/eval/typval_encode.c.h | 10 ++-- src/nvim/regexp.c | 8 ++-- 5 files changed, 187 insertions(+), 100 deletions(-) diff --git a/src/nvim/eval.c b/src/nvim/eval.c index b45cfcb427..ad8b575867 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -110,9 +110,6 @@ #define DICT_MAXNEST 100 /* maximum nesting of lists and dicts */ -#define DO_NOT_FREE_CNT 99999 /* refcount for dict or list that should not - be freed. */ - #define AUTOLOAD_CHAR '#' /* Character used as separator in autoload function/variable names. */ @@ -5271,7 +5268,6 @@ static int free_unref_items(int copyID) for (ll = gc_first_list; ll != NULL; ll = ll_next) { ll_next = ll->lv_used_next; if ((ll->lv_copyID & COPYID_MASK) != (copyID & COPYID_MASK) - // FIXME: Abstract away lv_watch. && ll->lv_watch == NULL) { // Free the List and ordinary items it contains, but don't recurse // into Lists and Dictionaries, they will be in the list of dicts @@ -13313,14 +13309,7 @@ static void f_remove(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (li == NULL) { // Didn't find "item2" after "item". emsgf(_(e_invrange)); } else { - tv_list_remove_items(l, item, item2); - // FIXME: Abstract the below away or move to eval/typval. - l = tv_list_alloc_ret(rettv); - l->lv_first = item; - l->lv_last = item2; - item->li_prev = NULL; - item2->li_next = NULL; - l->lv_len = cnt; + tv_list_move_items(l, item, item2, tv_list_alloc_ret(rettv), cnt); } } } @@ -13558,19 +13547,10 @@ static void f_reverse(typval_T *argvars, typval_T *rettv, FunPtr fptr) EMSG2(_(e_listarg), "reverse()"); } else if (!tv_check_lock(tv_list_locked((l = argvars[0].vval.v_list)), N_("reverse() argument"), TV_TRANSLATE)) { - // FIXME: Abstract the below away or move to eval/typval. - listitem_T *li = l->lv_last; - l->lv_first = l->lv_last = NULL; - l->lv_len = 0; - while (li != NULL) { - listitem_T *const ni = li->li_prev; - tv_list_append(l, li); - li = ni; - } + tv_list_reverse(l); rettv->vval.v_list = l; rettv->v_type = VAR_LIST; tv_list_ref(l); - l->lv_idx = l->lv_len - l->lv_idx - 1; } } @@ -15445,12 +15425,7 @@ static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort) if (!info.item_compare_func_err) { // Clear the list and append the items in the sorted order. - // FIXME: Somehow abstract away or move to eval/typval. - l->lv_first = NULL; - l->lv_last = NULL; - l->lv_idx_item = NULL; - l->lv_len = 0; - + tv_list_clear(l); for (i = 0; i < len; i++) { tv_list_append(l, ptrs[i].item); } @@ -15468,34 +15443,20 @@ static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort) item_compare_func_ptr = item_compare_keeping_zero; } - listitem_T *prev_li = NULL; - TV_LIST_ITER(l, li, { - if (prev_li != NULL) { - if (item_compare_func_ptr(&prev_li, &li) == 0) { - ptrs[i++].item = prev_li; - } + int idx = 0; + for (listitem_T *li = TV_LIST_ITEM_NEXT(l, tv_list_first(l)) + ; li != NULL + ; li = TV_LIST_ITEM_NEXT(l, li)) { + listitem_T *const prev_li = TV_LIST_ITEM_PREV(l, li); + if (item_compare_func_ptr(&prev_li, &li) == 0) { if (info.item_compare_func_err) { EMSG(_("E882: Uniq compare function failed")); break; } - } - prev_li = li; - }); - - if (!info.item_compare_func_err) { - while (--i >= 0) { - // FIXME: Abstract away. - assert(ptrs[i].item->li_next); - listitem_T *const li = ptrs[i].item->li_next; - ptrs[i].item->li_next = li->li_next; - if (li->li_next != NULL) { - li->li_next->li_prev = ptrs[i].item; - } else { - l->lv_last = ptrs[i].item; - } - tv_list_watch_fix(l, li); - tv_list_item_free(li); - l->lv_len--; + tv_list_item_remove(l, li); + li = tv_list_find(l, idx); + } else { + idx++; } } } @@ -17533,35 +17494,6 @@ write_list_error: return false; } -/// Initializes a static list with 10 items. -void init_static_list(staticList10_T *sl) -{ - // FIXME: Move to eval/typval. - list_T *l = &sl->sl_list; - - memset(sl, 0, sizeof(staticList10_T)); - l->lv_first = &sl->sl_items[0]; - l->lv_last = &sl->sl_items[9]; - l->lv_refcount = DO_NOT_FREE_CNT; - tv_list_set_lock(l, VAR_FIXED); - sl->sl_list.lv_len = 10; - - for (int i = 0; i < 10; i++) { - listitem_T *li = &sl->sl_items[i]; - - if (i == 0) { - li->li_prev = NULL; - } else { - li->li_prev = li - 1; - } - if (i == 9) { - li->li_next = NULL; - } else { - li->li_next = li + 1; - } - } -} - /// Saves a typval_T as a string. /// /// For lists, replaces NLs with NUL and separates items with NLs. @@ -19305,10 +19237,9 @@ int var_item_copy(const vimconv_T *const conv, to->v_lock = 0; if (from->vval.v_list == NULL) to->vval.v_list = NULL; - else if (copyID != 0 && from->vval.v_list->lv_copyID == copyID) { - // FIXME: Abstract away. + else if (copyID != 0 && tv_list_copyid(from->vval.v_list) == copyID) { // Use the copy made earlier. - to->vval.v_list = from->vval.v_list->lv_copylist; + to->vval.v_list = tv_list_latest_copy(from->vval.v_list); tv_list_ref(to->vval.v_list); } else { to->vval.v_list = tv_list_copy(conv, from->vval.v_list, deep, copyID); @@ -21190,9 +21121,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, v->di_tv.v_type = VAR_LIST; v->di_tv.v_lock = VAR_FIXED; v->di_tv.vval.v_list = &fc->l_varlist; - // FIXME: Abstract away static list. - memset(&fc->l_varlist, 0, sizeof(list_T)); - fc->l_varlist.lv_refcount = DO_NOT_FREE_CNT; + tv_list_init_static(&fc->l_varlist); tv_list_set_lock(&fc->l_varlist, VAR_FIXED); // Set a:firstline to "firstline" and a:lastline to "lastline". @@ -21474,7 +21403,6 @@ static void funccal_unref(funccall_T *fc, ufunc_T *fp, bool force) return; } - // FIXME: Abstract away static list implementation details. if (--fc->fc_refcount <= 0 && (force || ( fc->l_varlist.lv_refcount == DO_NOT_FREE_CNT && fc->l_vars.dv_refcount == DO_NOT_FREE_CNT diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index eaf70b41fd..df81c0450d 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -153,6 +153,45 @@ list_T *tv_list_alloc(void) return list; } +/// Initialize a static list with 10 items +/// +/// @param[out] sl Static list to initialize. +void tv_list_init_static10(staticList10_T *const sl) + FUNC_ATTR_NONNULL_ALL +{ +#define SL_SIZE ARRAY_SIZE(sl->sl_items) + list_T *const l = &sl->sl_list; + + memset(sl, 0, sizeof(staticList10_T)); + l->lv_first = &sl->sl_items[0]; + l->lv_last = &sl->sl_items[SL_SIZE - 1]; + l->lv_refcount = DO_NOT_FREE_CNT; + tv_list_set_lock(l, VAR_FIXED); + sl->sl_list.lv_len = 10; + + sl->sl_items[0].li_prev = NULL; + sl->sl_items[0].li_next = &sl->sl_items[1]; + sl->sl_items[SL_SIZE - 1].li_prev = &sl->sl_items[SL_SIZE - 2]; + sl->sl_items[SL_SIZE - 1].li_next = NULL; + + for (size_t i = 1; i < SL_SIZE - 1; i++) { + listitem_T *const li = &sl->sl_items[i]; + li->li_prev = li - 1; + li->li_next = li + 1; + } +#undef SL_SIZE +} + +/// Initialize static list with undefined number of elements +/// +/// @param[out] l List to initialize. +void tv_list_init_static(list_T *const l) + FUNC_ATTR_NONNULL_ALL +{ + memset(l, 0, sizeof(*l)); + l->lv_refcount = DO_NOT_FREE_CNT; +} + /// Free items contained in a list /// /// @param[in,out] l List to clear. @@ -221,7 +260,7 @@ void tv_list_unref(list_T *const l) //{{{2 Add/remove -/// Remove items "item" to "item2" from list "l". +/// Remove items "item" to "item2" from list "l" /// /// @warning Does not free the listitem or the value! /// @@ -251,6 +290,30 @@ void tv_list_remove_items(list_T *const l, listitem_T *const item, l->lv_idx_item = NULL; } +/// Move items "item" to "item2" from list "l" to the end of the list "tgt_l" +/// +/// @param[out] l List to move from. +/// @param[in] item First item to move. +/// @param[in] item2 Last item to move. +/// @param[out] tgt_l List to move to. +/// @param[in] cnt Number of items moved. +void tv_list_move_items(list_T *const l, listitem_T *const item, + listitem_T *const item2, list_T *const tgt_l, + const int cnt) + FUNC_ATTR_NONNULL_ALL +{ + tv_list_remove_items(l, item, item2); + item->li_prev = tgt_l->lv_last; + item2->li_next = NULL; + if (tgt_l->lv_last == NULL) { + tgt_l->lv_first = item; + } else { + tgt_l->lv_last->li_next = item; + } + tgt_l->lv_last = item2; + tgt_l->lv_len += cnt; +} + /// Insert list item /// /// @param[out] l List to insert to. @@ -644,6 +707,31 @@ bool tv_list_equal(list_T *const l1, list_T *const l2, const bool ic, return true; } +/// Reverse list in-place +/// +/// @param[in,out] l List to reverse. +void tv_list_reverse(list_T *const l) +{ + if (tv_list_len(l) <= 1) { + return; + } +#define SWAP(a, b) \ + do { \ + tmp = a; \ + a = b; \ + b = tmp; \ + } while (0) + listitem_T *tmp; + + SWAP(l->lv_first, l->lv_last); + for (listitem_T *li = l->lv_first; li != NULL; li = li->li_next) { + SWAP(li->li_next, li->li_prev); + } +#undef SWAP + + l->lv_idx = l->lv_len - l->lv_idx - 1; +} + //{{{2 Indexing/searching /// Locate item with a given index in a list and return it diff --git a/src/nvim/eval/typval.h b/src/nvim/eval/typval.h index a05abd8a53..a7255fdba0 100644 --- a/src/nvim/eval/typval.h +++ b/src/nvim/eval/typval.h @@ -7,6 +7,7 @@ #include #include #include +#include #include "nvim/types.h" #include "nvim/hashtab.h" @@ -27,6 +28,9 @@ typedef uint64_t uvarnumber_T; /// Type used for VimL VAR_FLOAT values typedef double float_T; +/// Refcount for dict or list that should not be freed +enum { DO_NOT_FREE_CNT = (INT_MAX / 2) }; + /// Maximal possible value of varnumber_T variable #define VARNUMBER_MAX INT64_MAX #define UVARNUMBER_MAX UINT64_MAX @@ -151,12 +155,26 @@ struct listvar_S { list_T *lv_used_prev; ///< Previous list in used lists list. }; -// Static list with 10 items. Use init_static_list() to initialize. +// Static list with 10 items. Use tv_list_init_static10() to initialize. typedef struct { list_T sl_list; // must be first listitem_T sl_items[10]; } staticList10_T; +#define TV_LIST_STATIC10_INIT { \ + .sl_list = { \ + .lv_first = NULL, \ + .lv_last = NULL, \ + .lv_refcount = 0, \ + .lv_len = 0, \ + .lv_watch = NULL, \ + .lv_idx_item = NULL, \ + .lv_lock = VAR_FIXED, \ + .lv_used_next = NULL, \ + .lv_used_prev = NULL, \ + }, \ + } + // Structure to hold an item of a Dictionary. // Also used for a variable. // The key is copied into "di_key" to avoid an extra alloc/free for it. @@ -330,6 +348,19 @@ static inline void tv_list_set_lock(list_T *const l, l->lv_lock = lock; } +/// Set list copyID +/// +/// Does not expect NULL list, be careful. +/// +/// @param[out] l List to modify. +/// @param[in] copyid New copyID. +static inline void tv_list_set_copyid(list_T *const l, + const int copyid) + FUNC_ATTR_NONNULL_ALL +{ + l->lv_copyID = copyid; +} + static inline int tv_list_len(const list_T *const l) REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; @@ -344,6 +375,48 @@ static inline int tv_list_len(const list_T *const l) return l->lv_len; } +static inline int tv_list_copyid(const list_T *const l) + REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT REAL_FATTR_NONNULL_ALL; + +/// Get list copyID +/// +/// Does not expect NULL list, be careful. +/// +/// @param[in] l List to check. +static inline int tv_list_copyid(const list_T *const l) +{ + return l->lv_copyID; +} + +static inline list_T *tv_list_latest_copy(const list_T *const l) + REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT REAL_FATTR_NONNULL_ALL; + +/// Get latest list copy +/// +/// Gets lv_copylist field assigned by tv_list_copy() earlier. +/// +/// Does not expect NULL list, be careful. +/// +/// @param[in] l List to check. +static inline list_T *tv_list_latest_copy(const list_T *const l) +{ + return l->lv_copylist; +} + +/// Clear the list without freeing anything at all +/// +/// For use in sort() which saves items to a separate array and readds them back +/// after sorting via a number of tv_list_append() calls. +/// +/// @param[out] l List to clear. +static inline void tv_list_clear(list_T *const l) +{ + l->lv_first = NULL; + l->lv_last = NULL; + l->lv_idx_item = NULL; + l->lv_len = 0; +} + static inline int tv_list_uidx(const list_T *const l, int n) REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; diff --git a/src/nvim/eval/typval_encode.c.h b/src/nvim/eval/typval_encode.c.h index 800760cf7b..e4ed2bc636 100644 --- a/src/nvim/eval/typval_encode.c.h +++ b/src/nvim/eval/typval_encode.c.h @@ -359,7 +359,7 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( TYPVAL_ENCODE_CONV_EMPTY_LIST(tv); break; } - const int saved_copyID = tv->vval.v_list->lv_copyID; + const int saved_copyID = tv_list_copyid(tv->vval.v_list); _TYPVAL_ENCODE_DO_CHECK_SELF_REFERENCE(tv->vval.v_list, lv_copyID, copyID, kMPConvList); TYPVAL_ENCODE_CONV_LIST_START(tv, tv_list_len(tv->vval.v_list)); @@ -515,7 +515,7 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( if (val_di->di_tv.v_type != VAR_LIST) { goto _convert_one_value_regular_dict; } - const int saved_copyID = val_di->di_tv.vval.v_list->lv_copyID; + const int saved_copyID = tv_list_copyid(val_di->di_tv.vval.v_list); _TYPVAL_ENCODE_DO_CHECK_SELF_REFERENCE(val_di->di_tv.vval.v_list, lv_copyID, copyID, kMPConvList); @@ -550,7 +550,7 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( goto _convert_one_value_regular_dict; } }); - const int saved_copyID = val_di->di_tv.vval.v_list->lv_copyID; + const int saved_copyID = tv_list_copyid(val_di->di_tv.vval.v_list); _TYPVAL_ENCODE_DO_CHECK_SELF_REFERENCE(val_list, lv_copyID, copyID, kMPConvPairs); TYPVAL_ENCODE_CONV_DICT_START(tv, TYPVAL_ENCODE_NODICT_VAR, @@ -694,7 +694,7 @@ typval_encode_stop_converting_one_item: case kMPConvList: { if (cur_mpsv->data.l.li == NULL) { (void)_mp_pop(mpstack); - cur_mpsv->data.l.list->lv_copyID = cur_mpsv->saved_copyID; + tv_list_set_copyid(cur_mpsv->data.l.list, cur_mpsv->saved_copyID); TYPVAL_ENCODE_CONV_LIST_END(cur_mpsv->tv); continue; } else if (cur_mpsv->data.l.li @@ -709,7 +709,7 @@ typval_encode_stop_converting_one_item: case kMPConvPairs: { if (cur_mpsv->data.l.li == NULL) { (void)_mp_pop(mpstack); - cur_mpsv->data.l.list->lv_copyID = cur_mpsv->saved_copyID; + tv_list_set_copyid(cur_mpsv->data.l.list, cur_mpsv->saved_copyID); TYPVAL_ENCODE_CONV_DICT_END(cur_mpsv->tv, TYPVAL_ENCODE_NODICT_VAR); continue; } else if (cur_mpsv->data.l.li diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c index ffe393f1b0..463a408885 100644 --- a/src/nvim/regexp.c +++ b/src/nvim/regexp.c @@ -6483,7 +6483,7 @@ static int fill_submatch_list(int argc, typval_T *argv, int argcount) } // Relies on sl_list to be the first item in staticList10_T. - init_static_list((staticList10_T *)(argv->vval.v_list)); + tv_list_init_static10((staticList10_T *)argv->vval.v_list); // There are always 10 list items in staticList10_T. listitem_T *li = tv_list_first(argv->vval.v_list); @@ -6639,14 +6639,12 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, typval_T argv[2]; int dummy; typval_T rettv; - staticList10_T matchList; + staticList10_T matchList = TV_LIST_STATIC10_INIT; rettv.v_type = VAR_STRING; rettv.vval.v_string = NULL; argv[0].v_type = VAR_LIST; argv[0].vval.v_list = &matchList.sl_list; - // FIXME: Abstract away - matchList.sl_list.lv_len = 0; if (expr->v_type == VAR_FUNC) { s = expr->vval.v_string; call_func(s, (int)STRLEN(s), &rettv, 1, argv, @@ -6660,7 +6658,7 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, fill_submatch_list, 0L, 0L, &dummy, true, partial, NULL); } - if (matchList.sl_list.lv_len > 0) { + if (tv_list_len(&matchList.sl_list) > 0) { // fill_submatch_list() was called. clear_submatch_list(&matchList); } -- cgit From f4132fb38b1355115d824b7c04eff25733d059d6 Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 11 Dec 2017 10:19:20 +0300 Subject: *: Fix linter errors --- src/nvim/eval.c | 23 +++++++++++++---------- src/nvim/eval/decode.c | 2 +- src/nvim/eval/encode.c | 2 +- src/nvim/eval/typval.c | 4 ++-- src/nvim/eval/typval_encode.c.h | 10 ++++++---- src/nvim/ops.c | 2 +- src/nvim/shada.c | 8 ++++---- src/nvim/window.c | 3 +-- 8 files changed, 29 insertions(+), 25 deletions(-) diff --git a/src/nvim/eval.c b/src/nvim/eval.c index ad8b575867..10dbf208ea 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -1540,7 +1540,8 @@ ex_let_vars ( listitem_T *item = tv_list_first(l); while (*arg != ']') { arg = skipwhite(arg + 1); - arg = ex_let_one(arg, TV_LIST_ITEM_TV(item), TRUE, (char_u *)",;]", nextchars); + arg = ex_let_one(arg, TV_LIST_ITEM_TV(item), true, (const char_u *)",;]", + nextchars); item = TV_LIST_ITEM_NEXT(l, item); if (arg == NULL) { return FAIL; @@ -2415,13 +2416,14 @@ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv, lp->ll_li = TV_LIST_ITEM_NEXT(lp->ll_list, lp->ll_li); lp->ll_n1++; } - if (ri != NULL) + if (ri != NULL) { EMSG(_("E710: List value has more items than target")); - else if (lp->ll_empty2 - ? (lp->ll_li != NULL - && TV_LIST_ITEM_NEXT(lp->ll_list, lp->ll_li) != NULL) - : lp->ll_n1 != lp->ll_n2) + } else if (lp->ll_empty2 + ? (lp->ll_li != NULL + && TV_LIST_ITEM_NEXT(lp->ll_list, lp->ll_li) != NULL) + : lp->ll_n1 != lp->ll_n2) { EMSG(_("E711: List value has not enough items")); + } } else { typval_T oldtv = TV_INITIAL_VALUE; dict_T *dict = lp->ll_dict; @@ -8561,8 +8563,9 @@ static void filter_map(typval_T *argvars, typval_T *rettv, int map) nli = TV_LIST_ITEM_NEXT(l, li); vimvars[VV_KEY].vv_nr = idx; if (filter_map_one(TV_LIST_ITEM_TV(li), expr, map, &rem) == FAIL - || did_emsg) + || did_emsg) { break; + } if (!map && rem) { tv_list_item_remove(l, li); } @@ -19235,9 +19238,9 @@ int var_item_copy(const vimconv_T *const conv, case VAR_LIST: to->v_type = VAR_LIST; to->v_lock = 0; - if (from->vval.v_list == NULL) + if (from->vval.v_list == NULL) { to->vval.v_list = NULL; - else if (copyID != 0 && tv_list_copyid(from->vval.v_list) == copyID) { + } else if (copyID != 0 && tv_list_copyid(from->vval.v_list) == copyID) { // Use the copy made earlier. to->vval.v_list = tv_list_latest_copy(from->vval.v_list); tv_list_ref(to->vval.v_list); @@ -22400,7 +22403,7 @@ typval_T eval_call_provider(char *provider, char *method, list_T *arguments) {.v_type = VAR_LIST, .vval.v_list = arguments, .v_lock = 0}, {.v_type = VAR_UNKNOWN} }; - typval_T rettv = {.v_type = VAR_UNKNOWN, .v_lock = 0}; + typval_T rettv = { .v_type = VAR_UNKNOWN, .v_lock = VAR_UNLOCKED }; tv_list_ref(arguments); int dummy; diff --git a/src/nvim/eval/decode.c b/src/nvim/eval/decode.c index b2d901462a..d5c65ebe81 100644 --- a/src/nvim/eval/decode.c +++ b/src/nvim/eval/decode.c @@ -60,7 +60,7 @@ static inline void create_special_dict(typval_T *const rettv, dictitem_T *const type_di = tv_dict_item_alloc_len(S_LEN("_TYPE")); type_di->di_tv.v_type = VAR_LIST; type_di->di_tv.v_lock = VAR_UNLOCKED; - type_di->di_tv.vval.v_list = (list_T *) eval_msgpack_type_lists[type]; + type_di->di_tv.vval.v_list = (list_T *)eval_msgpack_type_lists[type]; tv_list_ref(type_di->di_tv.vval.v_list); tv_dict_add(dict, type_di); dictitem_T *const val_di = tv_dict_item_alloc_len(S_LEN("_VAL")); diff --git a/src/nvim/eval/encode.c b/src/nvim/eval/encode.c index 85fd4d1578..9f16b78976 100644 --- a/src/nvim/eval/encode.c +++ b/src/nvim/eval/encode.c @@ -267,7 +267,7 @@ int encode_read_from_list(ListReaderState *const state, char *const buf, } *p++ = NL; if (TV_LIST_ITEM_TV(state->li)->v_type != VAR_STRING) { - *read_bytes = (size_t) (p - buf); + *read_bytes = (size_t)(p - buf); return FAIL; } state->offset = 0; diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index df81c0450d..0c9c4a0347 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -696,8 +696,8 @@ bool tv_list_equal(list_T *const l1, list_T *const l2, const bool ic, listitem_T *item1 = tv_list_first(l1); listitem_T *item2 = tv_list_first(l2); for (; item1 != NULL && item2 != NULL - ; item1 = TV_LIST_ITEM_NEXT(l1, item1), - item2 = TV_LIST_ITEM_NEXT(n2, item2)) { + ; (item1 = TV_LIST_ITEM_NEXT(l1, item1), + item2 = TV_LIST_ITEM_NEXT(n2, item2))) { if (!tv_equal(TV_LIST_ITEM_TV(item1), TV_LIST_ITEM_TV(item2), ic, recursive)) { return false; diff --git a/src/nvim/eval/typval_encode.c.h b/src/nvim/eval/typval_encode.c.h index e4ed2bc636..e9531178ef 100644 --- a/src/nvim/eval/typval_encode.c.h +++ b/src/nvim/eval/typval_encode.c.h @@ -586,9 +586,10 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( } size_t len; char *buf; - if (!encode_vim_list_to_buf( - TV_LIST_ITEM_TV(tv_list_last(val_list))->vval.v_list, &len, - &buf)) { + if (!( + encode_vim_list_to_buf( + TV_LIST_ITEM_TV(tv_list_last(val_list))->vval.v_list, &len, + &buf))) { goto _convert_one_value_regular_dict; } TYPVAL_ENCODE_CONV_EXT_STRING(tv, buf, len, type); @@ -721,7 +722,8 @@ typval_encode_stop_converting_one_item: TV_LIST_ITEM_TV(cur_mpsv->data.l.li)->vval.v_list); TYPVAL_ENCODE_SPECIAL_DICT_KEY_CHECK( encode_vim_to__error_ret, *TV_LIST_ITEM_TV(tv_list_first(kv_pair))); - if (_TYPVAL_ENCODE_CONVERT_ONE_VALUE( + if ( + _TYPVAL_ENCODE_CONVERT_ONE_VALUE( TYPVAL_ENCODE_FIRST_ARG_NAME, &mpstack, cur_mpsv, TV_LIST_ITEM_TV(tv_list_first(kv_pair)), copyID, objname) == FAIL) { diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 3cc73b2e40..a5e131190d 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -5616,7 +5616,7 @@ static bool get_clipboard(int name, yankreg_T **target, bool quiet) goto err; } char_u *regtype = TV_LIST_ITEM_TV(tv_list_last(res))->vval.v_string; - if (regtype == NULL || strlen((char*)regtype) > 1) { + if (regtype == NULL || strlen((char *)regtype) > 1) { goto err; } switch (regtype[0]) { diff --git a/src/nvim/shada.c b/src/nvim/shada.c index 75f91ce6d8..6bf816bb74 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -1646,20 +1646,20 @@ static ShaDaWriteResult shada_pack_entry(msgpack_packer *const packer, case kSDItemHistoryEntry: { const bool is_hist_search = entry.data.history_item.histtype == HIST_SEARCH; - const size_t arr_size = 2 + (size_t) is_hist_search + (size_t) ( + const size_t arr_size = 2 + (size_t)is_hist_search + (size_t)( tv_list_len(entry.data.history_item.additional_elements)); msgpack_pack_array(spacker, arr_size); msgpack_pack_uint8(spacker, entry.data.history_item.histtype); PACK_BIN(cstr_as_string(entry.data.history_item.string)); if (is_hist_search) { - msgpack_pack_uint8(spacker, (uint8_t) entry.data.history_item.sep); + msgpack_pack_uint8(spacker, (uint8_t)entry.data.history_item.sep); } DUMP_ADDITIONAL_ELEMENTS(entry.data.history_item.additional_elements, "history entry item"); break; } case kSDItemVariable: { - const size_t arr_size = 2 + (size_t) ( + const size_t arr_size = 2 + (size_t)( tv_list_len(entry.data.global_var.additional_elements)); msgpack_pack_array(spacker, arr_size); const String varname = cstr_as_string(entry.data.global_var.name); @@ -1679,7 +1679,7 @@ static ShaDaWriteResult shada_pack_entry(msgpack_packer *const packer, break; } case kSDItemSubString: { - const size_t arr_size = 1 + (size_t) ( + const size_t arr_size = 1 + (size_t)( tv_list_len(entry.data.sub_string.additional_elements)); msgpack_pack_array(spacker, arr_size); PACK_BIN(cstr_as_string(entry.data.sub_string.sub)); diff --git a/src/nvim/window.c b/src/nvim/window.c index 21d668a2bd..4c996aea79 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -5577,8 +5577,7 @@ int match_add(win_T *wp, const char *const grp, const char *const pat, } // Set up position matches - if (pos_list != NULL) - { + if (pos_list != NULL) { linenr_T toplnum = 0; linenr_T botlnum = 0; -- cgit From 1a961b57505f57130012fe4fcfda0e8009c8da45 Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 11 Dec 2017 10:34:58 +0300 Subject: eval: Fix add() --- src/nvim/eval.c | 6 +++--- test/functional/eval/null_spec.lua | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 10dbf208ea..357ca52a3d 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -6544,11 +6544,11 @@ static void f_abs(typval_T *argvars, typval_T *rettv, FunPtr fptr) static void f_add(typval_T *argvars, typval_T *rettv, FunPtr fptr) { list_T *l; - rettv->vval.v_number = 1; /* Default: Failed */ if (argvars[0].v_type == VAR_LIST) { - if ((l = argvars[0].vval.v_list) != NULL - && !tv_check_lock(tv_list_locked(l), "add() argument", TV_TRANSLATE)) { + if (!tv_check_lock(tv_list_locked((l = argvars[0].vval.v_list)), + N_("add() argument"), + TV_TRANSLATE)) { tv_list_append_tv(l, &argvars[1]); tv_copy(&argvars[0], rettv); } diff --git a/test/functional/eval/null_spec.lua b/test/functional/eval/null_spec.lua index 14b2d964d5..afe999e1fa 100644 --- a/test/functional/eval/null_spec.lua +++ b/test/functional/eval/null_spec.lua @@ -101,7 +101,8 @@ describe('NULL', function() null_expr_test('is accepted as an empty list by writefile()', ('[writefile(L, "%s"), readfile("%s")]'):format(tmpfname, tmpfname), 0, {0, {}}) - null_expr_test('does not crash add()', 'add(L, 0)', 0, 1) + null_expr_test('makes add() error out', 'add(L, 0)', + 'E742: Cannot change value of add() argument', 1) null_expr_test('makes insert() error out', 'insert(L, 1)', 'E742: Cannot change value of insert() argument', 0) null_expr_test('does not crash remove()', 'remove(L, 0)', -- cgit From 9f534422e6c3649ee40904ae3606455d26b48188 Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 11 Dec 2017 11:09:09 +0300 Subject: eval/typval: Fix typo [ci skip] --- src/nvim/eval/typval.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nvim/eval/typval.h b/src/nvim/eval/typval.h index a7255fdba0..c0d1790c63 100644 --- a/src/nvim/eval/typval.h +++ b/src/nvim/eval/typval.h @@ -422,7 +422,7 @@ static inline int tv_list_uidx(const list_T *const l, int n) /// Normalize index: that is, return either -1 or non-negative index /// -/// @param[in] l List to intex. Used to get length. +/// @param[in] l List to index. Used to get length. /// @param[in] n List index, possibly negative. /// /// @return -1 or list index in range [0, tv_list_len(l)). -- cgit From ceed29687f7772a661ab7caf5e32a7dfadebf743 Mon Sep 17 00:00:00 2001 From: James McCoy Date: Mon, 11 Dec 2017 10:50:45 -0500 Subject: Disable translation of default 'titleold' value It's an empty string, so there's no reason to try to translate it. Closes #7717 --- src/nvim/options.lua | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/nvim/options.lua b/src/nvim/options.lua index dd28a765fd..16f472230a 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -2505,11 +2505,10 @@ return { full_name='titleold', type='string', scope={'global'}, secure=true, - gettext=true, no_mkrc=true, vi_def=true, varname='p_titleold', - defaults={if_true={vi=N_("")}} + defaults={if_true={vi=""}} }, { full_name='titlestring', -- cgit From 45998deb5d325c7a44ce38b0c7d954919458f105 Mon Sep 17 00:00:00 2001 From: ZyX Date: Tue, 12 Dec 2017 00:52:14 +0300 Subject: *: Fix linter errors --- src/nvim/api/vim.c | 4 ++-- src/nvim/eval.c | 8 +++----- src/nvim/tui/tui.c | 34 ++++++++++++++++++---------------- 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index f0db391abe..c0daac8085 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -900,9 +900,9 @@ typedef struct { Object *ret_node_p; } ExprASTConvStackItem; -///@cond DOXYGEN_NOT_A_FUNCTION +/// @cond DOXYGEN_NOT_A_FUNCTION typedef kvec_withinit_t(ExprASTConvStackItem, 16) ExprASTConvStack; -///@endcond +/// @endcond /// Parse a VimL expression /// diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 357ca52a3d..2d8c22cc7a 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -6543,12 +6543,10 @@ static void f_abs(typval_T *argvars, typval_T *rettv, FunPtr fptr) */ static void f_add(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - list_T *l; - rettv->vval.v_number = 1; /* Default: Failed */ + rettv->vval.v_number = 1; // Default: failed. if (argvars[0].v_type == VAR_LIST) { - if (!tv_check_lock(tv_list_locked((l = argvars[0].vval.v_list)), - N_("add() argument"), - TV_TRANSLATE)) { + list_T *const l = argvars[0].vval.v_list; + if (!tv_check_lock(tv_list_locked(l), N_("add() argument"), TV_TRANSLATE)) { tv_list_append_tv(l, &argvars[1]); tv_copy(&argvars[0], rettv); } diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index 9ff1acf64a..60897c08da 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -1515,22 +1515,24 @@ static void patch_terminfo_bugs(TUIData *data, const char *term, // DECSCUSR (cursor shape) sequence is widely supported by several terminal // types. https://github.com/gnachman/iTerm2/pull/92 // xterm extension: vertical bar - if (!konsole && ((xterm && !vte_version) // anything claiming xterm compat - // per MinTTY 0.4.3-1 release notes from 2009 - || putty - // per https://bugzilla.gnome.org/show_bug.cgi?id=720821 - || (vte_version >= 3900) - || tmux // per tmux manual page - // https://lists.gnu.org/archive/html/screen-devel/2013-03/msg00000.html - || screen - || st // #7641 - || rxvt // per command.C - // per analysis of VT100Terminal.m - || iterm || iterm_pretending_xterm - || teraterm // per TeraTerm "Supported Control Functions" doco - // Some linux-type terminals (such as console-terminal-emulator - // from the nosh toolset) implement the xterm extension. - || (linuxvt && (xterm_version || (vte_version > 0) || colorterm)))) { + if (!konsole + && ((xterm && !vte_version) // anything claiming xterm compat + // per MinTTY 0.4.3-1 release notes from 2009 + || putty + // per https://bugzilla.gnome.org/show_bug.cgi?id=720821 + || (vte_version >= 3900) + || tmux // per tmux manual page + // https://lists.gnu.org/archive/html/screen-devel/2013-03/msg00000.html + || screen + || st // #7641 + || rxvt // per command.C + // per analysis of VT100Terminal.m + || iterm || iterm_pretending_xterm + || teraterm // per TeraTerm "Supported Control Functions" doco + // Some linux-type terminals implement the xterm extension. + // Example: console-terminal-emulator from the nosh toolset. + || (linuxvt + && (xterm_version || (vte_version > 0) || colorterm)))) { data->unibi_ext.set_cursor_style = (int)unibi_add_ext_str(ut, "Ss", "\x1b[%p1%d q"); if (-1 == data->unibi_ext.reset_cursor_style) { -- cgit From 932ea7a0d1d19288fad719afd52e9cbeb924c4c2 Mon Sep 17 00:00:00 2001 From: ZyX Date: Tue, 12 Dec 2017 01:13:04 +0300 Subject: clint,eval: Make linter check for direct usage of list attributes --- src/clint.py | 9 +++++++++ src/nvim/eval.c | 33 ++++++++++++++++++++++----------- src/nvim/eval/typval.h | 15 +++++++++++++++ 3 files changed, 46 insertions(+), 11 deletions(-) diff --git a/src/clint.py b/src/clint.py index e63175a69b..8426807c80 100755 --- a/src/clint.py +++ b/src/clint.py @@ -201,6 +201,7 @@ _ERROR_CATEGORIES = [ 'runtime/printf', 'runtime/printf_format', 'runtime/threadsafe_fn', + 'runtime/deprecated', 'syntax/parenthesis', 'whitespace/alignment', 'whitespace/blank_line', @@ -3200,6 +3201,14 @@ def CheckLanguage(filename, clean_lines, linenum, file_extension, if match: error(filename, linenum, 'runtime/printf', 4, 'Use xstrlcat or snprintf instead of %s' % match.group(1)) + if not Search(r'eval/typval\.[ch]$', filename): + match = Search(r'(?:\.|->)' + r'(?:lv_(?:first|last|refcount|len|watch|idx(?:_item)?' + r'|copylist|lock)' + r'|li_(?:next|prev|tv))\b', line) + if match: + error(filename, linenum, 'runtime/deprecated', 4, + 'Accessing list_T internals directly is prohibited') # Check for suspicious usage of "if" like # } if (a == b) { diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 2d8c22cc7a..37356d43b4 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -5249,8 +5249,8 @@ static int free_unref_items(int copyID) // But don't free a list that has a watcher (used in a for loop), these // are not referenced anywhere. for (list_T *ll = gc_first_list; ll != NULL; ll = ll->lv_used_next) { - if ((ll->lv_copyID & COPYID_MASK) != (copyID & COPYID_MASK) - && ll->lv_watch == NULL) { + if ((tv_list_copyid(ll) & COPYID_MASK) != (copyID & COPYID_MASK) + && !tv_list_has_watchers(ll)) { // Free the List and ordinary items it contains, but don't recurse // into Lists and Dictionaries, they will be in the list of dicts // or list of lists. @@ -5270,7 +5270,7 @@ static int free_unref_items(int copyID) for (ll = gc_first_list; ll != NULL; ll = ll_next) { ll_next = ll->lv_used_next; if ((ll->lv_copyID & COPYID_MASK) != (copyID & COPYID_MASK) - && ll->lv_watch == NULL) { + && !tv_list_has_watchers(ll)) { // Free the List and ordinary items it contains, but don't recurse // into Lists and Dictionaries, they will be in the list of dicts // or list of lists. @@ -21009,6 +21009,22 @@ void func_ptr_ref(ufunc_T *fp) } } +/// Check whether funccall is still referenced outside +/// +/// It is supposed to be referenced if either it is referenced itself or if l:, +/// a: or a:000 are referenced as all these are statically allocated within +/// funccall structure. +static inline bool fc_referenced(const funccall_T *const fc) + FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT + FUNC_ATTR_NONNULL_ALL +{ + return ((fc->l_varlist.lv_refcount // NOLINT(runtime/deprecated) + != DO_NOT_FREE_CNT) + || fc->l_vars.dv_refcount != DO_NOT_FREE_CNT + || fc->l_avars.dv_refcount != DO_NOT_FREE_CNT + || fc->fc_refcount > 0); +} + /// Call a user function /// /// @param fp Function to call. @@ -21357,10 +21373,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, // If the a:000 list and the l: and a: dicts are not referenced and there // is no closure using it, we can free the funccall_T and what's in it. - if (fc->l_varlist.lv_refcount == DO_NOT_FREE_CNT - && fc->l_vars.dv_refcount == DO_NOT_FREE_CNT - && fc->l_avars.dv_refcount == DO_NOT_FREE_CNT - && fc->fc_refcount <= 0) { + if (!fc_referenced(fc)) { free_funccal(fc, false); } else { // "fc" is still in use. This can happen when returning "a:000", @@ -21404,10 +21417,8 @@ static void funccal_unref(funccall_T *fc, ufunc_T *fp, bool force) return; } - if (--fc->fc_refcount <= 0 && (force || ( - fc->l_varlist.lv_refcount == DO_NOT_FREE_CNT - && fc->l_vars.dv_refcount == DO_NOT_FREE_CNT - && fc->l_avars.dv_refcount == DO_NOT_FREE_CNT))) { + fc->fc_refcount--; + if (force ? fc->fc_refcount <= 0 : !fc_referenced(fc)) { for (pfc = &previous_funccal; *pfc != NULL; pfc = &(*pfc)->caller) { if (fc == *pfc) { *pfc = fc->caller; diff --git a/src/nvim/eval/typval.h b/src/nvim/eval/typval.h index c0d1790c63..2bce7bd6b2 100644 --- a/src/nvim/eval/typval.h +++ b/src/nvim/eval/typval.h @@ -440,6 +440,21 @@ static inline int tv_list_uidx(const list_T *const l, int n) return n; } +static inline bool tv_list_has_watchers(const list_T *const l) + REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; + +/// Check whether list has watchers +/// +/// E.g. is referenced by a :for loop. +/// +/// @param[in] l List to check. +/// +/// @return true if there are watchers, false otherwise. +static inline bool tv_list_has_watchers(const list_T *const l) +{ + return l && l->lv_watch; +} + static inline listitem_T *tv_list_first(const list_T *const l) REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; -- cgit From 34057045beca40406673ff421a4ef1e8e8c08853 Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Tue, 12 Dec 2017 18:23:19 +0100 Subject: ui: forward relevant option updates to UIs (#7520) also make termguicolors mutable after startup --- runtime/doc/options.txt | 1 - runtime/doc/ui.txt | 21 ++++++++++ src/nvim/api/ui.c | 1 + src/nvim/api/ui_events.in.h | 2 + src/nvim/generators/gen_api_ui_events.lua | 15 +++++-- src/nvim/generators/gen_options.lua | 1 + src/nvim/option.c | 42 ++++++++++++++++++++ src/nvim/option_defs.h | 3 ++ src/nvim/options.lua | 26 ++++++------ src/nvim/tui/tui.c | 9 +++++ src/nvim/ui.c | 1 + src/nvim/ui_bridge.c | 24 +++++++++++ test/functional/terminal/tui_spec.lua | 53 +++++++++++++++++++++++++ test/functional/ui/options_spec.lua | 66 +++++++++++++++++++++++++++++++ test/functional/ui/screen.lua | 5 +++ 15 files changed, 255 insertions(+), 15 deletions(-) create mode 100644 test/functional/ui/options_spec.lua diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index 4180ca21f2..d3072d83e2 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -6192,7 +6192,6 @@ A jump table for the options with a short description can be found at |Q_op|. When on, uses |highlight-guifg| and |highlight-guibg| attributes in the terminal (thus using 24-bit color). Requires a ISO-8613-3 compatible terminal. - Must be set at startup (in your |init.vim| or |--cmd|). *'terse'* *'noterse'* 'terse' boolean (default off) diff --git a/runtime/doc/ui.txt b/runtime/doc/ui.txt index abbd063483..e1c5523202 100644 --- a/runtime/doc/ui.txt +++ b/runtime/doc/ui.txt @@ -80,6 +80,27 @@ Global Events *ui-global* Some keys are missing in some modes. +["option_set", name, value] + The value of ui related option `name` changed. The sent options are + listed below: + + 'arabicshape' + 'ambiwith' + 'emoji' + 'guifont' + 'guifontset' + 'guifontwide' + 'showtabline' + 'termguicolors' + + Options are not added to the list if their effects are already taken + care of. For instance, instead of forwarding the raw 'mouse' option + value, `mouse_on` and `mouse_off` directly indicate if mouse support + is active right now. Some options like 'ambiwith' have already taken + effect on the grid, where appropriate empty cells are added, however + an ui might still use these options when rendering raw text sent from + Nvim, like the text of the cmdline when |ui-ext-cmdline| is set. + ["mode_change", mode, mode_idx] The mode changed. The first parameter `mode` is a string representing the current mode. `mode_idx` is an index into the array received in diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c index a9eaccfac5..35508fde6b 100644 --- a/src/nvim/api/ui.c +++ b/src/nvim/api/ui.c @@ -93,6 +93,7 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height, ui->suspend = remote_ui_suspend; ui->set_title = remote_ui_set_title; ui->set_icon = remote_ui_set_icon; + ui->option_set = remote_ui_option_set; ui->event = remote_ui_event; memset(ui->ui_ext, 0, sizeof(ui->ui_ext)); diff --git a/src/nvim/api/ui_events.in.h b/src/nvim/api/ui_events.in.h index 847b21072a..3d2253e918 100644 --- a/src/nvim/api/ui_events.in.h +++ b/src/nvim/api/ui_events.in.h @@ -58,6 +58,8 @@ void set_title(String title) FUNC_API_SINCE(3); void set_icon(String icon) FUNC_API_SINCE(3); +void option_set(String name, Object value) + FUNC_API_SINCE(4) FUNC_API_BRIDGE_IMPL; void popupmenu_show(Array items, Integer selected, Integer row, Integer col) FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; diff --git a/src/nvim/generators/gen_api_ui_events.lua b/src/nvim/generators/gen_api_ui_events.lua index d2b90db707..2666ca6e6f 100644 --- a/src/nvim/generators/gen_api_ui_events.lua +++ b/src/nvim/generators/gen_api_ui_events.lua @@ -37,7 +37,7 @@ function write_arglist(output, ev, need_copy) for j = 1, #ev.parameters do local param = ev.parameters[j] local kind = string.upper(param[1]) - local do_copy = need_copy and (kind == "ARRAY" or kind == "DICTIONARY" or kind == "STRING") + local do_copy = need_copy and (kind == "ARRAY" or kind == "DICTIONARY" or kind == "STRING" or kind == "OBJECT") output:write(' ADD(args, ') if do_copy then output:write('copy_object(') @@ -91,7 +91,7 @@ for i = 1, #events do recv_cleanup = recv_cleanup..' api_free_string('..param[2]..');\n' argc = argc+2 elseif param[1] == 'Array' then - send = send..' Array copy_'..param[2]..' = copy_array('..param[2]..');\n' + send = send..' Array '..copy..' = copy_array('..param[2]..');\n' argv = argv..', '..copy..'.items, INT2PTR('..copy..'.size)' recv = (recv..' Array '..param[2].. ' = (Array){.items = argv['..argc..'],'.. @@ -99,6 +99,15 @@ for i = 1, #events do recv_argv = recv_argv..', '..param[2] recv_cleanup = recv_cleanup..' api_free_array('..param[2]..');\n' argc = argc+2 + elseif param[1] == 'Object' then + send = send..' Object *'..copy..' = xmalloc(sizeof(Object));\n' + send = send..' *'..copy..' = copy_object('..param[2]..');\n' + argv = argv..', '..copy + recv = recv..' Object '..param[2]..' = *(Object *)argv['..argc..'];\n' + recv_argv = recv_argv..', '..param[2] + recv_cleanup = (recv_cleanup..' api_free_object('..param[2]..');\n'.. + ' xfree(argv['..argc..']);\n') + argc = argc+1 elseif param[1] == 'Integer' or param[1] == 'Boolean' then argv = argv..', INT2PTR('..param[2]..')' recv_argv = recv_argv..', PTR2INT(argv['..argc..'])' @@ -119,7 +128,7 @@ for i = 1, #events do write_signature(bridge_output, ev, 'UI *ui') bridge_output:write('\n{\n') bridge_output:write(send) - bridge_output:write(' UI_BRIDGE_CALL(ui, '..ev.name..', '..argc..', ui'..argv..');\n}\n') + bridge_output:write(' UI_BRIDGE_CALL(ui, '..ev.name..', '..argc..', ui'..argv..');\n}\n\n') end end diff --git a/src/nvim/generators/gen_options.lua b/src/nvim/generators/gen_options.lua index 36562c0be9..fdc00d5dc0 100644 --- a/src/nvim/generators/gen_options.lua +++ b/src/nvim/generators/gen_options.lua @@ -36,6 +36,7 @@ local redraw_flags={ all_windows='P_RALL', everything='P_RCLR', curswant='P_CURSWANT', + ui_option='P_UI_OPTION', } local list_flags={ diff --git a/src/nvim/option.c b/src/nvim/option.c index f8a05f133d..499cf79836 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -74,6 +74,7 @@ #include "nvim/undo.h" #include "nvim/window.h" #include "nvim/os/os.h" +#include "nvim/api/private/helpers.h" #include "nvim/os/input.h" #include "nvim/os/lang.h" @@ -248,6 +249,7 @@ typedef struct vimoption { #define P_RWINONLY 0x10000000U ///< only redraw current window #define P_NDNAME 0x20000000U ///< only normal dir name chars allowed +#define P_UI_OPTION 0x40000000U ///< send option to remote ui #define HIGHLIGHT_INIT \ "8:SpecialKey,~:EndOfBuffer,z:TermCursor,Z:TermCursorNC,@:NonText," \ @@ -1188,6 +1190,7 @@ do_set ( set_options_default(OPT_FREE | opt_flags); didset_options(); didset_options2(); + ui_refresh_options(); redraw_all_later(CLEAR); } else { showoptions(1, opt_flags); @@ -1815,6 +1818,10 @@ do_set ( NULL, false, NULL); reset_v_option_vars(); xfree(saved_origval); + if (options[opt_idx].flags & P_UI_OPTION) { + ui_call_option_set(cstr_as_string(options[opt_idx].fullname), + STRING_OBJ(cstr_as_string(*(char **)varp))); + } } } else { // key code option(FIXME(tarruda): Show a warning or something @@ -2417,6 +2424,10 @@ static char *set_string_option(const int opt_idx, const char *const value, NULL, false, NULL); reset_v_option_vars(); xfree(saved_oldval); + if (options[opt_idx].flags & P_UI_OPTION) { + ui_call_option_set(cstr_as_string(options[opt_idx].fullname), + STRING_OBJ(cstr_as_string((char *)(*varp)))); + } } return r; @@ -4024,6 +4035,10 @@ static char *set_bool_option(const int opt_idx, char_u *const varp, (char_u *) options[opt_idx].fullname, NULL, false, NULL); reset_v_option_vars(); + if (options[opt_idx].flags & P_UI_OPTION) { + ui_call_option_set(cstr_as_string(options[opt_idx].fullname), + BOOLEAN_OBJ(value)); + } } comp_col(); /* in case 'ruler' or 'showcmd' changed */ @@ -4429,6 +4444,10 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, (char_u *) options[opt_idx].fullname, NULL, false, NULL); reset_v_option_vars(); + if (options[opt_idx].flags & P_UI_OPTION) { + ui_call_option_set(cstr_as_string(options[opt_idx].fullname), + INTEGER_OBJ(value)); + } } comp_col(); /* in case 'columns' or 'ls' changed */ @@ -4999,6 +5018,29 @@ static int optval_default(vimoption_T *p, char_u *varp) return STRCMP(*(char_u **)varp, p->def_val[dvi]) == 0; } +/// Send update to UIs with values of UI relevant options +void ui_refresh_options(void) +{ + for (int opt_idx = 0; options[opt_idx].fullname; opt_idx++) { + uint32_t flags = options[opt_idx].flags; + if (!(flags & P_UI_OPTION)) { + continue; + } + String name = cstr_as_string(options[opt_idx].fullname); + void *varp = options[opt_idx].var; + Object value = OBJECT_INIT; + if (flags & P_BOOL) { + value = BOOLEAN_OBJ(*(int *)varp); + } else if (flags & P_NUM) { + value = INTEGER_OBJ(*(long *)varp); + } else if (flags & P_STRING) { + // cstr_as_string handles NULL string + value = STRING_OBJ(cstr_as_string(*(char **)varp)); + } + ui_call_option_set(name, value); + } +} + /* * showoneopt: show the value of one option * must not be called with a hidden option! diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h index b16f222705..1b978137ae 100644 --- a/src/nvim/option_defs.h +++ b/src/nvim/option_defs.h @@ -447,6 +447,9 @@ EXTERN char_u *p_popt; // 'printoptions' EXTERN char_u *p_header; // 'printheader' EXTERN int p_prompt; // 'prompt' EXTERN char_u *p_guicursor; // 'guicursor' +EXTERN char_u *p_guifont; // 'guifont' +EXTERN char_u *p_guifontset; // 'guifontset' +EXTERN char_u *p_guifontwide; // 'guifontwide' EXTERN char_u *p_hf; // 'helpfile' EXTERN long p_hh; // 'helpheight' EXTERN char_u *p_hlg; // 'helplang' diff --git a/src/nvim/options.lua b/src/nvim/options.lua index 16f472230a..45efd49391 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -68,7 +68,8 @@ return { type='bool', scope={'global'}, vi_def=true, vim=true, - redraw={'everything'}, + redraw={'everything', 'ui_option'}, + varname='p_arshape', defaults={if_true={vi=true}} }, @@ -91,7 +92,7 @@ return { full_name='ambiwidth', abbreviation='ambw', type='string', scope={'global'}, vi_def=true, - redraw={'everything'}, + redraw={'everything', 'ui_option'}, varname='p_ambw', defaults={if_true={vi="single"}} }, @@ -661,7 +662,7 @@ return { full_name='emoji', abbreviation='emo', type='bool', scope={'global'}, vi_def=true, - redraw={'everything'}, + redraw={'everything', 'ui_option'}, varname='p_emoji', defaults={if_true={vi=true}} }, @@ -1021,23 +1022,26 @@ return { type='string', list='onecomma', scope={'global'}, deny_duplicates=true, vi_def=true, - redraw={'everything'}, - enable_if=false, + varname='p_guifont', + redraw={'everything', 'ui_option'}, + defaults={if_true={vi=""}} }, { full_name='guifontset', abbreviation='gfs', type='string', list='onecomma', scope={'global'}, vi_def=true, - redraw={'everything'}, - enable_if=false, + varname='p_guifontset', + redraw={'everything', 'ui_option'}, + defaults={if_true={vi=""}} }, { full_name='guifontwide', abbreviation='gfw', type='string', list='onecomma', scope={'global'}, deny_duplicates=true, vi_def=true, - redraw={'everything'}, - enable_if=false, + redraw={'everything', 'ui_option'}, + varname='p_guifontwide', + defaults={if_true={vi=""}} }, { full_name='guioptions', abbreviation='go', @@ -2164,7 +2168,7 @@ return { full_name='showtabline', abbreviation='stal', type='number', scope={'global'}, vi_def=true, - redraw={'all_windows'}, + redraw={'all_windows', 'ui_option'}, varname='p_stal', defaults={if_true={vi=1}} }, @@ -2435,7 +2439,7 @@ return { full_name='termguicolors', abbreviation='tgc', type='bool', scope={'global'}, vi_def=false, - redraw={'everything'}, + redraw={'everything', 'ui_option'}, varname='p_tgc', defaults={if_true={vi=false}} }, diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index 9ff1acf64a..49aa41b9b0 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -151,6 +151,7 @@ UI *tui_start(void) ui->suspend = tui_suspend; ui->set_title = tui_set_title; ui->set_icon = tui_set_icon; + ui->option_set= tui_option_set; ui->event = tui_event; memset(ui->ui_ext, 0, sizeof(ui->ui_ext)); @@ -1136,6 +1137,14 @@ static void tui_set_icon(UI *ui, String icon) { } +static void tui_option_set(UI *ui, String name, Object value) +{ + if (strequal(name.data, "termguicolors")) { + // NB: value for bridge is set in ui_bridge.c + ui->rgb = value.data.boolean; + } +} + // NB: if we start to use this, the ui_bridge must be updated // to make a copy for the tui thread static void tui_event(UI *ui, char *name, Array args, bool *args_consumed) diff --git a/src/nvim/ui.c b/src/nvim/ui.c index 3b8b3ac9a7..81da88c54a 100644 --- a/src/nvim/ui.c +++ b/src/nvim/ui.c @@ -339,6 +339,7 @@ void ui_attach_impl(UI *ui) } uis[ui_count++] = ui; + ui_refresh_options(); ui_refresh(); } diff --git a/src/nvim/ui_bridge.c b/src/nvim/ui_bridge.c index 7573fa1653..0a69cf0ecb 100644 --- a/src/nvim/ui_bridge.c +++ b/src/nvim/ui_bridge.c @@ -66,6 +66,7 @@ UI *ui_bridge_attach(UI *ui, ui_main_fn ui_main, event_scheduler scheduler) rv->bridge.suspend = ui_bridge_suspend; rv->bridge.set_title = ui_bridge_set_title; rv->bridge.set_icon = ui_bridge_set_icon; + rv->bridge.option_set = ui_bridge_option_set; rv->scheduler = scheduler; for (UIWidget i = 0; (int)i < UI_WIDGETS; i++) { @@ -144,6 +145,29 @@ static void ui_bridge_highlight_set_event(void **argv) xfree(argv[1]); } +static void ui_bridge_option_set(UI *ui, String name, Object value) +{ + // Assumes bridge is only used by TUI + if (strequal(name.data, "termguicolors")) { + ui->rgb = value.data.boolean; + } + String copy_name = copy_string(name); + Object *copy_value = xmalloc(sizeof(Object)); + *copy_value = copy_object(value); + UI_BRIDGE_CALL(ui, option_set, 4, ui, + copy_name.data, INT2PTR(copy_name.size), copy_value); +} +static void ui_bridge_option_set_event(void **argv) +{ + UI *ui = UI(argv[0]); + String name = (String){ .data = argv[1], .size = (size_t)argv[2] }; + Object value = *(Object *)argv[3]; + ui->option_set(ui, name, value); + api_free_string(name); + api_free_object(value); + xfree(argv[3]); +} + static void ui_bridge_suspend(UI *b) { UIBridgeData *data = (UIBridgeData *)b; diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua index bf3c6bdb3a..745bfeecf9 100644 --- a/test/functional/terminal/tui_spec.lua +++ b/test/functional/terminal/tui_spec.lua @@ -4,6 +4,7 @@ local global_helpers = require('test.helpers') local uname = global_helpers.uname local helpers = require('test.functional.helpers')(after_each) local thelpers = require('test.functional.terminal.helpers') +local Screen = require('test.functional.ui.screen') local eq = helpers.eq local feed_data = thelpers.feed_data local feed_command = helpers.feed_command @@ -179,6 +180,58 @@ describe('tui', function() {3:-- TERMINAL --} | ]]) end) + + it('allows termguicolors to be set at runtime', function() + screen:set_option('rgb', true) + screen:set_default_attr_ids({ + [1] = {reverse = true}, + [2] = {foreground = 13, special = Screen.colors.Grey0}, + [3] = {special = Screen.colors.Grey0, bold = true, reverse = true}, + [4] = {bold = true}, + [5] = {special = Screen.colors.Grey0, reverse = true, foreground = 4}, + [6] = {foreground = 4, special = Screen.colors.Grey0}, + [7] = {special = Screen.colors.Grey0, reverse = true, foreground = Screen.colors.SeaGreen4}, + [8] = {foreground = Screen.colors.SeaGreen4, special = Screen.colors.Grey0}, + [9] = {special = Screen.colors.Grey0, bold = true, foreground = Screen.colors.Blue1}, + }) + + feed_data(':hi SpecialKey ctermfg=3 guifg=SeaGreen\n') + feed_data('i') + feed_data('\022\007') -- ctrl+g + feed_data('\028\014') -- crtl+\ ctrl+N + feed_data(':set termguicolors?\n') + screen:expect([[ + {5:^}{6:G} | + {2:~ }| + {2:~ }| + {2:~ }| + {3:[No Name] [+] }| + notermguicolors | + {4:-- TERMINAL --} | + ]]) + + feed_data(':set termguicolors\n') + screen:expect([[ + {7:^}{8:G} | + {9:~ }| + {9:~ }| + {9:~ }| + {3:[No Name] [+] }| + | + {4:-- TERMINAL --} | + ]]) + + feed_data(':set notermguicolors\n') + screen:expect([[ + {5:^}{6:G} | + {2:~ }| + {2:~ }| + {2:~ }| + {3:[No Name] [+] }| + | + {4:-- TERMINAL --} | + ]]) + end) end) describe('tui with non-tty file descriptors', function() diff --git a/test/functional/ui/options_spec.lua b/test/functional/ui/options_spec.lua new file mode 100644 index 0000000000..14f40b3ec1 --- /dev/null +++ b/test/functional/ui/options_spec.lua @@ -0,0 +1,66 @@ +local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') +local clear = helpers.clear +local command = helpers.command +local eq = helpers.eq + +describe('ui receives option updates', function() + local screen + + before_each(function() + clear() + screen = Screen.new(20,5) + screen:attach() + end) + + after_each(function() + screen:detach() + end) + + local defaults = { + ambiwidth='single', + arabicshape=true, + emoji=true, + guifont='', + guifontset='', + guifontwide='', + showtabline=1, + termguicolors=false, + } + + it("for defaults", function() + screen:expect(function() + eq(defaults, screen.options) + end) + end) + + it("when setting options", function() + local changed = {} + for k,v in pairs(defaults) do + changed[k] = v + end + + command("set termguicolors") + changed.termguicolors = true + screen:expect(function() + eq(changed, screen.options) + end) + + command("set guifont=Comic\\ Sans") + changed.guifont = "Comic Sans" + screen:expect(function() + eq(changed, screen.options) + end) + + command("set showtabline=0") + changed.showtabline = 0 + screen:expect(function() + eq(changed, screen.options) + end) + + command("set all&") + screen:expect(function() + eq(defaults, screen.options) + end) + end) +end) diff --git a/test/functional/ui/screen.lua b/test/functional/ui/screen.lua index 075d8c40d7..696feabeed 100644 --- a/test/functional/ui/screen.lua +++ b/test/functional/ui/screen.lua @@ -137,6 +137,7 @@ function Screen.new(width, height) visual_bell = false, suspended = false, mode = 'normal', + options = {}, _default_attr_ids = nil, _default_attr_ignore = nil, _mouse_enabled = true, @@ -482,6 +483,10 @@ function Screen:_handle_set_icon(icon) self.icon = icon end +function Screen:_handle_option_set(name, value) + self.options[name] = value +end + function Screen:_clear_block(top, bot, left, right) for i = top, bot do self:_clear_row_section(i, left, right) -- cgit From 6203c23449cfdf8fb09c33d3ab267703d57123fa Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Tue, 12 Dec 2017 01:31:15 +0100 Subject: pty_process_unix: _exit() on execvp() failure Mostly cargo-culting based on a reading of the manpages, interwebs, and the Vim source. --- src/nvim/os/pty_process_unix.c | 8 ++++---- src/nvim/os/signal.c | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/nvim/os/pty_process_unix.c b/src/nvim/os/pty_process_unix.c index 53301e4b53..855ca2ae47 100644 --- a/src/nvim/os/pty_process_unix.c +++ b/src/nvim/os/pty_process_unix.c @@ -72,8 +72,7 @@ int pty_process_spawn(PtyProcess *ptyproc) ELOG("forkpty failed: %s", strerror(errno)); return status; } else if (pid == 0) { - init_child(ptyproc); - abort(); + init_child(ptyproc); // never returns } // make sure the master file descriptor is non blocking @@ -163,14 +162,15 @@ static void init_child(PtyProcess *ptyproc) FUNC_ATTR_NONNULL_ALL Process *proc = (Process *)ptyproc; if (proc->cwd && os_chdir(proc->cwd) != 0) { - fprintf(stderr, "chdir failed: %s\n", strerror(errno)); + ELOG("chdir failed: %s", strerror(errno)); return; } char *prog = ptyproc->process.argv[0]; setenv("TERM", ptyproc->term_name ? ptyproc->term_name : "ansi", 1); execvp(prog, ptyproc->process.argv); - fprintf(stderr, "execvp failed: %s: %s\n", strerror(errno), prog); + ELOG("execvp failed: %s: %s", strerror(errno), prog); + _exit(122); // 122 is EXEC_FAILED in the Vim source. } static void init_termios(struct termios *termios) FUNC_ATTR_NONNULL_ALL diff --git a/src/nvim/os/signal.c b/src/nvim/os/signal.c index fd6d3b32e4..732be072e1 100644 --- a/src/nvim/os/signal.c +++ b/src/nvim/os/signal.c @@ -10,6 +10,7 @@ #endif #include "nvim/ascii.h" +#include "nvim/log.h" #include "nvim/vim.h" #include "nvim/globals.h" #include "nvim/memline.h" @@ -162,7 +163,7 @@ static void on_signal(SignalWatcher *handle, int signum, void *data) } break; default: - fprintf(stderr, "Invalid signal %d", signum); + ELOG("invalid signal: %d", signum); break; } } -- cgit From 6b51c72e0cc99c6c03c521f77793028cf63afd45 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Mon, 11 Dec 2017 23:05:36 +0100 Subject: tui: rework deferred-termcodes implementation Try another approach to defer the termcodes. Seems less janky, but still not perfect. ref #7664 ref #7649 ref #7664 ref 27f9b1c7b029d8 --- src/nvim/main.c | 3 +++ src/nvim/tui/tui.c | 15 +++++++-------- src/nvim/ui.c | 8 ++++++++ src/nvim/ui.h | 3 +-- src/nvim/ui_bridge.c | 11 +++++++++++ 5 files changed, 30 insertions(+), 10 deletions(-) diff --git a/src/nvim/main.c b/src/nvim/main.c index 0346414697..5d826d9524 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -563,6 +563,9 @@ int main(int argc, char **argv) (void)eval_has_provider("clipboard"); } + if (!headless_mode) { + ui_builtin_after_startup(); + } TIME_MSG("before starting main loop"); ILOG("starting main loop"); diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index 49aa41b9b0..0d3a241793 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -126,6 +126,7 @@ UI *tui_start(void) { UI *ui = xcalloc(1, sizeof(UI)); ui->stop = tui_stop; + ui->after_startup = tui_after_startup; ui->rgb = p_tgc; ui->resize = tui_resize; ui->clear = tui_clear; @@ -174,13 +175,7 @@ static size_t unibi_pre_fmt_str(TUIData *data, unsigned int unibi_index, static void terminfo_after_startup_event(void **argv) { UI *ui = argv[0]; - bool defer = argv[1] != NULL; // clever(?) boolean without malloc() dance. TUIData *data = ui->data; - if (defer) { // We're on the main-loop. Now forward to the TUI loop. - loop_schedule(data->loop, - event_create(terminfo_after_startup_event, 2, ui, NULL)); - return; - } // Enable bracketed paste unibi_out_ext(ui, data->unibi_ext.enable_bracketed_paste); // Enable focus reporting @@ -278,9 +273,13 @@ static void terminfo_start(UI *ui) uv_pipe_init(&data->write_loop, &data->output_handle.pipe, 0); uv_pipe_open(&data->output_handle.pipe, data->out_fd); } +} - loop_schedule(&main_loop, - event_create(terminfo_after_startup_event, 2, ui, ui)); +static void tui_after_startup(UI *ui) +{ + TUIData *data = ui->data; + loop_schedule(data->loop, + event_create(terminfo_after_startup_event, 1, ui)); } static void terminfo_stop(UI *ui) diff --git a/src/nvim/ui.c b/src/nvim/ui.c index 81da88c54a..5c8e9380db 100644 --- a/src/nvim/ui.c +++ b/src/nvim/ui.c @@ -138,6 +138,14 @@ void ui_builtin_start(void) #endif } +/// Immediately after VimEnter event. +void ui_builtin_after_startup(void) +{ +#ifdef FEAT_TUI + UI_CALL(after_startup); +#endif +} + void ui_builtin_stop(void) { UI_CALL(stop); diff --git a/src/nvim/ui.h b/src/nvim/ui.h index f1ea0716e6..84d17e9ec2 100644 --- a/src/nvim/ui.h +++ b/src/nvim/ui.h @@ -31,13 +31,12 @@ struct ui_t { bool ui_ext[UI_WIDGETS]; ///< Externalized widgets int width, height; void *data; - #ifdef INCLUDE_GENERATED_DECLARATIONS # include "ui_events.generated.h" #endif - void (*event)(UI *ui, char *name, Array args, bool *args_consumed); void (*stop)(UI *ui); + void (*after_startup)(UI *ui); }; #ifdef INCLUDE_GENERATED_DECLARATIONS diff --git a/src/nvim/ui_bridge.c b/src/nvim/ui_bridge.c index 0a69cf0ecb..324d821df5 100644 --- a/src/nvim/ui_bridge.c +++ b/src/nvim/ui_bridge.c @@ -42,6 +42,7 @@ UI *ui_bridge_attach(UI *ui, ui_main_fn ui_main, event_scheduler scheduler) rv->ui = ui; rv->bridge.rgb = ui->rgb; rv->bridge.stop = ui_bridge_stop; + rv->bridge.after_startup = ui_bridge_after_startup; rv->bridge.resize = ui_bridge_resize; rv->bridge.clear = ui_bridge_clear; rv->bridge.eol_clear = ui_bridge_eol_clear; @@ -106,6 +107,16 @@ static void ui_thread_run(void *data) bridge->ui_main(bridge, bridge->ui); } +static void ui_bridge_after_startup(UI *b) +{ + UI_BRIDGE_CALL(b, after_startup, 1, b); +} +static void ui_bridge_after_startup_event(void **argv) +{ + UI *ui = UI(argv[0]); + ui->after_startup(ui); +} + static void ui_bridge_stop(UI *b) { UIBridgeData *bridge = (UIBridgeData *)b; -- cgit From ed92ece815e1f32259b3f0397476ff6ac92726e3 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Tue, 12 Dec 2017 20:41:25 +0100 Subject: tui: defer termcodes using a timer With this implementation there is no "jank" during startup. Using the main_loop in any fashion is janky. Using only the TUI loop emits the termcodes too soon, or requires bad hacks like counting tui_flush invocations (9 seems to work). ref #7664 ref #7649 ref #7664 ref 27f9b1c7b029d8 --- src/nvim/main.c | 3 --- src/nvim/tui/tui.c | 41 +++++++++++++++++++++-------------------- src/nvim/ui.c | 8 -------- src/nvim/ui.h | 1 - src/nvim/ui_bridge.c | 11 ----------- 5 files changed, 21 insertions(+), 43 deletions(-) diff --git a/src/nvim/main.c b/src/nvim/main.c index 5d826d9524..0346414697 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -563,9 +563,6 @@ int main(int argc, char **argv) (void)eval_has_provider("clipboard"); } - if (!headless_mode) { - ui_builtin_after_startup(); - } TIME_MSG("before starting main loop"); ILOG("starting main loop"); diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index 0d3a241793..b8f43e9d13 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -70,6 +70,7 @@ typedef struct { UIBridgeData *bridge; Loop *loop; bool stop; + uv_timer_t after_startup_timer; unibi_var_t params[9]; char buf[OUTBUF_SIZE]; size_t bufpos; @@ -126,7 +127,6 @@ UI *tui_start(void) { UI *ui = xcalloc(1, sizeof(UI)); ui->stop = tui_stop; - ui->after_startup = tui_after_startup; ui->rgb = p_tgc; ui->resize = tui_resize; ui->clear = tui_clear; @@ -170,18 +170,6 @@ static size_t unibi_pre_fmt_str(TUIData *data, unsigned int unibi_index, return unibi_run(str, data->params, buf, len); } -/// Emits some termcodes after Nvim startup, which were observed to slowdown -/// rendering during startup in tmux 2.3 (+focus-events). #7649 -static void terminfo_after_startup_event(void **argv) -{ - UI *ui = argv[0]; - TUIData *data = ui->data; - // Enable bracketed paste - unibi_out_ext(ui, data->unibi_ext.enable_bracketed_paste); - // Enable focus reporting - unibi_out_ext(ui, data->unibi_ext.enable_focus_reporting); -} - static void termname_set_event(void **argv) { char *termname = argv[0]; @@ -261,6 +249,9 @@ static void terminfo_start(UI *ui) unibi_out(ui, unibi_enter_ca_mode); unibi_out(ui, unibi_keypad_xmit); unibi_out(ui, unibi_clear_screen); + // Enable bracketed paste + unibi_out_ext(ui, data->unibi_ext.enable_bracketed_paste); + uv_loop_init(&data->write_loop); if (data->out_isatty) { uv_tty_init(&data->write_loop, &data->output_handle.tty, data->out_fd, 0); @@ -275,13 +266,6 @@ static void terminfo_start(UI *ui) } } -static void tui_after_startup(UI *ui) -{ - TUIData *data = ui->data; - loop_schedule(data->loop, - event_create(terminfo_after_startup_event, 1, ui)); -} - static void terminfo_stop(UI *ui) { TUIData *data = ui->data; @@ -307,6 +291,18 @@ static void terminfo_stop(UI *ui) unibi_destroy(data->ut); } +static void after_startup_timer_cb(uv_timer_t *handle) + FUNC_ATTR_NONNULL_ALL +{ + UI *ui = handle->data; + TUIData *data = ui->data; + uv_timer_stop(&data->after_startup_timer); + + // Emit this after Nvim startup, not during. This works around a tmux + // 2.3 bug(?) which caused slow drawing during startup. #7649 + unibi_out_ext(ui, data->unibi_ext.enable_focus_reporting); +} + static void tui_terminal_start(UI *ui) { TUIData *data = ui->data; @@ -316,6 +312,8 @@ static void tui_terminal_start(UI *ui) update_size(ui); signal_watcher_start(&data->winch_handle, sigwinch_cb, SIGWINCH); term_input_start(&data->input); + + uv_timer_start(&data->after_startup_timer, after_startup_timer_cb, 500, 0); } static void tui_terminal_stop(UI *ui) @@ -349,6 +347,8 @@ static void tui_main(UIBridgeData *bridge, UI *ui) #ifdef UNIX signal_watcher_start(&data->cont_handle, sigcont_cb, SIGCONT); #endif + uv_timer_init(&data->loop->uv, &data->after_startup_timer); + data->after_startup_timer.data = ui; #if TERMKEY_VERSION_MAJOR > 0 || TERMKEY_VERSION_MINOR > 18 data->input.tk_ti_hook_fn = tui_tk_ti_getstr; @@ -367,6 +367,7 @@ static void tui_main(UIBridgeData *bridge, UI *ui) loop_poll_events(&tui_loop, -1); // tui_loop.events is never processed } + uv_close((uv_handle_t *)&data->after_startup_timer, NULL); ui_bridge_stopped(bridge); term_input_destroy(&data->input); signal_watcher_stop(&data->cont_handle); diff --git a/src/nvim/ui.c b/src/nvim/ui.c index 5c8e9380db..81da88c54a 100644 --- a/src/nvim/ui.c +++ b/src/nvim/ui.c @@ -138,14 +138,6 @@ void ui_builtin_start(void) #endif } -/// Immediately after VimEnter event. -void ui_builtin_after_startup(void) -{ -#ifdef FEAT_TUI - UI_CALL(after_startup); -#endif -} - void ui_builtin_stop(void) { UI_CALL(stop); diff --git a/src/nvim/ui.h b/src/nvim/ui.h index 84d17e9ec2..0e40a1a215 100644 --- a/src/nvim/ui.h +++ b/src/nvim/ui.h @@ -36,7 +36,6 @@ struct ui_t { #endif void (*event)(UI *ui, char *name, Array args, bool *args_consumed); void (*stop)(UI *ui); - void (*after_startup)(UI *ui); }; #ifdef INCLUDE_GENERATED_DECLARATIONS diff --git a/src/nvim/ui_bridge.c b/src/nvim/ui_bridge.c index 324d821df5..0a69cf0ecb 100644 --- a/src/nvim/ui_bridge.c +++ b/src/nvim/ui_bridge.c @@ -42,7 +42,6 @@ UI *ui_bridge_attach(UI *ui, ui_main_fn ui_main, event_scheduler scheduler) rv->ui = ui; rv->bridge.rgb = ui->rgb; rv->bridge.stop = ui_bridge_stop; - rv->bridge.after_startup = ui_bridge_after_startup; rv->bridge.resize = ui_bridge_resize; rv->bridge.clear = ui_bridge_clear; rv->bridge.eol_clear = ui_bridge_eol_clear; @@ -107,16 +106,6 @@ static void ui_thread_run(void *data) bridge->ui_main(bridge, bridge->ui); } -static void ui_bridge_after_startup(UI *b) -{ - UI_BRIDGE_CALL(b, after_startup, 1, b); -} -static void ui_bridge_after_startup_event(void **argv) -{ - UI *ui = UI(argv[0]); - ui->after_startup(ui); -} - static void ui_bridge_stop(UI *b) { UIBridgeData *bridge = (UIBridgeData *)b; -- cgit From 7164f618504bb1b3374da1bcc26c6cf2ac8b3f86 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Wed, 13 Dec 2017 22:22:02 +0100 Subject: typval_encode.c.h: avoid -Wnonnull-compare warning (#7712) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * typval_encode.c.h: avoid -Wnonnull-compare warning closes #6847 The NULL check is needed because TYPVAL_ENCODE_CONV_EMPTY_DICT may be invoked with literal `NULL`. Warning occurs even for `Debug` build-type: neovim/src/nvim/eval/typval.c: In function ‘_typval_encode_nothing_convert_one_value’: neovim/src/nvim/eval/typval.c:1802:10: warning: nonnull argument ‘tv’ compared to NULL [-Wnonnull-compare] if (tv != NULL) { \ ^ ../src/nvim/eval/typval_encode.c.h:398:9: note: in expansion of macro ‘TYPVAL_ENCODE_CONV_EMPTY_DICT’ TYPVAL_ENCODE_CONV_EMPTY_DICT(tv, tv->vval.v_dict); ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~ gcc version: gcc (Ubuntu 6.3.0-12ubuntu2) 6.3.0 20170406 * fixup! typval_encode.c.h: avoid -Wnonnull-compare warning --- src/nvim/eval/typval.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 4bc3a85efb..b5382d1ccb 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -1794,14 +1794,20 @@ static inline void _nothing_conv_func_end(typval_T *const tv, const int copyID) tv->v_lock = VAR_UNLOCKED; \ } while (0) +static inline void _nothing_conv_empty_dict(typval_T *const tv, + dict_T **const dictp) + FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_NONNULL_ARG(2) +{ + tv_dict_unref(*dictp); + *dictp = NULL; + if (tv != NULL) { + tv->v_lock = VAR_UNLOCKED; + } +} #define TYPVAL_ENCODE_CONV_EMPTY_DICT(tv, dict) \ do { \ assert((void *)&dict != (void *)&TYPVAL_ENCODE_NODICT_VAR); \ - tv_dict_unref((dict_T *)dict); \ - *((dict_T **)&dict) = NULL; \ - if (tv != NULL) { \ - ((typval_T *)tv)->v_lock = VAR_UNLOCKED; \ - } \ + _nothing_conv_empty_dict(tv, ((dict_T **)&dict)); \ } while (0) static inline int _nothing_conv_real_list_after_start( -- cgit From c8a5d6181b19009e170a3497a30ce35cf288bddf Mon Sep 17 00:00:00 2001 From: ZyX Date: Fri, 15 Dec 2017 02:39:46 +0300 Subject: *: Fix some problems found during review Still missing: problems in window.c, it should be possible to construct a test for them. --- src/nvim/eval/encode.c | 2 +- src/nvim/eval/typval.c | 16 +++++---- test/functional/eval/interrupt_spec.lua | 61 +++++++++++++++++++++++++++++++++ 3 files changed, 71 insertions(+), 8 deletions(-) create mode 100644 test/functional/eval/interrupt_spec.lua diff --git a/src/nvim/eval/encode.c b/src/nvim/eval/encode.c index 9f16b78976..39897aa9ab 100644 --- a/src/nvim/eval/encode.c +++ b/src/nvim/eval/encode.c @@ -209,7 +209,7 @@ bool encode_vim_list_to_buf(const list_T *const list, size_t *const ret_len, return false; } len++; - if (TV_LIST_ITEM_TV(li)->vval.v_string != 0) { + if (TV_LIST_ITEM_TV(li)->vval.v_string != NULL) { len += STRLEN(TV_LIST_ITEM_TV(li)->vval.v_string); } }); diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 0c9c4a0347..5040695b09 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -406,9 +406,7 @@ void tv_list_append_list(list_T *const list, list_T *const itemlist) TV_LIST_ITEM_TV(li)->v_lock = VAR_UNLOCKED; TV_LIST_ITEM_TV(li)->vval.v_list = itemlist; tv_list_append(list, li); - if (itemlist != NULL) { - tv_list_ref(itemlist); - } + tv_list_ref(itemlist); } /// Append a dictionary to a list @@ -510,6 +508,9 @@ list_T *tv_list_copy(const vimconv_T *const conv, list_T *const orig, orig->lv_copylist = copy; } TV_LIST_ITER(orig, item, { + if (got_int) { + break; + } listitem_T *const ni = tv_list_item_alloc(); if (deep) { if (var_item_copy(conv, TV_LIST_ITEM_TV(item), TV_LIST_ITEM_TV(ni), @@ -605,6 +606,9 @@ static int list_join_inner(garray_T *const gap, list_T *const l, // Stringify each item in the list. TV_LIST_ITER(l, item, { + if (got_int) { + break; + } char *s; size_t len; s = encode_tv2echo(TV_LIST_ITEM_TV(item), &len); @@ -697,7 +701,7 @@ bool tv_list_equal(list_T *const l1, list_T *const l2, const bool ic, listitem_T *item2 = tv_list_first(l2); for (; item1 != NULL && item2 != NULL ; (item1 = TV_LIST_ITEM_NEXT(l1, item1), - item2 = TV_LIST_ITEM_NEXT(n2, item2))) { + item2 = TV_LIST_ITEM_NEXT(l2, item2))) { if (!tv_equal(TV_LIST_ITEM_TV(item1), TV_LIST_ITEM_TV(item2), ic, recursive)) { return false; @@ -2108,9 +2112,7 @@ void tv_copy(typval_T *const from, typval_T *const to) break; } case VAR_LIST: { - if (from->vval.v_list != NULL) { - tv_list_ref(to->vval.v_list); - } + tv_list_ref(to->vval.v_list); break; } case VAR_DICT: { diff --git a/test/functional/eval/interrupt_spec.lua b/test/functional/eval/interrupt_spec.lua new file mode 100644 index 0000000000..7f4ca95317 --- /dev/null +++ b/test/functional/eval/interrupt_spec.lua @@ -0,0 +1,61 @@ +local helpers = require('test.functional.helpers')(after_each) + +local command = helpers.command +local meths = helpers.meths +local clear = helpers.clear +local sleep = helpers.sleep +local wait = helpers.wait +local feed = helpers.feed +local eq = helpers.eq + +local dur +local min_dur = 8 +local len = 131072 + +describe('List support code', function() + if not pending('does not actually allows interrupting with just got_int', function() end) then return end + -- The following tests are confirmed to work with os_breakcheck() just before + -- `if (got_int) {break;}` in tv_list_copy and list_join_inner() and not to + -- work without. + setup(function() + clear() + dur = 0 + while true do + command(([[ + let rt = reltime() + let bl = range(%u) + let dur = reltimestr(reltime(rt)) + ]]):format(len)) + dur = tonumber(meths.get_var('dur')) + if dur >= min_dur then + -- print(('Using len %u, dur %g'):format(len, dur)) + break + else + len = len * 2 + end + end + end) + it('allows interrupting copy', function() + feed(':let t_rt = reltime():let t_bl = copy(bl)') + sleep(min_dur / 16 * 1000) + feed('') + wait() + command('let t_dur = reltimestr(reltime(t_rt))') + local t_dur = tonumber(meths.get_var('t_dur')) + if t_dur >= dur / 8 then + eq(nil, ('Took too long to cancel: %g >= %g'):format(t_dur, dur / 8)) + end + end) + it('allows interrupting join', function() + feed(':let t_rt = reltime():let t_j = join(bl)') + sleep(min_dur / 16 * 1000) + feed('') + wait() + command('let t_dur = reltimestr(reltime(t_rt))') + local t_dur = tonumber(meths.get_var('t_dur')) + print(('t_dur: %g'):format(t_dur)) + if t_dur >= dur / 8 then + eq(nil, ('Took too long to cancel: %g >= %g'):format(t_dur, dur / 8)) + end + end) +end) -- cgit From fb07391ce46b8bff90ef7ef073b75919a307e64c Mon Sep 17 00:00:00 2001 From: ZyX Date: Fri, 15 Dec 2017 11:38:34 +0300 Subject: window: Fix matchaddpos() and enhance error reporting --- runtime/doc/eval.txt | 6 +- src/nvim/eval.c | 82 +++++++++++------------ src/nvim/window.c | 20 +++--- test/functional/eval/match_functions_spec.lua | 95 +++++++++++++++++++++++++++ 4 files changed, 153 insertions(+), 50 deletions(-) diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index e337c5d6d5..6c3fd43db0 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -5493,7 +5493,7 @@ matchaddpos({group}, {pos} [, {priority} [, {id} [, {dict}]]]) sets buffer line boundaries to redraw screen. It is supposed to be used when fast match additions and deletions are required, for example to highlight matching parentheses. - + *E5030* *E5031* The list {pos} can contain one of these items: - A number. This whole line will be highlighted. The first line has number 1. @@ -5507,6 +5507,10 @@ matchaddpos({group}, {pos} [, {priority} [, {id} [, {dict}]]]) - A list with three numbers, e.g., [23, 11, 3]. As above, but the third number gives the length of the highlight in bytes. + Entries with zero and negative line numbers are silently + ignored, as well as entries with negative column numbers and + lengths. + The maximum number of positions is 8. Example: > diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 37356d43b4..640967b91c 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -12440,56 +12440,56 @@ static void f_matchadd(typval_T *argvars, typval_T *rettv, FunPtr fptr) static void f_matchaddpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - rettv->vval.v_number = -1; + rettv->vval.v_number = -1; - char buf[NUMBUFLEN]; - const char *const group = tv_get_string_buf_chk(&argvars[0], buf); - if (group == NULL) { - return; - } + char buf[NUMBUFLEN]; + const char *const group = tv_get_string_buf_chk(&argvars[0], buf); + if (group == NULL) { + return; + } - if (argvars[1].v_type != VAR_LIST) { - EMSG2(_(e_listarg), "matchaddpos()"); - return; - } + if (argvars[1].v_type != VAR_LIST) { + EMSG2(_(e_listarg), "matchaddpos()"); + return; + } - list_T *l; - l = argvars[1].vval.v_list; - if (l == NULL) { - return; - } + list_T *l; + l = argvars[1].vval.v_list; + if (l == NULL) { + return; + } - bool error = false; - int prio = 10; - int id = -1; - const char *conceal_char = NULL; + bool error = false; + int prio = 10; + int id = -1; + const char *conceal_char = NULL; - if (argvars[2].v_type != VAR_UNKNOWN) { - prio = tv_get_number_chk(&argvars[2], &error); - if (argvars[3].v_type != VAR_UNKNOWN) { - id = tv_get_number_chk(&argvars[3], &error); - if (argvars[4].v_type != VAR_UNKNOWN) { - if (argvars[4].v_type != VAR_DICT) { - EMSG(_(e_dictreq)); - return; - } - dictitem_T *di; - if ((di = tv_dict_find(argvars[4].vval.v_dict, S_LEN("conceal"))) - != NULL) { - conceal_char = tv_get_string(&di->di_tv); - } + if (argvars[2].v_type != VAR_UNKNOWN) { + prio = tv_get_number_chk(&argvars[2], &error); + if (argvars[3].v_type != VAR_UNKNOWN) { + id = tv_get_number_chk(&argvars[3], &error); + if (argvars[4].v_type != VAR_UNKNOWN) { + if (argvars[4].v_type != VAR_DICT) { + EMSG(_(e_dictreq)); + return; + } + dictitem_T *di; + if ((di = tv_dict_find(argvars[4].vval.v_dict, S_LEN("conceal"))) + != NULL) { + conceal_char = tv_get_string(&di->di_tv); } } } - if (error == true) { - return; - } + } + if (error == true) { + return; + } - // id == 3 is ok because matchaddpos() is supposed to substitute :3match - if (id == 1 || id == 2) { - EMSGN(_("E798: ID is reserved for \"match\": %" PRId64), id); - return; - } + // id == 3 is ok because matchaddpos() is supposed to substitute :3match + if (id == 1 || id == 2) { + EMSGN(_("E798: ID is reserved for \"match\": %" PRId64), id); + return; + } rettv->vval.v_number = match_add(curwin, group, NULL, prio, id, l, conceal_char); diff --git a/src/nvim/window.c b/src/nvim/window.c index 28cb9449d1..583314d437 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -5619,19 +5619,17 @@ int match_add(win_T *wp, const char *const grp, const char *const pat, if (TV_LIST_ITEM_TV(li)->v_type == VAR_LIST) { const list_T *const subl = TV_LIST_ITEM_TV(li)->vval.v_list; - if (subl == NULL) { - goto fail; - } const listitem_T *subli = tv_list_first(subl); if (subli == NULL) { + emsgf(_("E5030: Empty list at position %d"), + (int)tv_list_idx_of_item(pos_list, li)); goto fail; } lnum = tv_get_number_chk(TV_LIST_ITEM_TV(subli), &error); if (error) { goto fail; } - if (lnum == 0) { - --i; + if (lnum <= 0) { continue; } m->pos.pos[i].lnum = lnum; @@ -5641,9 +5639,15 @@ int match_add(win_T *wp, const char *const grp, const char *const pat, if (error) { goto fail; } + if (col < 0) { + continue; + } subli = TV_LIST_ITEM_NEXT(subl, subli); if (subli != NULL) { len = tv_get_number_chk(TV_LIST_ITEM_TV(subli), &error); + if (len < 0) { + continue; + } if (error) { goto fail; } @@ -5652,15 +5656,15 @@ int match_add(win_T *wp, const char *const grp, const char *const pat, m->pos.pos[i].col = col; m->pos.pos[i].len = len; } else if (TV_LIST_ITEM_TV(li)->v_type == VAR_NUMBER) { - if (TV_LIST_ITEM_TV(li)->vval.v_number == 0) { - i--; + if (TV_LIST_ITEM_TV(li)->vval.v_number <= 0) { continue; } m->pos.pos[i].lnum = TV_LIST_ITEM_TV(li)->vval.v_number; m->pos.pos[i].col = 0; m->pos.pos[i].len = 0; } else { - EMSG(_("List or number required")); + emsgf(_("E5031: List or number required at position %d"), + (int)tv_list_idx_of_item(pos_list, li)); goto fail; } if (toplnum == 0 || lnum < toplnum) { diff --git a/test/functional/eval/match_functions_spec.lua b/test/functional/eval/match_functions_spec.lua index 3150d89f62..0dc78de55e 100644 --- a/test/functional/eval/match_functions_spec.lua +++ b/test/functional/eval/match_functions_spec.lua @@ -1,9 +1,12 @@ local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') local eq = helpers.eq local clear = helpers.clear local funcs = helpers.funcs +local meths = helpers.meths local command = helpers.command +local exc_exec = helpers.exc_exec before_each(clear) @@ -59,3 +62,95 @@ describe('matchadd()', function() }}, funcs.getmatches()) end) end) + +describe('matchaddpos()', function() + it('errors out on invalid input', function() + command('hi clear PreProc') + eq('Vim(let):E5030: Empty list at position 0', + exc_exec('let val = matchaddpos("PreProc", [[]])')) + eq('Vim(let):E5030: Empty list at position 1', + exc_exec('let val = matchaddpos("PreProc", [1, v:_null_list])')) + eq('Vim(let):E5031: List or number required at position 1', + exc_exec('let val = matchaddpos("PreProc", [1, v:_null_dict])')) + end) + it('works with 0 lnum', function() + command('hi clear PreProc') + eq(4, funcs.matchaddpos('PreProc', {1}, 3, 4)) + eq({{ + group='PreProc', + pos1 = {1}, + priority=3, + id=4, + }}, funcs.getmatches()) + funcs.matchdelete(4) + eq(4, funcs.matchaddpos('PreProc', {{0}, 1}, 3, 4)) + eq({{ + group='PreProc', + pos1 = {1}, + priority=3, + id=4, + }}, funcs.getmatches()) + funcs.matchdelete(4) + eq(4, funcs.matchaddpos('PreProc', {0, 1}, 3, 4)) + eq({{ + group='PreProc', + pos1 = {1}, + priority=3, + id=4, + }}, funcs.getmatches()) + end) + it('works with negative numbers', function() + command('hi clear PreProc') + eq(4, funcs.matchaddpos('PreProc', {-10, 1}, 3, 4)) + eq({{ + group='PreProc', + pos1 = {1}, + priority=3, + id=4, + }}, funcs.getmatches()) + funcs.matchdelete(4) + eq(4, funcs.matchaddpos('PreProc', {{-10}, 1}, 3, 4)) + eq({{ + group='PreProc', + pos1 = {1}, + priority=3, + id=4, + }}, funcs.getmatches()) + funcs.matchdelete(4) + eq(4, funcs.matchaddpos('PreProc', {{2, -1}, 1}, 3, 4)) + eq({{ + group='PreProc', + pos1 = {1}, + priority=3, + id=4, + }}, funcs.getmatches()) + funcs.matchdelete(4) + eq(4, funcs.matchaddpos('PreProc', {{2, 0, -1}, 1}, 3, 4)) + eq({{ + group='PreProc', + pos1 = {1}, + priority=3, + id=4, + }}, funcs.getmatches()) + end) + it('works with zero length', function() + local screen = Screen.new(40, 5) + screen:attach() + funcs.setline(1, 'abcdef') + command('hi PreProc guifg=Red') + eq(4, funcs.matchaddpos('PreProc', {{1, 2, 0}}, 3, 4)) + eq({{ + group='PreProc', + pos1 = {1, 2, 0}, + priority=3, + id=4, + }}, funcs.getmatches()) + screen:expect([[ + ^a{1:b}cdef | + {2:~ }| + {2:~ }| + {2:~ }| + | + ]], {[1] = {foreground = Screen.colors.Red}, [2] = {bold = true, foreground = Screen.colors.Blue1}}) + end) +end) -- cgit From d5bce42b524708a54243658e87b1e3bd9c7acdf3 Mon Sep 17 00:00:00 2001 From: Michael Schupikov Date: Sat, 23 Sep 2017 09:56:44 +0200 Subject: vim-patch:8.0.0074 Problem: Cannot make Vim fail on an internal error. Solution: Add IEMSG() and IEMSG2(). (Domenique Pelle) Avoid reporting an internal error without mentioning where. https://github.com/vim/vim/commit/95f096030ed1a8afea028f2ea295d6f6a70f466f Signed-off-by: Michael Schupikov --- src/nvim/edit.c | 4 ++-- src/nvim/eval.c | 25 +++++++++++----------- src/nvim/eval/encode.c | 2 +- src/nvim/eval/typval_encode.c.h | 2 +- src/nvim/ex_eval.c | 14 ++++++------ src/nvim/getchar.c | 29 +++++++++++++------------ src/nvim/globals.h | 2 +- src/nvim/hashtab.c | 2 +- src/nvim/memfile.c | 5 +++-- src/nvim/memline.c | 39 +++++++++++++++++----------------- src/nvim/message.c | 47 ++++++++++++++++++++++++++++++++++++++--- src/nvim/message.h | 9 ++++++++ src/nvim/option.c | 8 +++---- src/nvim/os/env.c | 2 +- src/nvim/quickfix.c | 2 +- src/nvim/regexp.c | 11 +++++----- src/nvim/spell.c | 2 +- src/nvim/undo.c | 20 +++++++++--------- src/nvim/version.c | 2 +- src/nvim/window.c | 2 +- 20 files changed, 144 insertions(+), 85 deletions(-) diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 859f98d2ad..4f0a3eaf34 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -1927,7 +1927,7 @@ bool vim_is_ctrl_x_key(int c) case CTRL_X_EVAL: return (c == Ctrl_P || c == Ctrl_N); } - EMSG(_(e_internal)); + internal_error("vim_is_ctrl_x_key()"); return false; } @@ -4681,7 +4681,7 @@ static int ins_complete(int c, bool enable_pum) line = ml_get(curwin->w_cursor.lnum); compl_pattern = vim_strnsave(line + compl_col, compl_length); } else { - EMSG2(_(e_intern2), "ins_complete()"); + internal_error("ins_complete()"); return FAIL; } diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 56aedb1b4e..7fa9f7563e 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -1102,10 +1102,11 @@ static void restore_vimvar(int idx, typval_T *save_tv) vimvars[idx].vv_tv = *save_tv; if (vimvars[idx].vv_type == VAR_UNKNOWN) { hi = hash_find(&vimvarht, vimvars[idx].vv_di.di_key); - if (HASHITEM_EMPTY(hi)) - EMSG2(_(e_intern2), "restore_vimvar()"); - else + if (HASHITEM_EMPTY(hi)) { + internal_error("restore_vimvar()"); + } else { hash_remove(&vimvarht, hi); + } } } @@ -1567,7 +1568,7 @@ ex_let_vars ( } break; } else if (*arg != ',' && *arg != ']') { - EMSG2(_(e_intern2), "ex_let_vars()"); + internal_error("ex_let_vars()"); return FAIL; } } @@ -2960,7 +2961,7 @@ int do_unlet(const char *const name, const size_t name_len, const int forceit) d = di->di_tv.vval.v_dict; } if (d == NULL) { - EMSG2(_(e_intern2), "do_unlet()"); + internal_error("do_unlet()"); return FAIL; } hashitem_T *hi = hash_find(ht, (const char_u *)varname); @@ -7959,7 +7960,7 @@ static void f_empty(typval_T *argvars, typval_T *rettv, FunPtr fptr) n = argvars[0].vval.v_special != kSpecialVarTrue; break; case VAR_UNKNOWN: - EMSG2(_(e_intern2), "f_empty(UNKNOWN)"); + internal_error("f_empty(UNKNOWN)"); break; } @@ -17151,7 +17152,7 @@ static void f_type(typval_T *argvars, typval_T *rettv, FunPtr fptr) break; } case VAR_UNKNOWN: { - EMSG2(_(e_intern2), "f_type(UNKNOWN)"); + internal_error("f_type(UNKNOWN)"); break; } } @@ -19030,7 +19031,7 @@ static void set_var(const char *name, const size_t name_len, typval_T *const tv, } return; } else if (v->di_tv.v_type != tv->v_type) { - EMSG2(_(e_intern2), "set_var()"); + internal_error("set_var()"); } } @@ -19297,7 +19298,7 @@ int var_item_copy(const vimconv_T *const conv, } break; case VAR_UNKNOWN: - EMSG2(_(e_intern2), "var_item_copy(UNKNOWN)"); + internal_error("var_item_copy(UNKNOWN)"); ret = FAIL; } --recurse; @@ -20985,11 +20986,11 @@ void func_unref(char_u *name) if (fp == NULL && isdigit(*name)) { #ifdef EXITFREE if (!entered_free_all_mem) { - EMSG2(_(e_intern2), "func_unref()"); + internal_error("func_unref()"); abort(); } #else - EMSG2(_(e_intern2), "func_unref()"); + internal_error("func_unref()"); abort(); #endif } @@ -21028,7 +21029,7 @@ void func_ref(char_u *name) } else if (isdigit(*name)) { // Only give an error for a numbered function. // Fail silently, when named or lambda function isn't found. - EMSG2(_(e_intern2), "func_ref()"); + internal_error("func_ref()"); } } diff --git a/src/nvim/eval/encode.c b/src/nvim/eval/encode.c index ef647b3ee4..1607d2139a 100644 --- a/src/nvim/eval/encode.c +++ b/src/nvim/eval/encode.c @@ -340,7 +340,7 @@ int encode_read_from_list(ListReaderState *const state, char *const buf, do { \ const char *const fun_ = (const char *)(fun); \ if (fun_ == NULL) { \ - EMSG2(_(e_intern2), "string(): NULL function name"); \ + internal_error("string(): NULL function name"); \ ga_concat(gap, "function(NULL"); \ } else { \ ga_concat(gap, "function("); \ diff --git a/src/nvim/eval/typval_encode.c.h b/src/nvim/eval/typval_encode.c.h index a93ad2dbba..df820f664c 100644 --- a/src/nvim/eval/typval_encode.c.h +++ b/src/nvim/eval/typval_encode.c.h @@ -600,7 +600,7 @@ _convert_one_value_regular_dict: {} break; } case VAR_UNKNOWN: { - EMSG2(_(e_intern2), STR(_TYPVAL_ENCODE_CONVERT_ONE_VALUE) "()"); + internal_error(STR(_TYPVAL_ENCODE_CONVERT_ONE_VALUE) "()"); return FAIL; } } diff --git a/src/nvim/ex_eval.c b/src/nvim/ex_eval.c index 9037b3c151..4010a088c8 100644 --- a/src/nvim/ex_eval.c +++ b/src/nvim/ex_eval.c @@ -517,7 +517,7 @@ static void discard_exception(except_T *excp, int was_finished) char_u *saved_IObuff; if (excp == NULL) { - EMSG(_(e_internal)); + internal_error("discard_exception()"); return; } @@ -619,8 +619,9 @@ static void catch_exception(except_T *excp) */ static void finish_exception(except_T *excp) { - if (excp != caught_stack) - EMSG(_(e_internal)); + if (excp != caught_stack) { + internal_error("finish_exception()"); + } caught_stack = caught_stack->caught; if (caught_stack != NULL) { set_vim_var_string(VV_EXCEPTION, (char *) caught_stack->value, -1); @@ -1422,8 +1423,9 @@ void ex_catch(exarg_T *eap) * ":endtry" or when the catch clause is left by a ":continue", * ":break", ":return", ":finish", error, interrupt, or another * exception. */ - if (cstack->cs_exception[cstack->cs_idx] != current_exception) - EMSG(_(e_internal)); + if (cstack->cs_exception[cstack->cs_idx] != current_exception) { + internal_error("ex_catch()"); + } } else { /* * If there is a preceding catch clause and it caught the exception, @@ -1547,7 +1549,7 @@ void ex_finally(exarg_T *eap) * exception will be discarded. */ if (did_throw && cstack->cs_exception[cstack->cs_idx] != current_exception) - EMSG(_(e_internal)); + internal_error("ex_finally()"); } /* diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index 4f8a8528a0..1b5d3472ab 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -254,16 +254,17 @@ static void add_buff(buffheader_T *const buf, const char *const s, return; } - if (buf->bh_first.b_next == NULL) { /* first add to list */ + if (buf->bh_first.b_next == NULL) { // first add to list buf->bh_space = 0; buf->bh_curr = &(buf->bh_first); - } else if (buf->bh_curr == NULL) { /* buffer has already been read */ - EMSG(_("E222: Add to read buffer")); + } else if (buf->bh_curr == NULL) { // buffer has already been read + IEMSG(_("E222: Add to read buffer")); return; - } else if (buf->bh_index != 0) + } else if (buf->bh_index != 0) { memmove(buf->bh_first.b_next->b_str, - buf->bh_first.b_next->b_str + buf->bh_index, - STRLEN(buf->bh_first.b_next->b_str + buf->bh_index) + 1); + buf->bh_first.b_next->b_str + buf->bh_index, + STRLEN(buf->bh_first.b_next->b_str + buf->bh_index) + 1); + } buf->bh_index = 0; size_t len; @@ -1152,14 +1153,16 @@ void alloc_typebuf(void) */ void free_typebuf(void) { - if (typebuf.tb_buf == typebuf_init) - EMSG2(_(e_intern2), "Free typebuf 1"); - else + if (typebuf.tb_buf == typebuf_init) { + internal_error("Free typebuf 1"); + } else { xfree(typebuf.tb_buf); - if (typebuf.tb_noremap == noremapbuf_init) - EMSG2(_(e_intern2), "Free typebuf 2"); - else + } + if (typebuf.tb_noremap == noremapbuf_init) { + internal_error("Free typebuf 2"); + } else { xfree(typebuf.tb_noremap); + } } /* @@ -3905,7 +3908,7 @@ makemap ( c1 = 't'; break; default: - EMSG(_("E228: makemap: Illegal mode")); + IEMSG(_("E228: makemap: Illegal mode")); return FAIL; } do { /* do this twice if c2 is set, 3 times with c3 */ diff --git a/src/nvim/globals.h b/src/nvim/globals.h index ef56954aa0..dd216f177f 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -1043,6 +1043,7 @@ EXTERN char_u e_for[] INIT(= N_("E588: :endfor without :for")); EXTERN char_u e_exists[] INIT(= N_("E13: File exists (add ! to override)")); EXTERN char_u e_failed[] INIT(= N_("E472: Command failed")); EXTERN char_u e_internal[] INIT(= N_("E473: Internal error")); +EXTERN char_u e_intern2[] INIT(= N_("E685: Internal error: %s")); EXTERN char_u e_interr[] INIT(= N_("Interrupted")); EXTERN char_u e_invaddr[] INIT(= N_("E14: Invalid address")); EXTERN char_u e_invarg[] INIT(= N_("E474: Invalid argument")); @@ -1134,7 +1135,6 @@ EXTERN char_u e_write[] INIT(= N_("E80: Error while writing")); EXTERN char_u e_zerocount[] INIT(= N_("E939: Positive count required")); EXTERN char_u e_usingsid[] INIT(= N_( "E81: Using not in a script context")); -EXTERN char_u e_intern2[] INIT(= N_("E685: Internal error: %s")); EXTERN char_u e_maxmempat[] INIT(= N_( "E363: pattern uses more memory than 'maxmempattern'")); EXTERN char_u e_emptybuf[] INIT(= N_("E749: empty buffer")); diff --git a/src/nvim/hashtab.c b/src/nvim/hashtab.c index 3397788b00..526bc284a4 100644 --- a/src/nvim/hashtab.c +++ b/src/nvim/hashtab.c @@ -208,7 +208,7 @@ int hash_add(hashtab_T *ht, char_u *key) hash_T hash = hash_hash(key); hashitem_T *hi = hash_lookup(ht, (const char *)key, STRLEN(key), hash); if (!HASHITEM_EMPTY(hi)) { - EMSG2(_(e_intern2), "hash_add()"); + internal_error("hash_add()"); return FAIL; } hash_add_item(ht, hi, key, hash); diff --git a/src/nvim/memfile.c b/src/nvim/memfile.c index 4428dd42aa..4eeba12b87 100644 --- a/src/nvim/memfile.c +++ b/src/nvim/memfile.c @@ -376,8 +376,9 @@ void mf_put(memfile_T *mfp, bhdr_T *hp, bool dirty, bool infile) { unsigned flags = hp->bh_flags; - if ((flags & BH_LOCKED) == 0) - EMSG(_("E293: block was not locked")); + if ((flags & BH_LOCKED) == 0) { + IEMSG(_("E293: block was not locked")); + } flags &= ~BH_LOCKED; if (dirty) { flags |= BH_DIRTY; diff --git a/src/nvim/memline.c b/src/nvim/memline.c index f28a9e60f4..fde21f7992 100644 --- a/src/nvim/memline.c +++ b/src/nvim/memline.c @@ -293,7 +293,7 @@ int ml_open(buf_T *buf) */ hp = mf_new(mfp, false, 1); if (hp->bh_bnum != 0) { - EMSG(_("E298: Didn't get block nr 0?")); + IEMSG(_("E298: Didn't get block nr 0?")); goto error; } b0p = hp->bh_data; @@ -335,7 +335,7 @@ int ml_open(buf_T *buf) if ((hp = ml_new_ptr(mfp)) == NULL) goto error; if (hp->bh_bnum != 1) { - EMSG(_("E298: Didn't get block nr 1?")); + IEMSG(_("E298: Didn't get block nr 1?")); goto error; } pp = hp->bh_data; @@ -351,7 +351,7 @@ int ml_open(buf_T *buf) */ hp = ml_new_data(mfp, FALSE, 1); if (hp->bh_bnum != 2) { - EMSG(_("E298: Didn't get block nr 2?")); + IEMSG(_("E298: Didn't get block nr 2?")); goto error; } @@ -635,13 +635,14 @@ static void ml_upd_block0(buf_T *buf, upd_block0_T what) if (mfp == NULL || (hp = mf_get(mfp, 0, 1)) == NULL) return; b0p = hp->bh_data; - if (ml_check_b0_id(b0p) == FAIL) - EMSG(_("E304: ml_upd_block0(): Didn't get block 0??")); - else { - if (what == UB_FNAME) + if (ml_check_b0_id(b0p) == FAIL) { + IEMSG(_("E304: ml_upd_block0(): Didn't get block 0??")); + } else { + if (what == UB_FNAME) { set_b0_fname(b0p, buf); - else /* what == UB_SAME_DIR */ + } else { // what == UB_SAME_DIR set_b0_dir_flag(b0p, buf); + } } mf_put(mfp, hp, true, false); } @@ -1745,7 +1746,7 @@ ml_get_buf ( /* Avoid giving this message for a recursive call, may happen when * the GUI redraws part of the text. */ ++recursive; - EMSGN(_("E315: ml_get: invalid lnum: %" PRId64), lnum); + IEMSGN(_("E315: ml_get: invalid lnum: %" PRId64), lnum); --recursive; } errorret: @@ -1777,7 +1778,7 @@ errorret: /* Avoid giving this message for a recursive call, may happen * when the GUI redraws part of the text. */ ++recursive; - EMSGN(_("E316: ml_get: cannot find line %" PRId64), lnum); + IEMSGN(_("E316: ml_get: cannot find line %" PRId64), lnum); --recursive; } goto errorret; @@ -2162,7 +2163,7 @@ ml_append_int ( return FAIL; pp = hp->bh_data; /* must be pointer block */ if (pp->pb_id != PTR_ID) { - EMSG(_("E317: pointer block id wrong 3")); + IEMSG(_("E317: pointer block id wrong 3")); mf_put(mfp, hp, false, false); return FAIL; } @@ -2295,8 +2296,8 @@ ml_append_int ( * Safety check: fallen out of for loop? */ if (stack_idx < 0) { - EMSG(_("E318: Updated too many blocks?")); - buf->b_ml.ml_stack_top = 0; /* invalidate stack */ + IEMSG(_("E318: Updated too many blocks?")); + buf->b_ml.ml_stack_top = 0; // invalidate stack } } @@ -2435,7 +2436,7 @@ static int ml_delete_int(buf_T *buf, linenr_T lnum, int message) return FAIL; pp = hp->bh_data; /* must be pointer block */ if (pp->pb_id != PTR_ID) { - EMSG(_("E317: pointer block id wrong 4")); + IEMSG(_("E317: pointer block id wrong 4")); mf_put(mfp, hp, false, false); return FAIL; } @@ -2631,7 +2632,7 @@ static void ml_flush_line(buf_T *buf) hp = ml_find_line(buf, lnum, ML_FIND); if (hp == NULL) - EMSGN(_("E320: Cannot find line %" PRId64), lnum); + IEMSGN(_("E320: Cannot find line %" PRId64), lnum); else { dp = hp->bh_data; idx = lnum - buf->b_ml.ml_locked_low; @@ -2841,7 +2842,7 @@ static bhdr_T *ml_find_line(buf_T *buf, linenr_T lnum, int action) pp = (PTR_BL *)(dp); /* must be pointer block */ if (pp->pb_id != PTR_ID) { - EMSG(_("E317: pointer block id wrong")); + IEMSG(_("E317: pointer block id wrong")); goto error_block; } @@ -2880,11 +2881,11 @@ static bhdr_T *ml_find_line(buf_T *buf, linenr_T lnum, int action) } if (idx >= (int)pp->pb_count) { /* past the end: something wrong! */ if (lnum > buf->b_ml.ml_line_count) - EMSGN(_("E322: line number out of range: %" PRId64 " past the end"), + IEMSGN(_("E322: line number out of range: %" PRId64 " past the end"), lnum - buf->b_ml.ml_line_count); else - EMSGN(_("E323: line count wrong in block %" PRId64), bnum); + IEMSGN(_("E323: line count wrong in block %" PRId64), bnum); goto error_block; } if (action == ML_DELETE) { @@ -2960,7 +2961,7 @@ static void ml_lineadd(buf_T *buf, int count) pp = hp->bh_data; /* must be pointer block */ if (pp->pb_id != PTR_ID) { mf_put(mfp, hp, false, false); - EMSG(_("E317: pointer block id wrong 2")); + IEMSG(_("E317: pointer block id wrong 2")); break; } pp->pb_pointer[ip->ip_index].pe_line_count += count; diff --git a/src/nvim/message.c b/src/nvim/message.c index 5c8f0655bf..1d9a4de9c0 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -582,20 +582,61 @@ void emsg_invreg(int name) /// Print an error message with unknown number of arguments bool emsgf(const char *const fmt, ...) +{ + bool ret; + + va_list ap; + va_start(ap, fmt); + ret = emsgfv(fmt, ap); + va_end(ap); + + return ret; +} + +/// Print an error message with unknown number of arguments +static bool emsgfv(const char *fmt, va_list ap) { static char errbuf[IOSIZE]; if (emsg_not_now()) { return true; } - va_list ap; - va_start(ap, fmt); vim_vsnprintf(errbuf, sizeof(errbuf), fmt, ap, NULL); - va_end(ap); return emsg((const char_u *)errbuf); } +/// Same as emsg(...), but abort on error when ABORT_ON_INTERNAL_ERROR is +/// defined. It is used for internal errors only, so that they can be +/// detected when fuzzing vim. +void iemsg(const char *s) +{ + msg((char_u *)s); +#ifdef ABORT_ON_INTERNAL_ERROR + abort(); +#endif +} + +/// Same as emsgf(...) but abort on error when ABORT_ON_INTERNAL_ERROR is +/// defined. It is used for internal errors only, so that they can be +/// detected when fuzzing vim. +void iemsgf(const char *s, ...) +{ + va_list ap; + va_start(ap, s); + (void)emsgfv(s, ap); + va_end(ap); +#ifdef ABORT_ON_INTERNAL_ERROR + abort(); +#endif +} + +/// Give an "Internal error" message. +void internal_error(char *where) +{ + IEMSG2(_(e_intern2), where); +} + static void msg_emsgf_event(void **argv) { char *s = argv[0]; diff --git a/src/nvim/message.h b/src/nvim/message.h index 904a9d3ca1..82935a36a9 100644 --- a/src/nvim/message.h +++ b/src/nvim/message.h @@ -49,6 +49,15 @@ /// Like #EMSG, but for messages with one "%" PRIu64 inside #define EMSGU(s, n) emsgf((const char *) (s), (uint64_t)(n)) +/// Like #EMSG, but for internal messages +#define IEMSG(s) iemsg((const char *)(s)) + +/// Like #EMSG2, but for internal messages +#define IEMSG2(s, p) iemsgf((const char *)(s), (p)) + +/// Like #EMSGN, but for internal messages +#define IEMSGN(s, n) iemsgf((const char *)(s), (int64_t)(n)) + /// Display message at the recorded position #define MSG_PUTS(s) msg_puts((const char *)(s)) diff --git a/src/nvim/option.c b/src/nvim/option.c index 499cf79836..a345906200 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -2258,7 +2258,7 @@ int was_set_insecurely(char_u *opt, int opt_flags) uint32_t *flagp = insecure_flag(idx, opt_flags); return (*flagp & P_INSECURE) != 0; } - EMSG2(_(e_intern2), "was_set_insecurely()"); + internal_error("was_set_insecurely()"); return -1; } @@ -2317,8 +2317,8 @@ set_string_option_direct ( if (idx == -1) { // Use name. idx = findoption((const char *)name); if (idx < 0) { // Not found (should not happen). - EMSG2(_(e_intern2), "set_string_option_direct()"); - EMSG2(_("For option %s"), name); + internal_error("set_string_option_direct()"); + IEMSG2(_("For option %s"), name); return; } } @@ -5584,7 +5584,7 @@ static char_u *get_varp(vimoption_T *p) case PV_KMAP: return (char_u *)&(curbuf->b_p_keymap); case PV_SCL: return (char_u *)&(curwin->w_p_scl); case PV_WINHL: return (char_u *)&(curwin->w_p_winhl); - default: EMSG(_("E356: get_varp ERROR")); + default: IEMSG(_("E356: get_varp ERROR")); } /* always return a valid pointer to avoid a crash! */ return (char_u *)&(curbuf->b_p_wm); diff --git a/src/nvim/os/env.c b/src/nvim/os/env.c index de0cd10d9c..999fcd434a 100644 --- a/src/nvim/os/env.c +++ b/src/nvim/os/env.c @@ -887,7 +887,7 @@ bool os_setenv_append_path(const char *fname) # define MAX_ENVPATHLEN INT_MAX #endif if (!path_is_absolute_path((char_u *)fname)) { - EMSG2(_(e_intern2), "os_setenv_append_path()"); + internal_error("os_setenv_append_path()"); return false; } const char *tail = (char *)path_tail_with_sep((char_u *)fname); diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 1fc585f0c9..3df025a51d 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -2853,7 +2853,7 @@ static void qf_fill_buffer(qf_info_T *qi, buf_T *buf, qfline_T *old_last) if (old_last == NULL) { if (buf != curbuf) { - EMSG2(_(e_intern2), "qf_fill_buffer()"); + internal_error("qf_fill_buffer()"); return; } diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c index ae611a0005..e921706b3a 100644 --- a/src/nvim/regexp.c +++ b/src/nvim/regexp.c @@ -459,14 +459,15 @@ static int toggle_Magic(int x) /* Used for an error (down from) vim_regcomp(): give the error message, set * rc_did_emsg and return NULL */ #define EMSG_RET_NULL(m) return (EMSG(m), rc_did_emsg = TRUE, (void *)NULL) +#define IEMSG_RET_NULL(m) return (IEMSG(m), rc_did_emsg = true, (void *)NULL) #define EMSG_RET_FAIL(m) return (EMSG(m), rc_did_emsg = TRUE, FAIL) #define EMSG2_RET_NULL(m, \ c) return (EMSG2((m), \ - (c) ? "" : "\\"), rc_did_emsg = TRUE, \ + (c) ? "" : "\\"), rc_did_emsg = true, \ (void *)NULL) #define EMSG2_RET_FAIL(m, \ c) return (EMSG2((m), \ - (c) ? "" : "\\"), rc_did_emsg = TRUE, \ + (c) ? "" : "\\"), rc_did_emsg = true, \ FAIL) #define EMSG_ONE_RET_NULL EMSG2_RET_NULL(_( \ "E369: invalid item in %s%%[]"), reg_magic == MAGIC_ALL) @@ -1891,8 +1892,8 @@ static char_u *regatom(int *flagp) case Magic(')'): if (one_exactly) EMSG_ONE_RET_NULL; - EMSG_RET_NULL(_(e_internal)); /* Supposed to be caught earlier. */ - /* NOTREACHED */ + IEMSG_RET_NULL(_(e_internal)); // Supposed to be caught earlier. + // NOTREACHED case Magic('='): case Magic('?'): @@ -4534,7 +4535,7 @@ regmatch ( brace_max[no] = OPERAND_MAX(scan); brace_count[no] = 0; } else { - EMSG(_(e_internal)); /* Shouldn't happen */ + internal_error("BRACE_LIMITS"); status = RA_FAIL; } } diff --git a/src/nvim/spell.c b/src/nvim/spell.c index 42c9bcc0ee..c837038659 100644 --- a/src/nvim/spell.c +++ b/src/nvim/spell.c @@ -5355,7 +5355,7 @@ add_sound_suggest ( // Find the word nr in the soundfold tree. sfwordnr = soundfold_find(slang, goodword); if (sfwordnr < 0) { - EMSG2(_(e_intern2), "add_sound_suggest()"); + internal_error("add_sound_suggest()"); return; } diff --git a/src/nvim/undo.c b/src/nvim/undo.c index 3d7ebc8837..f611a3bb29 100644 --- a/src/nvim/undo.c +++ b/src/nvim/undo.c @@ -2096,8 +2096,8 @@ void undo_time(long step, int sec, int file, int absolute) uhp = uhp->uh_prev.ptr; if (uhp == NULL || uhp->uh_walk != mark) { - /* Need to redo more but can't find it... */ - EMSG2(_(e_intern2), "undo_time()"); + // Need to redo more but can't find it... + internal_error("undo_time()"); break; } } @@ -2163,8 +2163,8 @@ static void u_undoredo(int undo) if (top > curbuf->b_ml.ml_line_count || top >= bot || bot > curbuf->b_ml.ml_line_count + 1) { unblock_autocmds(); - EMSG(_("E438: u_undo: line numbers wrong")); - changed(); /* don't want UNCHANGED now */ + IEMSG(_("E438: u_undo: line numbers wrong")); + changed(); // don't want UNCHANGED now return; } @@ -2655,7 +2655,7 @@ static void u_unch_branch(u_header_T *uhp) static u_entry_T *u_get_headentry(void) { if (curbuf->b_u_newhead == NULL || curbuf->b_u_newhead->uh_entry == NULL) { - EMSG(_("E439: undo list corrupt")); + IEMSG(_("E439: undo list corrupt")); return NULL; } return curbuf->b_u_newhead->uh_entry; @@ -2684,11 +2684,11 @@ static void u_getbot(void) extra = curbuf->b_ml.ml_line_count - uep->ue_lcount; uep->ue_bot = uep->ue_top + uep->ue_size + 1 + extra; if (uep->ue_bot < 1 || uep->ue_bot > curbuf->b_ml.ml_line_count) { - EMSG(_("E440: undo line missing")); - uep->ue_bot = uep->ue_top + 1; /* assume all lines deleted, will - * get all the old lines back - * without deleting the current - * ones */ + IEMSG(_("E440: undo line missing")); + uep->ue_bot = uep->ue_top + 1; // assume all lines deleted, will + // get all the old lines back + // without deleting the current + // ones } curbuf->b_u_newhead->uh_getbot_entry = NULL; diff --git a/src/nvim/version.c b/src/nvim/version.c index e8cf1d0f0b..8ab9fc1a4b 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -1182,7 +1182,7 @@ static const int included_patches[] = { // 77 NA // 76 NA 75, - // 74, + 74, 73, // 72 NA // 71 NA diff --git a/src/nvim/window.c b/src/nvim/window.c index e39569321e..63c9361663 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -2115,7 +2115,7 @@ void win_close_othertab(win_T *win, int free_buf, tabpage_T *tp) ptp = ptp->tp_next) ; if (ptp == NULL) { - EMSG2(_(e_intern2), "win_close_othertab()"); + internal_error("win_close_othertab()"); return; } ptp->tp_next = tp->tp_next; -- cgit From dcb2780b834d4df006f55a0475e03bd2a38ac344 Mon Sep 17 00:00:00 2001 From: James McCoy Date: Fri, 15 Dec 2017 15:57:38 -0500 Subject: lint --- src/nvim/memline.c | 29 +++++++++++++++-------------- src/nvim/regexp.c | 16 ++++++---------- 2 files changed, 21 insertions(+), 24 deletions(-) diff --git a/src/nvim/memline.c b/src/nvim/memline.c index fde21f7992..80775c607d 100644 --- a/src/nvim/memline.c +++ b/src/nvim/memline.c @@ -1743,11 +1743,11 @@ ml_get_buf ( if (lnum > buf->b_ml.ml_line_count) { /* invalid line number */ if (recursive == 0) { - /* Avoid giving this message for a recursive call, may happen when - * the GUI redraws part of the text. */ - ++recursive; + // Avoid giving this message for a recursive call, may happen when + // the GUI redraws part of the text. + recursive++; IEMSGN(_("E315: ml_get: invalid lnum: %" PRId64), lnum); - --recursive; + recursive--; } errorret: STRCPY(IObuff, "???"); @@ -1775,11 +1775,11 @@ errorret: */ if ((hp = ml_find_line(buf, lnum, ML_FIND)) == NULL) { if (recursive == 0) { - /* Avoid giving this message for a recursive call, may happen - * when the GUI redraws part of the text. */ - ++recursive; + // Avoid giving this message for a recursive call, may happen + // when the GUI redraws part of the text. + recursive++; IEMSGN(_("E316: ml_get: cannot find line %" PRId64), lnum); - --recursive; + recursive--; } goto errorret; } @@ -2631,9 +2631,9 @@ static void ml_flush_line(buf_T *buf) new_line = buf->b_ml.ml_line_ptr; hp = ml_find_line(buf, lnum, ML_FIND); - if (hp == NULL) + if (hp == NULL) { IEMSGN(_("E320: Cannot find line %" PRId64), lnum); - else { + } else { dp = hp->bh_data; idx = lnum - buf->b_ml.ml_locked_low; start = ((dp->db_index[idx]) & DB_INDEX_MASK); @@ -2879,13 +2879,14 @@ static bhdr_T *ml_find_line(buf_T *buf, linenr_T lnum, int action) break; } } - if (idx >= (int)pp->pb_count) { /* past the end: something wrong! */ - if (lnum > buf->b_ml.ml_line_count) + if (idx >= (int)pp->pb_count) { // past the end: something wrong! + if (lnum > buf->b_ml.ml_line_count) { IEMSGN(_("E322: line number out of range: %" PRId64 " past the end"), - lnum - buf->b_ml.ml_line_count); + lnum - buf->b_ml.ml_line_count); - else + } else { IEMSGN(_("E323: line count wrong in block %" PRId64), bnum); + } goto error_block; } if (action == ML_DELETE) { diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c index e921706b3a..ec9916f4a2 100644 --- a/src/nvim/regexp.c +++ b/src/nvim/regexp.c @@ -458,17 +458,13 @@ static int toggle_Magic(int x) /* Used for an error (down from) vim_regcomp(): give the error message, set * rc_did_emsg and return NULL */ -#define EMSG_RET_NULL(m) return (EMSG(m), rc_did_emsg = TRUE, (void *)NULL) +#define EMSG_RET_NULL(m) return (EMSG(m), rc_did_emsg = true, (void *)NULL) #define IEMSG_RET_NULL(m) return (IEMSG(m), rc_did_emsg = true, (void *)NULL) -#define EMSG_RET_FAIL(m) return (EMSG(m), rc_did_emsg = TRUE, FAIL) -#define EMSG2_RET_NULL(m, \ - c) return (EMSG2((m), \ - (c) ? "" : "\\"), rc_did_emsg = true, \ - (void *)NULL) -#define EMSG2_RET_FAIL(m, \ - c) return (EMSG2((m), \ - (c) ? "" : "\\"), rc_did_emsg = true, \ - FAIL) +#define EMSG_RET_FAIL(m) return (EMSG(m), rc_did_emsg = true, FAIL) +#define EMSG2_RET_NULL(m, c) \ + return (EMSG2((m), (c) ? "" : "\\"), rc_did_emsg = true, (void *)NULL) +#define EMSG2_RET_FAIL(m, c) \ + return (EMSG2((m), (c) ? "" : "\\"), rc_did_emsg = true, FAIL) #define EMSG_ONE_RET_NULL EMSG2_RET_NULL(_( \ "E369: invalid item in %s%%[]"), reg_magic == MAGIC_ALL) -- cgit From 91d3efa35a26f6c5e58850413ccbb350cb8e7b37 Mon Sep 17 00:00:00 2001 From: ZyX Date: Fri, 15 Dec 2017 11:40:27 +0300 Subject: eval/encode: Avoid unnecessary tv_list_idx_of_item() calls --- src/nvim/eval/encode.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/nvim/eval/encode.c b/src/nvim/eval/encode.c index 39897aa9ab..b7af102681 100644 --- a/src/nvim/eval/encode.c +++ b/src/nvim/eval/encode.c @@ -136,13 +136,18 @@ static int conv_error(const char *const msg, const MPConvStack *const mpstack, } case kMPConvPairs: case kMPConvList: { + const int idx = (v.data.l.li == tv_list_first(v.data.l.list) + ? 0 + : (v.data.l.li == NULL + ? tv_list_len(v.data.l.list) - 1 + : (int)tv_list_idx_of_item( + v.data.l.list, + TV_LIST_ITEM_PREV(v.data.l.list, + v.data.l.li)))); const listitem_T *const li = (v.data.l.li == NULL ? tv_list_last(v.data.l.list) : TV_LIST_ITEM_PREV(v.data.l.list, v.data.l.li)); - int idx = (li == NULL - ? 0 - : (int)tv_list_idx_of_item(v.data.l.list, li)); if (v.type == kMPConvList || li == NULL || (TV_LIST_ITEM_TV(li)->v_type != VAR_LIST -- cgit From bfb21f3e012d9473d6038dd254fc3a0ecdf8c0e9 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sat, 16 Dec 2017 00:38:58 +0100 Subject: tui: rework deferred-termcodes ... again - Revert timer-based approach. - Instead, call loop_poll_events() with a timeout in an "active" loop, to infer that "TUI startup activity has mostly finished", but also to enforce a mininum time (100 ms) before emitting "enable focus reporting" termcode. (If TUI startup takes longer than that minimum time, it's probably a slow environment anyways.) - Tickle `main_loop` by sending a dummy event. Without this, the initial "focus-gained" response from the terminal may not get processed until the user hits a key. ref #7720 ref #7664 ref #7649 ref #7664 ref 27f9b1c7b029d8 --- src/nvim/event/loop.c | 19 +++++++++++++++---- src/nvim/tui/tui.c | 42 +++++++++++++++++++++++++----------------- 2 files changed, 40 insertions(+), 21 deletions(-) diff --git a/src/nvim/event/loop.c b/src/nvim/event/loop.c index 5adf16c0f3..55ef0261d9 100644 --- a/src/nvim/event/loop.c +++ b/src/nvim/event/loop.c @@ -30,19 +30,22 @@ void loop_init(Loop *loop, void *data) uv_signal_init(&loop->uv, &loop->children_watcher); uv_timer_init(&loop->uv, &loop->children_kill_timer); uv_timer_init(&loop->uv, &loop->poll_timer); + loop->poll_timer.data = xmalloc(sizeof(bool)); // "timeout expired" flag } -void loop_poll_events(Loop *loop, int ms) +/// @returns true if `ms` timeout was reached +bool loop_poll_events(Loop *loop, int ms) { if (loop->recursive++) { abort(); // Should not re-enter uv_run } uv_run_mode mode = UV_RUN_ONCE; + bool timeout_expired = false; if (ms > 0) { - // Use a repeating timeout of ms milliseconds to make sure - // we do not block indefinitely for I/O. + *((bool *)loop->poll_timer.data) = false; // reset "timeout expired" flag + // Dummy timer to ensure UV_RUN_ONCE does not block indefinitely for I/O. uv_timer_start(&loop->poll_timer, timer_cb, (uint64_t)ms, (uint64_t)ms); } else if (ms == 0) { // For ms == 0, do a non-blocking event poll. @@ -52,11 +55,13 @@ void loop_poll_events(Loop *loop, int ms) uv_run(&loop->uv, mode); if (ms > 0) { + timeout_expired = *((bool *)loop->poll_timer.data); uv_timer_stop(&loop->poll_timer); } loop->recursive--; // Can re-enter uv_run now multiqueue_process_events(loop->fast_events); + return timeout_expired; } /// Schedules an event from another thread. @@ -111,7 +116,7 @@ bool loop_close(Loop *loop, bool wait) uv_mutex_destroy(&loop->mutex); uv_close((uv_handle_t *)&loop->children_watcher, NULL); uv_close((uv_handle_t *)&loop->children_kill_timer, NULL); - uv_close((uv_handle_t *)&loop->poll_timer, NULL); + uv_close((uv_handle_t *)&loop->poll_timer, timer_close_cb); uv_close((uv_handle_t *)&loop->async, NULL); uint64_t start = wait ? os_hrtime() : 0; while (true) { @@ -163,5 +168,11 @@ static void async_cb(uv_async_t *handle) static void timer_cb(uv_timer_t *handle) { + bool *timeout_expired = handle->data; + *timeout_expired = true; } +static void timer_close_cb(uv_handle_t *handle) +{ + xfree(handle->data); +} diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index b8f43e9d13..3c181aca65 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -70,7 +70,6 @@ typedef struct { UIBridgeData *bridge; Loop *loop; bool stop; - uv_timer_t after_startup_timer; unibi_var_t params[9]; char buf[OUTBUF_SIZE]; size_t bufpos; @@ -291,18 +290,6 @@ static void terminfo_stop(UI *ui) unibi_destroy(data->ut); } -static void after_startup_timer_cb(uv_timer_t *handle) - FUNC_ATTR_NONNULL_ALL -{ - UI *ui = handle->data; - TUIData *data = ui->data; - uv_timer_stop(&data->after_startup_timer); - - // Emit this after Nvim startup, not during. This works around a tmux - // 2.3 bug(?) which caused slow drawing during startup. #7649 - unibi_out_ext(ui, data->unibi_ext.enable_focus_reporting); -} - static void tui_terminal_start(UI *ui) { TUIData *data = ui->data; @@ -312,8 +299,16 @@ static void tui_terminal_start(UI *ui) update_size(ui); signal_watcher_start(&data->winch_handle, sigwinch_cb, SIGWINCH); term_input_start(&data->input); +} + +static void tui_terminal_after_startup(UI *ui) + FUNC_ATTR_NONNULL_ALL +{ + TUIData *data = ui->data; - uv_timer_start(&data->after_startup_timer, after_startup_timer_cb, 500, 0); + // Emit this after Nvim startup, not during. This works around a tmux + // 2.3 bug(?) which caused slow drawing during startup. #7649 + unibi_out_ext(ui, data->unibi_ext.enable_focus_reporting); } static void tui_terminal_stop(UI *ui) @@ -347,8 +342,6 @@ static void tui_main(UIBridgeData *bridge, UI *ui) #ifdef UNIX signal_watcher_start(&data->cont_handle, sigcont_cb, SIGCONT); #endif - uv_timer_init(&data->loop->uv, &data->after_startup_timer); - data->after_startup_timer.data = ui; #if TERMKEY_VERSION_MAJOR > 0 || TERMKEY_VERSION_MINOR > 18 data->input.tk_ti_hook_fn = tui_tk_ti_getstr; @@ -363,11 +356,21 @@ static void tui_main(UIBridgeData *bridge, UI *ui) loop_schedule_deferred(&main_loop, event_create(show_termcap_event, 1, data->ut)); + // "Active" loop: first ~100 ms of startup. + for (size_t ms = 0; ms < 100 && !data->stop;) { + ms += (loop_poll_events(&tui_loop, 20) ? 20 : 1); + } + if (!data->stop) { + tui_terminal_after_startup(ui); + // Tickle `main_loop` with a dummy event, else the initial "focus-gained" + // terminal response may not get processed until user hits a key. + loop_schedule_deferred(&main_loop, event_create(tui_dummy_event, 0)); + } + // "Passive" (I/O-driven) loop: TUI thread "main loop". while (!data->stop) { loop_poll_events(&tui_loop, -1); // tui_loop.events is never processed } - uv_close((uv_handle_t *)&data->after_startup_timer, NULL); ui_bridge_stopped(bridge); term_input_destroy(&data->input); signal_watcher_stop(&data->cont_handle); @@ -379,6 +382,10 @@ static void tui_main(UIBridgeData *bridge, UI *ui) xfree(ui); } +static void tui_dummy_event(void **argv) +{ +} + static void tui_scheduler(Event event, void *d) { UI *ui = d; @@ -1100,6 +1107,7 @@ static void suspend_event(void **argv) loop_poll_events(data->loop, -1); } tui_terminal_start(ui); + tui_terminal_after_startup(ui); if (enable_mouse) { tui_mouse_on(ui); } -- cgit From 76ffe0c5aa51fe4fe14811e86867af2c711190a1 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sat, 16 Dec 2017 14:21:56 +0300 Subject: eval: Fix linter error --- src/nvim/eval.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 640967b91c..3aaa5f349d 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -12485,7 +12485,7 @@ static void f_matchaddpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } - // id == 3 is ok because matchaddpos() is supposed to substitute :3match + // id == 3 is ok because matchaddpos() is supposed to substitute :3match if (id == 1 || id == 2) { EMSGN(_("E798: ID is reserved for \"match\": %" PRId64), id); return; -- cgit From 023631463cfe776f71492b8d853c473d0c547647 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sat, 16 Dec 2017 16:14:53 +0300 Subject: functests: Fix linter error --- test/functional/eval/match_functions_spec.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/test/functional/eval/match_functions_spec.lua b/test/functional/eval/match_functions_spec.lua index 0dc78de55e..7989b22b5e 100644 --- a/test/functional/eval/match_functions_spec.lua +++ b/test/functional/eval/match_functions_spec.lua @@ -4,7 +4,6 @@ local Screen = require('test.functional.ui.screen') local eq = helpers.eq local clear = helpers.clear local funcs = helpers.funcs -local meths = helpers.meths local command = helpers.command local exc_exec = helpers.exc_exec -- cgit From edccf18df56d982dea350770ea42ffa25759b43f Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 17 Dec 2017 15:23:27 +0300 Subject: eval: Fix some issues found in review --- src/nvim/eval.c | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 74b0cdc746..6b04dd4eea 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -5336,18 +5336,16 @@ bool set_ref_in_list(list_T *l, int copyID, ht_stack_T **ht_stack) list_T *cur_l = l; for (;;) { - if (!abort) { - // Mark each item in the list. If the item contains a hashtab - // it is added to ht_stack, if it contains a list it is added to - // list_stack. - TV_LIST_ITER(cur_l, li, { - abort = set_ref_in_item(TV_LIST_ITEM_TV(li), copyID, ht_stack, - &list_stack); - if (abort) { - break; - } - }); - } + // Mark each item in the list. If the item contains a hashtab + // it is added to ht_stack, if it contains a list it is added to + // list_stack. + TV_LIST_ITER(cur_l, li, { + if (abort) { + break; + } + abort = set_ref_in_item(TV_LIST_ITEM_TV(li), copyID, ht_stack, + &list_stack); + }); if (list_stack == NULL) { break; @@ -8968,7 +8966,7 @@ static void common_function(typval_T *argvars, typval_T *rettv, goto theend; } list = argvars[arg_idx].vval.v_list; - if (list == NULL || tv_list_len(list) == 0) { + if (tv_list_len(list) == 0) { arg_idx = 0; } } @@ -11069,8 +11067,7 @@ static void f_index(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (argvars[2].v_type != VAR_UNKNOWN) { bool error = false; - // Start at specified item. Use the cached index that tv_list_find() - // sets, so that a negative number also works. + // Start at specified item. idx = tv_list_uidx(l, tv_get_number_chk(&argvars[2], &error)); if (error || idx == -1) { item = NULL; @@ -11080,6 +11077,9 @@ static void f_index(typval_T *argvars, typval_T *rettv, FunPtr fptr) } if (argvars[3].v_type != VAR_UNKNOWN) { ic = !!tv_get_number_chk(&argvars[3], &error); + if (error) { + item = NULL; + } } } @@ -12248,10 +12248,10 @@ static void find_some_match(typval_T *argvars, typval_T *rettv, int type) } if (l != NULL) { idx = tv_list_uidx(l, start); - li = tv_list_find(l, idx); - if (li == NULL) { + if (idx == -1) { goto theend; } + li = tv_list_find(l, idx); } else { if (start < 0) start = 0; -- cgit From a1adfdc7d59979824addce2f519a527f9a5c0290 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Wed, 29 Nov 2017 01:50:11 -0500 Subject: ci: nodejs client acceptance-test #7706 ci: install nodejs 8 in Appveyor, Travis provider: check node version for debug support Resolve https://github.com/neovim/neovim/pull/7577#issuecomment-350590592 for Unix. provider: test if nodejs in ci supports --inspect-brk nodejs host for neovim requires nodejs 6+ to work properly. nodejs 6.12+ or 7.6+ is required for debug support via `node --inspect-brk`. provider: run cli.js of nodejs host directly npm shims are useless because the user cannot set node to debug mode via --inspect-brk. This is problematic on Windows which use batchfiles and shell scripts to compensate for not supporting shebang. The patch uses `npm root -g` to get the absolute path of the global npm modules. If that fails, then the user did not install neovim npm package globally. Use that absolute path to find `neovim/bin/cli.js`, which is what the npm shim actually runs with node. glob() is for a simple file check in case bin/ is removed because the npm shims are ignored now. --- appveyor.yml | 2 + ci/before_install.sh | 12 ++++++ ci/build.bat | 6 +++ ci/install.sh | 5 +++ ci/run_tests.sh | 1 + runtime/autoload/health/provider.vim | 15 +++++-- runtime/autoload/provider/node.vim | 40 +++++++++++++----- test/functional/helpers.lua | 2 +- test/functional/provider/nodejs_spec.lua | 70 ++++++++++++++++++++++++++++++++ 9 files changed, 138 insertions(+), 15 deletions(-) create mode 100644 test/functional/provider/nodejs_spec.lua diff --git a/appveyor.yml b/appveyor.yml index 2d6135c7a2..30b7947da0 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -9,6 +9,8 @@ matrix: allow_failures: - configuration: MINGW_64-gcov install: [] +before_build: +- ps: Install-Product node 8 build_script: - call ci\build.bat cache: diff --git a/ci/before_install.sh b/ci/before_install.sh index f84ad935bc..f5a57ad657 100755 --- a/ci/before_install.sh +++ b/ci/before_install.sh @@ -37,3 +37,15 @@ else # https://github.com/travis-ci/travis-ci/issues/8363 pip3 -q install --user --upgrade pip || true fi + +if [[ "${TRAVIS_OS_NAME}" == linux ]]; then + echo "Install node (LTS)" + + if [ ! -f ~/.nvm/nvm.sh ]; then + curl -o ~/.nvm/nvm.sh https://raw.githubusercontent.com/creationix/nvm/master/nvm.sh + fi + + source ~/.nvm/nvm.sh + nvm install --lts + nvm use --lts +fi diff --git a/ci/build.bat b/ci/build.bat index 25f949b5e4..c5353ec5d1 100644 --- a/ci/build.bat +++ b/ci/build.bat @@ -37,6 +37,12 @@ set PATH=C:\Ruby24\bin;%PATH% cmd /c gem.cmd install neovim || goto :error where.exe neovim-ruby-host.bat || goto :error +cmd /c npm.cmd install -g neovim || goto :error +where.exe neovim-node-host.cmd || goto :error +for /f %%F in ('cmd /c npm root -g') do ( + set NODE_PATH=%%F +) + mkdir .deps cd .deps cmake -G "MinGW Makefiles" -DCMAKE_BUILD_TYPE=RelWithDebInfo ..\third-party\ || goto :error diff --git a/ci/install.sh b/ci/install.sh index c8a0c8825d..2fe4f88822 100755 --- a/ci/install.sh +++ b/ci/install.sh @@ -23,3 +23,8 @@ CC=cc pip3 -q install --user --upgrade neovim || true echo "Install neovim RubyGem." gem install --no-document --version ">= 0.2.0" neovim + +if [[ "${TRAVIS_OS_NAME}" == linux ]]; then + echo "Install neovim npm package" + npm install -g neovim +fi diff --git a/ci/run_tests.sh b/ci/run_tests.sh index a0bf6e010d..ee3fa4a5af 100755 --- a/ci/run_tests.sh +++ b/ci/run_tests.sh @@ -23,6 +23,7 @@ if test "$CLANG_SANITIZER" != "TSAN" ; then # Additional threads are only created when the builtin UI starts, which # doesn't happen in the unit/functional tests run_test run_unittests + export NODE_PATH="$(npm root -g)" run_test run_functionaltests fi run_test run_oldtests diff --git a/runtime/autoload/health/provider.vim b/runtime/autoload/health/provider.vim index 0201ed8062..188b67c4c1 100644 --- a/runtime/autoload/health/provider.vim +++ b/runtime/autoload/health/provider.vim @@ -488,7 +488,7 @@ function! s:check_ruby() abort endfunction function! s:check_node() abort - call health#report_start('Node provider (optional)') + call health#report_start('Node.js provider (optional)') let loaded_var = 'g:loaded_node_provider' if exists(loaded_var) && !exists('*provider#node#Call') @@ -502,7 +502,14 @@ function! s:check_node() abort \ ['Install Node.js and verify that `node` and `npm` commands work.']) return endif - call health#report_info('Node: '. s:system('node -v')) + let node_v = get(split(s:system('node -v'), "\n"), 0, '') + call health#report_info('Node.js: '. node_v) + if !s:shell_error && s:version_cmp(node_v[1:], '6.0.0') < 0 + call health#report_warn('Neovim node.js host does not support '.node_v) + endif + if !provider#node#can_inspect() + call health#report_warn('node.js on this system does not support --inspect-brk so $NVIM_NODE_HOST_DEBUG is ignored.') + endif let host = provider#node#Detect() if empty(host) @@ -511,7 +518,7 @@ function! s:check_node() abort \ 'Is the npm bin directory in $PATH?']) return endif - call health#report_info('Host: '. host) + call health#report_info('Neovim node.js host: '. host) let latest_npm_cmd = has('win32') ? 'cmd /c npm info neovim --json' : 'npm info neovim --json' let latest_npm = s:system(split(latest_npm_cmd)) @@ -530,7 +537,7 @@ function! s:check_node() abort let latest_npm = get(get(pkg_data, 'dist-tags', {}), 'latest', 'unable to parse') endif - let current_npm_cmd = host .' --version' + let current_npm_cmd = ['node', host, '--version'] let current_npm = s:system(current_npm_cmd) if s:shell_error call health#report_error('Failed to run: '. current_npm_cmd, diff --git a/runtime/autoload/provider/node.vim b/runtime/autoload/provider/node.vim index b08ad4f316..67b54c6439 100644 --- a/runtime/autoload/provider/node.vim +++ b/runtime/autoload/provider/node.vim @@ -5,8 +5,32 @@ let g:loaded_node_provider = 1 let s:job_opts = {'rpc': v:true, 'on_stderr': function('provider#stderr_collector')} +" Support for --inspect-brk requires node 6.12+ or 7.6+ or 8+ +" Return 1 if it is supported +" Return 0 otherwise +function! provider#node#can_inspect() + if !executable('node') + return 0 + endif + let node_v = get(split(system(['node', '-v']), "\n"), 0, '') + if v:shell_error || node_v[0] !=# 'v' + return 0 + endif + " [major, minor, patch] + let node_v = split(node_v[1:], '\.') + return len(node_v) == 3 && ( + \ (node_v[0] > 7) || + \ (node_v[0] == 7 && node_v[1] >= 6) || + \ (node_v[0] == 6 && node_v[1] >= 12) + \ ) +endfunction + function! provider#node#Detect() abort - return has('win32') ? exepath('neovim-node-host.cmd') : exepath('neovim-node-host') + let global_modules = get(split(system('npm root -g'), "\n"), 0, '') + if v:shell_error || !isdirectory(global_modules) + return '' + endif + return glob(global_modules . '/neovim/bin/cli.js') endfunction function! provider#node#Prog() @@ -19,18 +43,14 @@ function! provider#node#Require(host) abort return endif - if has('win32') - let args = provider#node#Prog() - else - let args = ['node'] - - if !empty($NVIM_NODE_HOST_DEBUG) - call add(args, '--inspect-brk') - endif + let args = ['node'] - call add(args , provider#node#Prog()) + if !empty($NVIM_NODE_HOST_DEBUG) && provider#node#can_inspect() + call add(args, '--inspect-brk') endif + call add(args, provider#node#Prog()) + try let channel_id = jobstart(args, s:job_opts) if rpcrequest(channel_id, 'poll') ==# 'ok' diff --git a/test/functional/helpers.lua b/test/functional/helpers.lua index f939567693..bff8d065f8 100644 --- a/test/functional/helpers.lua +++ b/test/functional/helpers.lua @@ -614,7 +614,7 @@ local function new_pipename() end local function missing_provider(provider) - if provider == 'ruby' then + if provider == 'ruby' or provider == 'node' then local prog = funcs['provider#' .. provider .. '#Detect']() return prog == '' and (provider .. ' not detected') or false elseif provider == 'python' or provider == 'python3' then diff --git a/test/functional/provider/nodejs_spec.lua b/test/functional/provider/nodejs_spec.lua new file mode 100644 index 0000000000..9423243607 --- /dev/null +++ b/test/functional/provider/nodejs_spec.lua @@ -0,0 +1,70 @@ +local helpers = require('test.functional.helpers')(after_each) +local eq, clear = helpers.eq, helpers.clear +local missing_provider = helpers.missing_provider +local command = helpers.command +local write_file = helpers.write_file +local eval = helpers.eval +local sleep = helpers.sleep +local funcs = helpers.funcs +local retry = helpers.retry + +do + clear() + if missing_provider('node') then + pending( + "Cannot find the neovim nodejs host. Try :checkhealth", + function() end) + return + end +end + +before_each(function() + clear() +end) + +describe('nodejs', function() + it('can inspect', function() + eq(1, funcs['provider#node#can_inspect']()) + end) +end) + +describe('nodejs host', function() + teardown(function () + os.remove('Xtest-nodejs-hello.js') + os.remove('Xtest-nodejs-hello-plugin.js') + end) + + it('works', function() + local fname = 'Xtest-nodejs-hello.js' + write_file(fname, [[ + const socket = process.env.NVIM_LISTEN_ADDRESS; + const neovim = require('neovim'); + const nvim = neovim.attach({socket: socket}); + nvim.command('let g:job_out = "hello"'); + nvim.command('call jobstop(g:job_id)'); + ]]) + command('let g:job_id = jobstart(["node", "'..fname..'"])') + retry(nil, 1000, function() eq('hello', eval('g:job_out')) end) + end) + it('plugin works', function() + local fname = 'Xtest-nodejs-hello-plugin.js' + write_file(fname, [[ + const socket = process.env.NVIM_LISTEN_ADDRESS; + const neovim = require('neovim'); + const nvim = neovim.attach({socket: socket}); + + class TestPlugin { + hello() { + this.nvim.command('let g:job_out = "hello-plugin"') + } + } + + const PluginClass = neovim.Plugin(TestPlugin); + const plugin = new PluginClass(nvim); + plugin.hello(); + nvim.command('call jobstop(g:job_id)'); + ]]) + command('let g:job_id = jobstart(["node", "'..fname..'"])') + retry(nil, 1000, function() eq('hello-plugin', eval('g:job_out')) end) + end) +end) -- cgit From 5b692124cc94c8e5edc0c767e6a71887754643cd Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sat, 16 Dec 2017 16:29:33 -0500 Subject: test: remove inspect test; set NODE_PATH in nodejs_spec.lua provider#node#can_inspect will fail on some systems because it is common to have old node versions in OS (any Linux OS that has LTS releases) and CI (Travis, Appveyor). NODE_PATH can be trivially set with VimL. Build scripts don't have to set it for the nodejs tests to work. NODE_PATH is optional to begin with and is used only as a workaround for the neovim node.js host. --- ci/build.bat | 3 --- ci/run_tests.sh | 1 - test/functional/provider/nodejs_spec.lua | 9 +-------- 3 files changed, 1 insertion(+), 12 deletions(-) diff --git a/ci/build.bat b/ci/build.bat index c5353ec5d1..9909d102a4 100644 --- a/ci/build.bat +++ b/ci/build.bat @@ -39,9 +39,6 @@ where.exe neovim-ruby-host.bat || goto :error cmd /c npm.cmd install -g neovim || goto :error where.exe neovim-node-host.cmd || goto :error -for /f %%F in ('cmd /c npm root -g') do ( - set NODE_PATH=%%F -) mkdir .deps cd .deps diff --git a/ci/run_tests.sh b/ci/run_tests.sh index ee3fa4a5af..a0bf6e010d 100755 --- a/ci/run_tests.sh +++ b/ci/run_tests.sh @@ -23,7 +23,6 @@ if test "$CLANG_SANITIZER" != "TSAN" ; then # Additional threads are only created when the builtin UI starts, which # doesn't happen in the unit/functional tests run_test run_unittests - export NODE_PATH="$(npm root -g)" run_test run_functionaltests fi run_test run_oldtests diff --git a/test/functional/provider/nodejs_spec.lua b/test/functional/provider/nodejs_spec.lua index 9423243607..ef563dd0b0 100644 --- a/test/functional/provider/nodejs_spec.lua +++ b/test/functional/provider/nodejs_spec.lua @@ -4,8 +4,6 @@ local missing_provider = helpers.missing_provider local command = helpers.command local write_file = helpers.write_file local eval = helpers.eval -local sleep = helpers.sleep -local funcs = helpers.funcs local retry = helpers.retry do @@ -20,12 +18,7 @@ end before_each(function() clear() -end) - -describe('nodejs', function() - it('can inspect', function() - eq(1, funcs['provider#node#can_inspect']()) - end) + command([[let $NODE_PATH = get(split(system('npm root -g'), "\n"), 0, '')]]) end) describe('nodejs host', function() -- cgit From e0054fef7d351b0f8af3a04bb22d6e6ee8bae63f Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sun, 17 Dec 2017 15:03:07 +0100 Subject: health.vim: nodejs: skip if nodejs is too old --- runtime/autoload/health/provider.vim | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/runtime/autoload/health/provider.vim b/runtime/autoload/health/provider.vim index 188b67c4c1..205a23dcbd 100644 --- a/runtime/autoload/health/provider.vim +++ b/runtime/autoload/health/provider.vim @@ -506,6 +506,8 @@ function! s:check_node() abort call health#report_info('Node.js: '. node_v) if !s:shell_error && s:version_cmp(node_v[1:], '6.0.0') < 0 call health#report_warn('Neovim node.js host does not support '.node_v) + " Skip further checks, they are nonsense if nodejs is too old. + return endif if !provider#node#can_inspect() call health#report_warn('node.js on this system does not support --inspect-brk so $NVIM_NODE_HOST_DEBUG is ignored.') @@ -540,8 +542,8 @@ function! s:check_node() abort let current_npm_cmd = ['node', host, '--version'] let current_npm = s:system(current_npm_cmd) if s:shell_error - call health#report_error('Failed to run: '. current_npm_cmd, - \ ['Report this issue with the output of: ', current_npm_cmd]) + call health#report_error('Failed to run: '. string(current_npm_cmd), + \ ['Report this issue with the output of: ', string(current_npm_cmd)]) return endif @@ -551,7 +553,7 @@ function! s:check_node() abort \ current_npm, latest_npm), \ ['Run in shell: npm update neovim']) else - call health#report_ok('Latest "neovim" npm is installed: '. current_npm) + call health#report_ok('Latest "neovim" npm package is installed: '. current_npm) endif endfunction -- cgit From 103ff26c0ae76491c73a6c078b52f2e56af16fb8 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sun, 17 Dec 2017 15:53:11 +0100 Subject: provider/nodejs: check version in Detect() --- runtime/autoload/provider/node.vim | 44 ++++++++++++++++++++++---------- runtime/autoload/provider/python.vim | 6 ++--- runtime/autoload/provider/python3.vim | 6 ++--- runtime/autoload/provider/ruby.vim | 6 ++--- test/functional/provider/nodejs_spec.lua | 4 +-- test/functional/provider/ruby_spec.lua | 4 +-- 6 files changed, 42 insertions(+), 28 deletions(-) diff --git a/runtime/autoload/provider/node.vim b/runtime/autoload/provider/node.vim index 67b54c6439..f75d2632be 100644 --- a/runtime/autoload/provider/node.vim +++ b/runtime/autoload/provider/node.vim @@ -5,24 +5,35 @@ let g:loaded_node_provider = 1 let s:job_opts = {'rpc': v:true, 'on_stderr': function('provider#stderr_collector')} +function! s:is_minimum_version(version, min_major, min_minor) abort + let nodejs_version = a:version + if !a:version + let nodejs_version = get(split(system(['node', '-v']), "\n"), 0, '') + if v:shell_error || nodejs_version[0] !=# 'v' + return 0 + endif + endif + " [major, minor, patch] + let v_list = !!a:version ? a:version : split(nodejs_version[1:], '\.') + return len(v_list) == 3 + \ && ((str2nr(v_list[0]) > str2nr(a:min_major)) + \ || (str2nr(v_list[0]) == str2nr(a:min_major) + \ && str2nr(v_list[1]) >= str2nr(a:min_minor))) +endfunction + " Support for --inspect-brk requires node 6.12+ or 7.6+ or 8+ " Return 1 if it is supported " Return 0 otherwise -function! provider#node#can_inspect() +function! provider#node#can_inspect() abort if !executable('node') return 0 endif - let node_v = get(split(system(['node', '-v']), "\n"), 0, '') - if v:shell_error || node_v[0] !=# 'v' + let ver = get(split(system(['node', '-v']), "\n"), 0, '') + if v:shell_error || ver[0] !=# 'v' return 0 endif - " [major, minor, patch] - let node_v = split(node_v[1:], '\.') - return len(node_v) == 3 && ( - \ (node_v[0] > 7) || - \ (node_v[0] == 7 && node_v[1] >= 6) || - \ (node_v[0] == 6 && node_v[1] >= 12) - \ ) + return (ver[1] ==# '6' && s:is_minimum_version(ver, 6, 12)) + \ || s:is_minimum_version(ver, 7, 6) endfunction function! provider#node#Detect() abort @@ -30,10 +41,17 @@ function! provider#node#Detect() abort if v:shell_error || !isdirectory(global_modules) return '' endif - return glob(global_modules . '/neovim/bin/cli.js') + if !s:is_minimum_version(v:null, 6, 0) + return '' + endif + let entry_point = glob(global_modules . '/neovim/bin/cli.js') + if !filereadable(entry_point) + return '' + endif + return entry_point endfunction -function! provider#node#Prog() +function! provider#node#Prog() abort return s:prog endfunction @@ -69,7 +87,7 @@ function! provider#node#Require(host) abort throw remote#host#LoadErrorForHost(a:host.orig_name, '$NVIM_NODE_LOG_FILE') endfunction -function! provider#node#Call(method, args) +function! provider#node#Call(method, args) abort if s:err != '' echoerr s:err return diff --git a/runtime/autoload/provider/python.vim b/runtime/autoload/provider/python.vim index 81fe194cb9..a06cbe4814 100644 --- a/runtime/autoload/provider/python.vim +++ b/runtime/autoload/provider/python.vim @@ -11,11 +11,11 @@ let g:loaded_python_provider = 1 let [s:prog, s:err] = provider#pythonx#Detect(2) -function! provider#python#Prog() +function! provider#python#Prog() abort return s:prog endfunction -function! provider#python#Error() +function! provider#python#Error() abort return s:err endfunction @@ -29,7 +29,7 @@ endif call remote#host#RegisterClone('legacy-python-provider', 'python') call remote#host#RegisterPlugin('legacy-python-provider', 'script_host.py', []) -function! provider#python#Call(method, args) +function! provider#python#Call(method, args) abort if s:err != '' return endif diff --git a/runtime/autoload/provider/python3.vim b/runtime/autoload/provider/python3.vim index 0c3b75b73d..242a224cb3 100644 --- a/runtime/autoload/provider/python3.vim +++ b/runtime/autoload/provider/python3.vim @@ -11,11 +11,11 @@ let g:loaded_python3_provider = 1 let [s:prog, s:err] = provider#pythonx#Detect(3) -function! provider#python3#Prog() +function! provider#python3#Prog() abort return s:prog endfunction -function! provider#python3#Error() +function! provider#python3#Error() abort return s:err endfunction @@ -29,7 +29,7 @@ endif call remote#host#RegisterClone('legacy-python3-provider', 'python3') call remote#host#RegisterPlugin('legacy-python3-provider', 'script_host.py', []) -function! provider#python3#Call(method, args) +function! provider#python3#Call(method, args) abort if s:err != '' return endif diff --git a/runtime/autoload/provider/ruby.vim b/runtime/autoload/provider/ruby.vim index da73a0dfc0..3fb65fecdf 100644 --- a/runtime/autoload/provider/ruby.vim +++ b/runtime/autoload/provider/ruby.vim @@ -7,7 +7,7 @@ let g:loaded_ruby_provider = 1 let s:stderr = {} let s:job_opts = {'rpc': v:true} -function! s:job_opts.on_stderr(chan_id, data, event) +function! s:job_opts.on_stderr(chan_id, data, event) abort let stderr = get(s:stderr, a:chan_id, ['']) let last = remove(stderr, -1) let a:data[0] = last.a:data[0] @@ -23,7 +23,7 @@ function! provider#ruby#Detect() abort end endfunction -function! provider#ruby#Prog() +function! provider#ruby#Prog() abort return s:prog endfunction @@ -50,7 +50,7 @@ function! provider#ruby#Require(host) abort throw remote#host#LoadErrorForHost(a:host.orig_name, '$NVIM_RUBY_LOG_FILE') endfunction -function! provider#ruby#Call(method, args) +function! provider#ruby#Call(method, args) abort if s:err != '' echoerr s:err return diff --git a/test/functional/provider/nodejs_spec.lua b/test/functional/provider/nodejs_spec.lua index ef563dd0b0..0a12b1a154 100644 --- a/test/functional/provider/nodejs_spec.lua +++ b/test/functional/provider/nodejs_spec.lua @@ -9,9 +9,7 @@ local retry = helpers.retry do clear() if missing_provider('node') then - pending( - "Cannot find the neovim nodejs host. Try :checkhealth", - function() end) + pending("Missing nodejs host, or nodejs version is too old.", function()end) return end end diff --git a/test/functional/provider/ruby_spec.lua b/test/functional/provider/ruby_spec.lua index c70f90da1c..a2c6c6a10e 100644 --- a/test/functional/provider/ruby_spec.lua +++ b/test/functional/provider/ruby_spec.lua @@ -15,9 +15,7 @@ local missing_provider = helpers.missing_provider do clear() if missing_provider('ruby') then - pending( - "Cannot find the neovim RubyGem. Try :checkhealth", - function() end) + pending("Missing neovim RubyGem.", function() end) return end end -- cgit From 067bb1e9f41319fbe695ba6c0209621addca5b7f Mon Sep 17 00:00:00 2001 From: James McCoy Date: Sat, 16 Dec 2017 21:42:08 -0500 Subject: vim-patch.sh: Include upstream summary in commit message [ci skip] --- scripts/vim-patch.sh | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/scripts/vim-patch.sh b/scripts/vim-patch.sh index 04bc16dd25..530701e223 100755 --- a/scripts/vim-patch.sh +++ b/scripts/vim-patch.sh @@ -92,8 +92,11 @@ get_vim_sources() { } commit_message() { - printf 'vim-patch:%s\n\n%s\n\n%s' "${vim_version}" \ - "${vim_message}" "${vim_commit_url}" + if [[ -n "$vim_tag" ]]; then + printf '%s\n\n%s' "${vim_message}" "${vim_commit_url}" + else + printf 'vim-patch:%s\n\n%s\n\n%s' "$vim_version" "$vim_message" "$vim_commit_url" + fi } find_git_remote() { @@ -108,22 +111,23 @@ assign_commit_details() { vim_tag="v${1}" vim_commit=$(cd "${VIM_SOURCE_DIR}" \ && git log -1 --format="%H" "${vim_tag}") - local strip_commit_line=true + local munge_commit_line=true else # Interpret parameter as commit hash. vim_version="${1:0:12}" + vim_tag= vim_commit=$(cd "${VIM_SOURCE_DIR}" \ && git log -1 --format="%H" "${vim_version}") - local strip_commit_line=false + local munge_commit_line=false fi vim_commit_url="https://github.com/vim/vim/commit/${vim_commit}" vim_message="$(cd "${VIM_SOURCE_DIR}" \ && git log -1 --pretty='format:%B' "${vim_commit}" \ | sed -e 's/\(#[0-9]*\)/vim\/vim\1/g')" - if [[ ${strip_commit_line} == "true" ]]; then + if [[ ${munge_commit_line} == "true" ]]; then # Remove first line of commit message. - vim_message="$(echo "${vim_message}" | sed -e '1d')" + vim_message="$(echo "${vim_message}" | sed -e '1s/^patch /vim-patch:/')" fi patch_file="vim-${vim_version}.patch" } @@ -286,9 +290,10 @@ submit_pr() { local git_remote git_remote="$(find_git_remote)" local pr_body - pr_body="$(git log --reverse --format='#### %s%n%n%b%n' "${git_remote}"/master..HEAD)" + pr_body="$(git log --grep=vim-patch --reverse --format='#### %s%n%n%b%n' "${git_remote}"/master..HEAD)" local patches - patches=("$(git log --reverse --format='%s' "${git_remote}"/master..HEAD)") + # Extract just the "vim-patch:X.Y.ZZZZ" or "vim-patch:sha" portion of each log + patches=("$(git log --grep=vim-patch --reverse --format='%s' "${git_remote}"/master..HEAD | sed 's/: .*//')") patches=(${patches[@]//vim-patch:}) # Remove 'vim-patch:' prefix for each item in array. local pr_title="${patches[*]}" # Create space-separated string from array. pr_title="${pr_title// /,}" # Replace spaces with commas. -- cgit From c162bc629440afaf8910f1a29f1dfdb03f898101 Mon Sep 17 00:00:00 2001 From: James McCoy Date: Sat, 16 Dec 2017 21:50:20 -0500 Subject: vim-patch:8.0.0420: text garbled when the system encoding differs from 'encoding' Problem: When running :make the output may be in the system encoding, different from 'encoding'. Solution: Add the 'makeencoding' option. (Ken Takata) https://github.com/vim/vim/commit/2c7292dc5bbf155fe2192d417363b8c085759cad --- runtime/doc/options.txt | 17 ++++++ runtime/doc/quickfix.txt | 21 +++++++ runtime/doc/quickref.txt | 1 + src/nvim/buffer.c | 1 + src/nvim/buffer_defs.h | 1 + src/nvim/if_cscope.c | 4 +- src/nvim/main.c | 2 +- src/nvim/option.c | 12 +++- src/nvim/option_defs.h | 2 + src/nvim/options.lua | 7 +++ src/nvim/quickfix.c | 56 +++++++++++++---- src/nvim/testdir/Makefile | 1 + src/nvim/testdir/test_makeencoding.py | 67 +++++++++++++++++++++ src/nvim/testdir/test_makeencoding.vim | 106 +++++++++++++++++++++++++++++++++ 14 files changed, 280 insertions(+), 18 deletions(-) create mode 100644 src/nvim/testdir/test_makeencoding.py create mode 100644 src/nvim/testdir/test_makeencoding.vim diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index d3072d83e2..f70ec32bd8 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -3809,6 +3809,23 @@ A jump table for the options with a short description can be found at |Q_op|. This option cannot be set from a |modeline| or in the |sandbox|, for security reasons. + *'makeencoding'* *'menc'* +'makeencoding' 'menc' string (default "") + global or local to buffer |global-local| + {only available when compiled with the |+multi_byte| + feature} + {not in Vi} + Encoding used for reading the output of external commands. When empty, + encoding is not converted. + This is used for `:make`, `:lmake`, `:grep`, `:lgrep`, `:grepadd`, + `:lgrepadd`, `:cfile`, `:cgetfile`, `:caddfile`, `:lfile`, `:lgetfile`, + and `:laddfile`. + + This would be mostly useful when you use MS-Windows. If |+iconv| is + enabled and GNU libiconv is used, setting 'makeencoding' to "char" has + the same effect as setting to the system locale encoding. Example: > + :set makeencoding=char " system locale is used +< *'makeprg'* *'mp'* 'makeprg' 'mp' string (default "make") global or local to buffer |global-local| diff --git a/runtime/doc/quickfix.txt b/runtime/doc/quickfix.txt index da167c0f5b..74f82b2c65 100644 --- a/runtime/doc/quickfix.txt +++ b/runtime/doc/quickfix.txt @@ -165,6 +165,9 @@ processing a quickfix or location list command, it will be aborted. keep Vim running while compiling. If you give the name of the errorfile, the 'errorfile' option will be set to [errorfile]. See |:cc| for [!]. + If the encoding of the error file differs from the + 'encoding' option, you can use the 'makeencoding' + option to specify the encoding. *:lf* *:lfile* :lf[ile][!] [errorfile] Same as ":cfile", except the location list for the @@ -176,6 +179,9 @@ processing a quickfix or location list command, it will be aborted. :cg[etfile] [errorfile] *:cg* *:cgetfile* Read the error file. Just like ":cfile" but don't jump to the first error. + If the encoding of the error file differs from the + 'encoding' option, you can use the 'makeencoding' + option to specify the encoding. :lg[etfile] [errorfile] *:lg* *:lgetfile* @@ -186,6 +192,9 @@ processing a quickfix or location list command, it will be aborted. :caddf[ile] [errorfile] Read the error file and add the errors from the errorfile to the current quickfix list. If a quickfix list is not present, then a new list is created. + If the encoding of the error file differs from the + 'encoding' option, you can use the 'makeencoding' + option to specify the encoding. *:laddf* *:laddfile* :laddf[ile] [errorfile] Same as ":caddfile", except the location list for the @@ -322,6 +331,7 @@ use this code: > endfunction au QuickfixCmdPost make call QfMakeConv() +Another option is using 'makeencoding'. EXECUTE A COMMAND IN ALL THE BUFFERS IN QUICKFIX OR LOCATION LIST: *:cdo* @@ -586,6 +596,9 @@ lists, use ":cnewer 99" first. like |:cnext| and |:cprevious|, see above. This command does not accept a comment, any " characters are considered part of the arguments. + If the encoding of the program output differs from the + 'encoding' option, you can use the 'makeencoding' + option to specify the encoding. *:lmak* *:lmake* :lmak[e][!] [arguments] @@ -645,6 +658,7 @@ read the error messages: > au QuickfixCmdPost make call QfMakeConv() (Example by Faque Cheng) +Another option is using 'makeencoding'. ============================================================================== 5. Using :vimgrep and :grep *grep* *lid* @@ -759,6 +773,9 @@ id-utils) in a similar way to its compiler integration (see |:make| above). When 'grepprg' is "internal" this works like |:vimgrep|. Note that the pattern needs to be enclosed in separator characters then. + If the encoding of the program output differs from the + 'encoding' option, you can use the 'makeencoding' + option to specify the encoding. *:lgr* *:lgrep* :lgr[ep][!] [arguments] Same as ":grep", except the location list for the @@ -783,6 +800,10 @@ id-utils) in a similar way to its compiler integration (see |:make| above). \ | catch /E480:/ \ | endtry" < + If the encoding of the program output differs from the + 'encoding' option, you can use the 'makeencoding' + option to specify the encoding. + *:lgrepa* *:lgrepadd* :lgrepa[dd][!] [arguments] Same as ":grepadd", except the location list for the diff --git a/runtime/doc/quickref.txt b/runtime/doc/quickref.txt index b22d2afa7e..902b0175a2 100644 --- a/runtime/doc/quickref.txt +++ b/runtime/doc/quickref.txt @@ -762,6 +762,7 @@ Short explanation of each option: *option-list* 'loadplugins' 'lpl' load plugin scripts when starting up 'magic' changes special characters in search patterns 'makeef' 'mef' name of the errorfile for ":make" +'makeencoding' 'menc' encoding of external make/grep commands 'makeprg' 'mp' program to use for the ":make" command 'matchpairs' 'mps' pairs of characters that "%" can match 'matchtime' 'mat' tenths of a second to show matching paren diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 766003a021..21830539f5 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -1808,6 +1808,7 @@ void free_buf_options(buf_T *buf, int free_p_ff) buf->b_p_ul = NO_LOCAL_UNDOLEVEL; clear_string_option(&buf->b_p_lw); clear_string_option(&buf->b_p_bkc); + clear_string_option(&buf->b_p_menc); } diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index f1cbcb2627..5702ceaaef 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -637,6 +637,7 @@ struct file_buffer { uint32_t b_p_fex_flags; ///< flags for 'formatexpr' char_u *b_p_kp; ///< 'keywordprg' int b_p_lisp; ///< 'lisp' + char_u *b_p_menc; ///< 'makeencoding' char_u *b_p_mps; ///< 'matchpairs' int b_p_ml; ///< 'modeline' int b_p_ml_nobin; ///< b_p_ml saved for binary mode diff --git a/src/nvim/if_cscope.c b/src/nvim/if_cscope.c index 3c02f5acbd..654b4630c5 100644 --- a/src/nvim/if_cscope.c +++ b/src/nvim/if_cscope.c @@ -1012,9 +1012,9 @@ static int cs_find_common(char *opt, char *pat, int forceit, int verbose, fclose(f); if (use_ll) /* Use location list */ wp = curwin; - /* '-' starts a new error list */ + // '-' starts a new error list if (qf_init(wp, tmp, (char_u *)"%f%*\\t%l%*\\t%m", - *qfpos == '-', cmdline) > 0) { + *qfpos == '-', cmdline, NULL) > 0) { if (postponed_split != 0) { (void)win_split(postponed_split > 0 ? postponed_split : 0, postponed_split_flags); diff --git a/src/nvim/main.c b/src/nvim/main.c index 0346414697..0b24023ad0 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -1369,7 +1369,7 @@ static void handle_quickfix(mparm_T *paramp) set_string_option_direct((char_u *)"ef", -1, paramp->use_ef, OPT_FREE, SID_CARG); vim_snprintf((char *)IObuff, IOSIZE, "cfile %s", p_ef); - if (qf_init(NULL, p_ef, p_efm, true, IObuff) < 0) { + if (qf_init(NULL, p_ef, p_efm, true, IObuff, p_menc) < 0) { ui_linefeed(); mch_exit(3); } diff --git a/src/nvim/option.c b/src/nvim/option.c index a345906200..192d2b0f78 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -2217,6 +2217,7 @@ void check_buf_options(buf_T *buf) check_string_option(&buf->b_p_tsr); check_string_option(&buf->b_p_lw); check_string_option(&buf->b_p_bkc); + check_string_option(&buf->b_p_menc); } /* @@ -2635,8 +2636,8 @@ did_set_string_option ( else if (varp == &p_ei) { if (check_ei() == FAIL) errmsg = e_invarg; - /* 'encoding' and 'fileencoding' */ - } else if (varp == &p_enc || gvarp == &p_fenc) { + // 'encoding', 'fileencoding' and 'makeencoding' + } else if (varp == &p_enc || gvarp == &p_fenc || gvarp == &p_menc) { if (gvarp == &p_fenc) { if (!MODIFIABLE(curbuf) && opt_flags != OPT_GLOBAL) { errmsg = e_modifiable; @@ -5400,6 +5401,9 @@ void unset_global_local_option(char *name, void *from) case PV_LW: clear_string_option(&buf->b_p_lw); break; + case PV_MENC: + clear_string_option(&buf->b_p_menc); + break; } } @@ -5433,6 +5437,7 @@ static char_u *get_varp_scope(vimoption_T *p, int opt_flags) case PV_UL: return (char_u *)&(curbuf->b_p_ul); case PV_LW: return (char_u *)&(curbuf->b_p_lw); case PV_BKC: return (char_u *)&(curbuf->b_p_bkc); + case PV_MENC: return (char_u *)&(curbuf->b_p_menc); } return NULL; /* "cannot happen" */ } @@ -5488,6 +5493,8 @@ static char_u *get_varp(vimoption_T *p) ? (char_u *)&(curbuf->b_p_ul) : p->var; case PV_LW: return *curbuf->b_p_lw != NUL ? (char_u *)&(curbuf->b_p_lw) : p->var; + case PV_MENC: return *curbuf->b_p_menc != NUL + ? (char_u *)&(curbuf->b_p_menc) : p->var; case PV_ARAB: return (char_u *)&(curwin->w_p_arab); case PV_LIST: return (char_u *)&(curwin->w_p_list); @@ -5885,6 +5892,7 @@ void buf_copy_options(buf_T *buf, int flags) buf->b_p_qe = vim_strsave(p_qe); buf->b_p_udf = p_udf; buf->b_p_lw = empty_option; + buf->b_p_menc = empty_option; /* * Don't copy the options set by ex_help(), use the saved values, diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h index 1b978137ae..864723a10c 100644 --- a/src/nvim/option_defs.h +++ b/src/nvim/option_defs.h @@ -489,6 +489,7 @@ EXTERN char_u *p_lcs; // 'listchars' EXTERN int p_lz; // 'lazyredraw' EXTERN int p_lpl; // 'loadplugins' EXTERN int p_magic; // 'magic' +EXTERN char_u *p_menc; // 'makeencoding' EXTERN char_u *p_mef; // 'makeef' EXTERN char_u *p_mp; // 'makeprg' EXTERN char_u *p_cc; // 'colorcolumn' @@ -736,6 +737,7 @@ enum { , BV_KP , BV_LISP , BV_LW + , BV_MENC , BV_MA , BV_ML , BV_MOD diff --git a/src/nvim/options.lua b/src/nvim/options.lua index 45efd49391..35baaf948f 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -1456,6 +1456,13 @@ return { varname='p_mef', defaults={if_true={vi=""}} }, + { + full_name='makeencoding', abbreviation='menc', + type='string', scope={'global', 'buffer'}, + vi_def=true, + varname='p_menc', + defaults={if_true={vi=""}} + }, { full_name='makeprg', abbreviation='mp', type='string', scope={'global', 'buffer'}, diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 3df025a51d..844ff071c9 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -160,6 +160,7 @@ typedef struct { buf_T *buf; linenr_T buflnum; linenr_T lnumlast; + vimconv_T vc; } qfstate_T; typedef struct { @@ -204,7 +205,8 @@ qf_init ( char_u *efile, char_u *errorformat, int newlist, /* TRUE: start a new error list */ - char_u *qf_title + char_u *qf_title, + char_u *enc ) { qf_info_T *qi = &ql_info; @@ -214,8 +216,8 @@ qf_init ( } return qf_init_ext(qi, efile, curbuf, NULL, errorformat, newlist, - (linenr_T)0, (linenr_T)0, - qf_title); + (linenr_T)0, (linenr_T)0, + qf_title, enc); } // Maximum number of bytes allowed per line while reading an errorfile. @@ -638,6 +640,22 @@ retry: } else { state->linebuf = IObuff; } + + // Convert a line if it contains a non-ASCII character + if (state->vc.vc_type != CONV_NONE && has_non_ascii(state->linebuf)) { + char_u *line = string_convert(&state->vc, state->linebuf, &state->linelen); + if (line != NULL) { + if (state->linelen < IOSIZE) { + STRLCPY(state->linebuf, line, state->linelen + 1); + xfree(line); + } else { + xfree(state->growbuf); + state->linebuf = state->growbuf = line; + state->growbufsiz = state->linelen < LINE_MAXLEN + ? state->linelen : LINE_MAXLEN; + } + } + } return QF_OK; } @@ -979,12 +997,12 @@ qf_init_ext( int newlist, /* TRUE: start a new error list */ linenr_T lnumfirst, /* first line number to use */ linenr_T lnumlast, /* last line number to use */ - char_u *qf_title + char_u *qf_title, + char_u *enc ) { - qfstate_T state = { NULL, 0, NULL, 0, NULL, NULL, NULL, NULL, - NULL, 0, 0 }; - qffields_T fields = { NULL, NULL, 0, 0L, 0, false, NULL, 0, 0, 0 }; + qfstate_T state; + qffields_T fields; qfline_T *old_last = NULL; bool adding = false; static efm_T *fmt_first = NULL; @@ -997,6 +1015,13 @@ qf_init_ext( xfree(qf_last_bufname); qf_last_bufname = NULL; + memset(&state, 0, sizeof(state)); + memset(&fields, 0, sizeof(fields)); + state.vc.vc_type = CONV_NONE; + if (enc != NULL && *enc != NUL) { + convert_setup(&state.vc, enc, p_enc); + } + fields.namebuf = xmalloc(CMDBUFFSIZE + 1); fields.errmsglen = CMDBUFFSIZE + 1; fields.errmsg = xmalloc(fields.errmsglen); @@ -1147,6 +1172,10 @@ qf_init_end: qf_update_buffer(qi, old_last); + if (state.vc.vc_type != CONV_NONE) { + convert_setup(&state.vc, NULL, NULL); + } + return retval; } @@ -3024,6 +3053,7 @@ void ex_make(exarg_T *eap) qf_info_T *qi = &ql_info; int res; char_u *au_name = NULL; + char_u *enc = (*curbuf->b_p_menc != NUL) ? curbuf->b_p_menc : p_menc; /* Redirect ":grep" to ":vimgrep" if 'grepprg' is "internal". */ if (grep_internal(eap->cmdidx)) { @@ -3083,9 +3113,8 @@ void ex_make(exarg_T *eap) res = qf_init(wp, fname, (eap->cmdidx != CMD_make && eap->cmdidx != CMD_lmake) ? p_gefm : p_efm, - (eap->cmdidx != CMD_grepadd - && eap->cmdidx != CMD_lgrepadd), - *eap->cmdlinep); + (eap->cmdidx != CMD_grepadd && eap->cmdidx != CMD_lgrepadd), + *eap->cmdlinep, enc); if (wp != NULL) qi = GET_LOC_LIST(wp); if (au_name != NULL) { @@ -3429,6 +3458,7 @@ void ex_cfile(exarg_T *eap) if (*eap->arg != NUL) set_string_option_direct((char_u *)"ef", -1, eap->arg, OPT_FREE, 0); + char_u *enc = (*curbuf->b_p_menc != NUL) ? curbuf->b_p_menc : p_menc; /* * This function is used by the :cfile, :cgetfile and :caddfile * commands. @@ -3441,7 +3471,7 @@ void ex_cfile(exarg_T *eap) */ if (qf_init(wp, p_ef, p_efm, (eap->cmdidx != CMD_caddfile && eap->cmdidx != CMD_laddfile), - *eap->cmdlinep) > 0 + *eap->cmdlinep, enc) > 0 && (eap->cmdidx == CMD_cfile || eap->cmdidx == CMD_lfile)) { if (au_name != NULL) @@ -4338,7 +4368,7 @@ void ex_cbuffer(exarg_T *eap) if (qf_init_ext(qi, NULL, buf, NULL, p_efm, (eap->cmdidx != CMD_caddbuffer && eap->cmdidx != CMD_laddbuffer), - eap->line1, eap->line2, qf_title) > 0) { + eap->line1, eap->line2, qf_title, NULL) > 0) { if (au_name != NULL) { apply_autocmds(EVENT_QUICKFIXCMDPOST, (char_u *)au_name, curbuf->b_fname, true, curbuf); @@ -4403,7 +4433,7 @@ void ex_cexpr(exarg_T *eap) if (qf_init_ext(qi, NULL, NULL, &tv, p_efm, (eap->cmdidx != CMD_caddexpr && eap->cmdidx != CMD_laddexpr), - (linenr_T)0, (linenr_T)0, *eap->cmdlinep) > 0) { + (linenr_T)0, (linenr_T)0, *eap->cmdlinep, NULL) > 0) { if (au_name != NULL) { apply_autocmds(EVENT_QUICKFIXCMDPOST, (char_u *)au_name, curbuf->b_fname, true, curbuf); diff --git a/src/nvim/testdir/Makefile b/src/nvim/testdir/Makefile index e1faaccb84..1f8cf8a0e7 100644 --- a/src/nvim/testdir/Makefile +++ b/src/nvim/testdir/Makefile @@ -65,6 +65,7 @@ NEW_TESTS ?= \ test_increment_dbcs.res \ test_lambda.res \ test_langmap.res \ + test_makeencoding.res \ test_marks.res \ test_match.res \ test_matchadd_conceal.res \ diff --git a/src/nvim/testdir/test_makeencoding.py b/src/nvim/testdir/test_makeencoding.py new file mode 100644 index 0000000000..041edadc0a --- /dev/null +++ b/src/nvim/testdir/test_makeencoding.py @@ -0,0 +1,67 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Test program for :make, :grep and :cgetfile. + +from __future__ import print_function, unicode_literals +import locale +import io +import sys + +def set_output_encoding(enc=None): + """Set the encoding of stdout and stderr + + arguments: + enc -- Encoding name. + If omitted, locale.getpreferredencoding() is used. + """ + if enc is None: + enc = locale.getpreferredencoding() + + def get_text_writer(fo, **kwargs): + kw = dict(kwargs) + kw.setdefault('errors', 'backslashreplace') # use \uXXXX style + kw.setdefault('closefd', False) + + if sys.version_info[0] < 3: + # Work around for Python 2.x + # New line conversion isn't needed here. Done in somewhere else. + writer = io.open(fo.fileno(), mode='w', newline='', **kw) + write = writer.write # save the original write() function + enc = locale.getpreferredencoding() + def convwrite(s): + if isinstance(s, bytes): + write(s.decode(enc)) # convert to unistr + else: + write(s) + try: + writer.flush() # needed on Windows + except IOError: + pass + writer.write = convwrite + else: + writer = io.open(fo.fileno(), mode='w', **kw) + return writer + + sys.stdout = get_text_writer(sys.stdout, encoding=enc) + sys.stderr = get_text_writer(sys.stderr, encoding=enc) + + +def main(): + enc = 'utf-8' + if len(sys.argv) > 1: + enc = sys.argv[1] + set_output_encoding(enc) + + message_tbl = { + 'utf-8': 'ÀÈÌÒÙ こんにちは 你好', + 'latin1': 'ÀÈÌÒÙ', + 'cp932': 'こんにちは', + 'cp936': '你好', + } + + print('Xfoobar.c(10) : %s (%s)' % (message_tbl[enc], enc)) + + +if __name__ == "__main__": + main() diff --git a/src/nvim/testdir/test_makeencoding.vim b/src/nvim/testdir/test_makeencoding.vim new file mode 100644 index 0000000000..a3d5538a47 --- /dev/null +++ b/src/nvim/testdir/test_makeencoding.vim @@ -0,0 +1,106 @@ +" Tests for 'makeencoding'. +if !has('multi_byte') + finish +endif + +source shared.vim + +let s:python = PythonProg() +if s:python == '' + " Can't run this test. + finish +endif + +let s:script = 'test_makeencoding.py' + +let s:message_tbl = { + \ 'utf-8': 'ÀÈÌÒÙ こんにちは 你好', + \ 'latin1': 'ÀÈÌÒÙ', + \ 'cp932': 'こんにちは', + \ 'cp936': '你好', + \} + + +" Tests for :cgetfile and :lgetfile. +func Test_getfile() + set errorfile=Xerror.txt + set errorformat=%f(%l)\ :\ %m + + " :cgetfile + for enc in keys(s:message_tbl) + let &makeencoding = enc + exec "silent !" . s:python . " " . s:script . " " . enc . " > " . &errorfile + cgetfile + copen + call assert_equal("Xfoobar.c|10| " . s:message_tbl[enc] . " (" . enc . ")", + \ getline('.')) + cclose + endfor + + " :lgetfile + for enc in keys(s:message_tbl) + let &makeencoding = enc + exec "silent !" . s:python . " " . s:script . " " . enc . " > " . &errorfile + lgetfile + lopen + call assert_equal("Xfoobar.c|10| " . s:message_tbl[enc] . " (" . enc . ")", + \ getline('.')) + lclose + endfor + + call delete(&errorfile) +endfunc + + +" Tests for :grep and :lgrep. +func Test_grep() + let &grepprg = s:python + set grepformat=%f(%l)\ :\ %m + + " :grep + for enc in keys(s:message_tbl) + let &makeencoding = enc + exec "silent grep! " . s:script . " " . enc + copen + call assert_equal("Xfoobar.c|10| " . s:message_tbl[enc] . " (" . enc . ")", + \ getline('.')) + cclose + endfor + + " :lgrep + for enc in keys(s:message_tbl) + let &makeencoding = enc + exec "silent lgrep! " . s:script . " " . enc + lopen + call assert_equal("Xfoobar.c|10| " . s:message_tbl[enc] . " (" . enc . ")", + \ getline('.')) + lclose + endfor +endfunc + + +" Tests for :make and :lmake. +func Test_make() + let &makeprg = s:python + set errorformat=%f(%l)\ :\ %m + + " :make + for enc in keys(s:message_tbl) + let &makeencoding = enc + exec "silent make! " . s:script . " " . enc + copen + call assert_equal("Xfoobar.c|10| " . s:message_tbl[enc] . " (" . enc . ")", + \ getline('.')) + cclose + endfor + + " :lmake + for enc in keys(s:message_tbl) + let &makeencoding = enc + exec "silent lmake! " . s:script . " " . enc + lopen + call assert_equal("Xfoobar.c|10| " . s:message_tbl[enc] . " (" . enc . ")", + \ getline('.')) + lclose + endfor +endfunc -- cgit From db0685a663a7d86d960f22e18f4e8e5041f80833 Mon Sep 17 00:00:00 2001 From: James McCoy Date: Sun, 17 Dec 2017 11:17:30 -0500 Subject: lint --- src/nvim/quickfix.c | 54 ++++++++++++++++++++++++++--------------------------- 1 file changed, 26 insertions(+), 28 deletions(-) diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 844ff071c9..696f123871 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -194,20 +194,19 @@ typedef struct { static char_u *qf_last_bufname = NULL; static bufref_T qf_last_bufref = { NULL, 0, 0 }; -/* - * Read the errorfile "efile" into memory, line by line, building the error - * list. Set the error list's title to qf_title. - * Return -1 for error, number of errors for success. - */ -int -qf_init ( - win_T *wp, - char_u *efile, - char_u *errorformat, - int newlist, /* TRUE: start a new error list */ - char_u *qf_title, - char_u *enc -) +/// Read the errorfile "efile" into memory, line by line, building the error +/// list. Set the error list's title to qf_title. +/// +/// @params wp If non-NULL, make a location list +/// @params efile If non-NULL, errorfile to parse +/// @params errorformat 'errorformat' string used to parse the error lines +/// @params newlist If true, create a new error list +/// @params qf_title If non-NULL, title of the error list +/// @params enc If non-NULL, encoding used to parse errors +/// +/// @returns -1 for error, number of errors for success. +int qf_init(win_T *wp, char_u *efile, char_u *errorformat, int newlist, + char_u *qf_title, char_u *enc) { qf_info_T *qi = &ql_info; @@ -994,9 +993,9 @@ qf_init_ext( buf_T *buf, typval_T *tv, char_u *errorformat, - int newlist, /* TRUE: start a new error list */ - linenr_T lnumfirst, /* first line number to use */ - linenr_T lnumlast, /* last line number to use */ + int newlist, // TRUE: start a new error list + linenr_T lnumfirst, // first line number to use + linenr_T lnumlast, // last line number to use char_u *qf_title, char_u *enc ) @@ -3115,8 +3114,9 @@ void ex_make(exarg_T *eap) && eap->cmdidx != CMD_lmake) ? p_gefm : p_efm, (eap->cmdidx != CMD_grepadd && eap->cmdidx != CMD_lgrepadd), *eap->cmdlinep, enc); - if (wp != NULL) + if (wp != NULL) { qi = GET_LOC_LIST(wp); + } if (au_name != NULL) { apply_autocmds(EVENT_QUICKFIXCMDPOST, au_name, curbuf->b_fname, TRUE, curbuf); @@ -3459,16 +3459,14 @@ void ex_cfile(exarg_T *eap) set_string_option_direct((char_u *)"ef", -1, eap->arg, OPT_FREE, 0); char_u *enc = (*curbuf->b_p_menc != NUL) ? curbuf->b_p_menc : p_menc; - /* - * This function is used by the :cfile, :cgetfile and :caddfile - * commands. - * :cfile always creates a new quickfix list and jumps to the - * first error. - * :cgetfile creates a new quickfix list but doesn't jump to the - * first error. - * :caddfile adds to an existing quickfix list. If there is no - * quickfix list then a new list is created. - */ + // This function is used by the :cfile, :cgetfile and :caddfile + // commands. + // :cfile always creates a new quickfix list and jumps to the + // first error. + // :cgetfile creates a new quickfix list but doesn't jump to the + // first error. + // :caddfile adds to an existing quickfix list. If there is no + // quickfix list then a new list is created. if (qf_init(wp, p_ef, p_efm, (eap->cmdidx != CMD_caddfile && eap->cmdidx != CMD_laddfile), *eap->cmdlinep, enc) > 0 -- cgit From f3d7eeff7736b10e43b52016fe9de5daa000a585 Mon Sep 17 00:00:00 2001 From: quinoa42 Date: Sat, 30 Sep 2017 16:08:32 -0700 Subject: health.vim: Try `pyenv root` #7341 --- runtime/autoload/health/provider.vim | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/runtime/autoload/health/provider.vim b/runtime/autoload/health/provider.vim index 205a23dcbd..fbd8d50f1b 100644 --- a/runtime/autoload/health/provider.vim +++ b/runtime/autoload/health/provider.vim @@ -253,14 +253,18 @@ function! s:check_python(version) abort if !empty(pyenv) if empty(pyenv_root) - call health#report_warn( - \ 'pyenv was found, but $PYENV_ROOT is not set.', - \ ['Did you follow the final install instructions?', - \ 'If you use a shell "framework" like Prezto or Oh My Zsh, try without.', - \ 'Try a different shell (bash).'] + call health#report_info( + \ 'pyenv was found, but $PYENV_ROOT is not set. `pyenv root` will be used.' + \ .' If you run into problems, try setting $PYENV_ROOT explicitly.' \ ) + let pyenv_root = s:trim(s:system([pyenv, 'root'])) + endif + + if !isdirectory(pyenv_root) + call health#report_error('Invalid pyenv root: '.pyenv_root) else - call health#report_ok(printf('pyenv found: "%s"', pyenv)) + call health#report_info(printf('pyenv: %s', pyenv)) + call health#report_info(printf('pyenv root: %s', pyenv_root)) endif endif -- cgit From 2851bb9effafda599e21e3b639e99c636ce70ba6 Mon Sep 17 00:00:00 2001 From: Alex Genco Date: Sun, 17 Dec 2017 08:47:52 -0800 Subject: health.vim: mention g:ruby_host_prog #7737 --- runtime/autoload/health/provider.vim | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/runtime/autoload/health/provider.vim b/runtime/autoload/health/provider.vim index fbd8d50f1b..39e592c471 100644 --- a/runtime/autoload/health/provider.vim +++ b/runtime/autoload/health/provider.vim @@ -455,10 +455,11 @@ function! s:check_ruby() abort let host = provider#ruby#Detect() if empty(host) - call health#report_warn('Missing "neovim" gem.', - \ ['Run in shell: gem install neovim', - \ 'Is the gem bin directory in $PATH? Check `gem environment`.', - \ 'If you are using rvm/rbenv/chruby, try "rehashing".']) + call health#report_warn("`neovim-ruby-host` not found.", + \ ['Run `gem install neovim` to ensure the neovim RubyGem is installed.', + \ 'Run `gem environment` to ensure the gem bin directory is in $PATH.', + \ 'If you are using rvm/rbenv/chruby, try "rehashing".', + \ 'See :help g:ruby_host_prog for non-standard gem installations.']) return endif call health#report_info('Host: '. host) -- cgit From 6e0c038a3cf3565b10ce3b49e4394ba464418b92 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sun, 17 Dec 2017 22:47:27 +0100 Subject: ASAN/LeakSanitizer: ignore loop_schedule_deferred() clang ASAN/LeakSanitizer error (observed in #7706): ==21832==ERROR: LeakSanitizer: detected memory leaks Direct leak of 56 byte(s) in 1 object(s) allocated from: 0 0x511b26 in malloc (/home/travis/build/neovim/neovim/build/bin/nvim+0x511b26) 1 0x1009a84 in try_malloc /home/travis/build/neovim/neovim/src/nvim/memory.c:87:15 2 0x1009c44 in xmalloc /home/travis/build/neovim/neovim/src/nvim/memory.c:121:15 3 0xaa8c36 in loop_schedule_deferred /home/travis/build/neovim/neovim/src/nvim/event/loop.c:89:19 4 0x190856a in tui_main /home/travis/build/neovim/neovim/src/nvim/tui/tui.c:367:5 5 0x1963d61 in ui_thread_run /home/travis/build/neovim/neovim/src/nvim/ui_bridge.c:106:3 6 0x2b5d4190d183 in start_thread /build/eglibc-SvCtMH/eglibc-2.19/nptl/pthread_create.c:312 Possible explanation: During exit, `Loop.thread_events` may not get flushed, so `loop_deferred_event()` is never called. We could instead try to unwind `Loop.thread_events` during teardown, but it seems lower-risk to just tell ASAN to ignore it. Valgrind does not complain: $ while :; do { 2>valglog.txt valgrind ./build/bin/nvim -u NONE +q ; } ; if ! [ $? = 0 ] ; then break ; fi ; done --- src/.asan-blacklist | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/.asan-blacklist b/src/.asan-blacklist index 928d81bd5a..9d7f721a88 100644 --- a/src/.asan-blacklist +++ b/src/.asan-blacklist @@ -1,3 +1,7 @@ # multiqueue.h pointer arithmetic is not accepted by asan fun:multiqueue_node_data fun:tv_dict_watcher_node_data + +# Allocation in loop_schedule_deferred() is freed by loop_deferred_event(), but +# this sometimes does not happen during teardown. +func:loop_schedule_deferred -- cgit From cca6d4b2674304d544b3880a616fd2ca7df2b891 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Mon, 18 Dec 2017 01:48:30 +0100 Subject: provider/nodejs: more robust version-check (#7738) --- runtime/autoload/provider/node.vim | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/runtime/autoload/provider/node.vim b/runtime/autoload/provider/node.vim index f75d2632be..4e737fb51c 100644 --- a/runtime/autoload/provider/node.vim +++ b/runtime/autoload/provider/node.vim @@ -6,15 +6,18 @@ let g:loaded_node_provider = 1 let s:job_opts = {'rpc': v:true, 'on_stderr': function('provider#stderr_collector')} function! s:is_minimum_version(version, min_major, min_minor) abort - let nodejs_version = a:version - if !a:version + if empty(a:version) let nodejs_version = get(split(system(['node', '-v']), "\n"), 0, '') if v:shell_error || nodejs_version[0] !=# 'v' return 0 endif + else + let nodejs_version = a:version endif + " Remove surrounding junk. Example: 'v4.12.0' => '4.12.0' + let nodejs_version = matchstr(nodejs_version, '\(\d\.\?\)\+') " [major, minor, patch] - let v_list = !!a:version ? a:version : split(nodejs_version[1:], '\.') + let v_list = split(nodejs_version, '\.') return len(v_list) == 3 \ && ((str2nr(v_list[0]) > str2nr(a:min_major)) \ || (str2nr(v_list[0]) == str2nr(a:min_major) -- cgit From ccbf14322a8d56567a0fe9708cead27818d14271 Mon Sep 17 00:00:00 2001 From: James McCoy Date: Sun, 17 Dec 2017 20:51:51 -0500 Subject: vim-patch:8.0.0404: not enough testing for quickfix Problem: Not enough testing for quickfix. Solution: Add some more tests. (Yegappan Lakshmanan) https://github.com/vim/vim/commit/391b1dd040af204b150d43c5a1c97477ee450a28 --- src/nvim/testdir/test_quickfix.vim | 83 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim index aff5fc2eed..773502ede4 100644 --- a/src/nvim/testdir/test_quickfix.vim +++ b/src/nvim/testdir/test_quickfix.vim @@ -128,6 +128,14 @@ func XlistTests(cchar) let l = split(result, "\n") call assert_equal([' 2 Xtestfile1:1 col 3: Line1', \ ' 3: non-error 2', ' 4 Xtestfile2:2 col 2: Line2'], l) + + " Test for '+' + redir => result + Xlist! +2 + redir END + let l = split(result, "\n") + call assert_equal([' 2 Xtestfile1:1 col 3: Line1', + \ ' 3: non-error 2', ' 4 Xtestfile2:2 col 2: Line2'], l) endfunc func Test_clist() @@ -907,6 +915,11 @@ func Test_efm2() \ "(67,3) warning: 's' already defined" \] set efm=%+P[%f],(%l\\,%c)%*[\ ]%t%*[^:]:\ %m,%-Q + " To exercise the push/pop file functionality in quickfix, the test files + " need to be created. + call writefile(['Line1'], 'Xtestfile1') + call writefile(['Line2'], 'Xtestfile2') + call writefile(['Line3'], 'Xtestfile3') cexpr "" for l in lines caddexpr l @@ -917,6 +930,9 @@ func Test_efm2() call assert_equal(2, l[2].col) call assert_equal('w', l[2].type) call assert_equal('e', l[3].type) + call delete('Xtestfile1') + call delete('Xtestfile2') + call delete('Xtestfile3') " Tests for %E, %C and %Z format specifiers let lines = ["Error 275", @@ -1351,11 +1367,25 @@ func Test_switchbuf() call assert_equal(2, winnr('$')) call assert_equal(1, bufwinnr('Xqftestfile3')) + " If only quickfix window is open in the current tabpage, jumping to an + " entry with 'switchubf' set to 'usetab' should search in other tabpages. enew | only + set switchbuf=usetab + tabedit Xqftestfile1 + tabedit Xqftestfile2 + tabedit Xqftestfile3 + tabfirst + copen | only + clast + call assert_equal(4, tabpagenr()) + tabfirst | tabonly | enew | only call delete('Xqftestfile1') call delete('Xqftestfile2') call delete('Xqftestfile3') + set switchbuf&vim + + enew | only endfunc func Xadjust_qflnum(cchar) @@ -1673,3 +1703,56 @@ func Test_dirstack_cleanup() caddbuffer let &efm = save_efm endfunc + +" Tests for jumping to entries from the location list window and quickfix +" window +func Test_cwindow_jump() + set efm=%f%%%l%%%m + lgetexpr ["F1%10%Line 10", "F2%20%Line 20", "F3%30%Line 30"] + lopen | only + lfirst + call assert_true(winnr('$') == 2) + call assert_true(winnr() == 1) + " Location list for the new window should be set + call assert_true(getloclist(0)[2].text == 'Line 30') + + " Open a scratch buffer + " Open a new window and create a location list + " Open the location list window and close the other window + " Jump to an entry. + " Should create a new window and jump to the entry. The scrtach buffer + " should not be used. + enew | only + set buftype=nofile + below new + lgetexpr ["F1%10%Line 10", "F2%20%Line 20", "F3%30%Line 30"] + lopen + 2wincmd c + lnext + call assert_true(winnr('$') == 3) + call assert_true(winnr() == 2) + + " Open two windows with two different location lists + " Open the location list window and close the previous window + " Jump to an entry in the location list window + " Should open the file in the first window and not set the location list. + enew | only + lgetexpr ["F1%5%Line 5"] + below new + lgetexpr ["F1%10%Line 10", "F2%20%Line 20", "F3%30%Line 30"] + lopen + 2wincmd c + lnext + call assert_true(winnr() == 1) + call assert_true(getloclist(0)[0].text == 'Line 5') + + enew | only + cgetexpr ["F1%10%Line 10", "F2%20%Line 20", "F3%30%Line 30"] + copen + cnext + call assert_true(winnr('$') == 2) + call assert_true(winnr() == 1) + + enew | only + set efm&vim +endfunc -- cgit From 853634881335eb9fe4c593710fbb8c71324e1690 Mon Sep 17 00:00:00 2001 From: James McCoy Date: Sun, 17 Dec 2017 20:56:01 -0500 Subject: vim-patch:8.0.0484: :lhelpgrep does not fail after a successful one Problem: Using :lhelpgrep with an argument that should fail does not produce an error if the previous :helpgrep worked. Solution: Use another way to detect that autocommands made the quickfix info invalid. (Yegappan Lakshmanan) https://github.com/vim/vim/commit/ee85df37634dfb0c40ae5de0b4f246aef460b392 --- src/nvim/quickfix.c | 10 +- src/nvim/testdir/test_quickfix.vim | 184 +++++++++++++++++++++++++++++-------- 2 files changed, 151 insertions(+), 43 deletions(-) diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 696f123871..6d59d15515 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -4502,6 +4502,9 @@ void ex_helpgrep(exarg_T *eap) } } + // Autocommands may change the list. Save it for later comparison + qf_info_T *save_qi = qi; + regmatch.regprog = vim_regcomp(eap->arg, RE_MAGIC + RE_STRING); regmatch.rm_ic = FALSE; if (regmatch.regprog != NULL) { @@ -4614,10 +4617,11 @@ void ex_helpgrep(exarg_T *eap) if (au_name != NULL) { apply_autocmds(EVENT_QUICKFIXCMDPOST, au_name, - curbuf->b_fname, TRUE, curbuf); - if (!new_qi && qi != &ql_info && qf_find_buf(qi) == NULL) - /* autocommands made "qi" invalid */ + curbuf->b_fname, true, curbuf); + if (!new_qi && qi != save_qi && qf_find_buf(qi) == NULL) { + // autocommands made "qi" invalid return; + } } /* Jump to first match. */ diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim index 773502ede4..3d8d18cf88 100644 --- a/src/nvim/testdir/test_quickfix.vim +++ b/src/nvim/testdir/test_quickfix.vim @@ -31,7 +31,8 @@ func s:setup_commands(cchar) command! -nargs=* -bang Xnfile cnfile command! -nargs=* -bang Xpfile cpfile command! -nargs=* Xexpr cexpr - command! -nargs=* Xvimgrep vimgrep + command! -range -nargs=* Xvimgrep vimgrep + command! -nargs=* Xvimgrepadd vimgrepadd command! -nargs=* Xgrep grep command! -nargs=* Xgrepadd grepadd command! -nargs=* Xhelpgrep helpgrep @@ -61,7 +62,8 @@ func s:setup_commands(cchar) command! -nargs=* -bang Xnfile lnfile command! -nargs=* -bang Xpfile lpfile command! -nargs=* Xexpr lexpr - command! -nargs=* Xvimgrep lvimgrep + command! -range -nargs=* Xvimgrep lvimgrep + command! -nargs=* Xvimgrepadd lvimgrepadd command! -nargs=* Xgrep lgrep command! -nargs=* Xgrepadd lgrepadd command! -nargs=* Xhelpgrep lhelpgrep @@ -85,57 +87,52 @@ func XlistTests(cchar) \ 'non-error 3', 'Xtestfile3:3:1:Line3'] " List only valid entries - redir => result - Xlist - redir END - let l = split(result, "\n") + let l = split(execute('Xlist', ''), "\n") call assert_equal([' 2 Xtestfile1:1 col 3: Line1', \ ' 4 Xtestfile2:2 col 2: Line2', \ ' 6 Xtestfile3:3 col 1: Line3'], l) " List all the entries - redir => result - Xlist! - redir END - let l = split(result, "\n") + let l = split(execute('Xlist!', ''), "\n") call assert_equal([' 1: non-error 1', ' 2 Xtestfile1:1 col 3: Line1', \ ' 3: non-error 2', ' 4 Xtestfile2:2 col 2: Line2', \ ' 5: non-error 3', ' 6 Xtestfile3:3 col 1: Line3'], l) " List a range of errors - redir => result - Xlist 3,6 - redir END - let l = split(result, "\n") + let l = split(execute('Xlist 3,6', ''), "\n") call assert_equal([' 4 Xtestfile2:2 col 2: Line2', \ ' 6 Xtestfile3:3 col 1: Line3'], l) - redir => result - Xlist! 3,4 - redir END - let l = split(result, "\n") + let l = split(execute('Xlist! 3,4', ''), "\n") call assert_equal([' 3: non-error 2', ' 4 Xtestfile2:2 col 2: Line2'], l) - redir => result - Xlist -6,-4 - redir END - let l = split(result, "\n") + let l = split(execute('Xlist -6,-4', ''), "\n") call assert_equal([' 2 Xtestfile1:1 col 3: Line1'], l) - redir => result - Xlist! -5,-3 - redir END - let l = split(result, "\n") + let l = split(execute('Xlist! -5,-3', ''), "\n") call assert_equal([' 2 Xtestfile1:1 col 3: Line1', \ ' 3: non-error 2', ' 4 Xtestfile2:2 col 2: Line2'], l) " Test for '+' - redir => result - Xlist! +2 - redir END - let l = split(result, "\n") + let l = split(execute('Xlist! +2', ''), "\n") call assert_equal([' 2 Xtestfile1:1 col 3: Line1', \ ' 3: non-error 2', ' 4 Xtestfile2:2 col 2: Line2'], l) + + " Different types of errors + call g:Xsetlist([{'lnum':10,'col':5,'type':'W', 'text':'Warning','nr':11}, + \ {'lnum':20,'col':10,'type':'e','text':'Error','nr':22}, + \ {'lnum':30,'col':15,'type':'i','text':'Info','nr':33}, + \ {'lnum':40,'col':20,'type':'x', 'text':'Other','nr':44}, + \ {'lnum':50,'col':25,'type':"\",'text':'one','nr':55}]) + let l = split(execute('Xlist', ""), "\n") + call assert_equal([' 1:10 col 5 warning 11: Warning', + \ ' 2:20 col 10 error 22: Error', + \ ' 3:30 col 15 info 33: Info', + \ ' 4:40 col 20 x 44: Other', + \ ' 5:50 col 25 55: one'], l) + + " Error cases + call assert_fails('Xlist abc', 'E488:') endfunc func Test_clist() @@ -324,6 +321,23 @@ func XbufferTests(cchar) \ l[3].lnum == 750 && l[3].col == 25 && l[3].text ==# 'Line 750') enew! + " Check for invalid buffer + call assert_fails('Xbuffer 199', 'E474:') + + " Check for unloaded buffer + edit Xtestfile1 + let bnr = bufnr('%') + enew! + call assert_fails('Xbuffer ' . bnr, 'E681:') + + " Check for invalid range + " Using Xbuffer will not run the range check in the cbuffer/lbuffer + " commands. So directly call the commands. + if (a:cchar == 'c') + call assert_fails('900,999cbuffer', 'E16:') + else + call assert_fails('900,999lbuffer', 'E16:') + endif endfunc func Test_cbuffer() @@ -372,6 +386,9 @@ func Xtest_browse(cchar) call assert_equal('Xqftestfile1', bufname('%')) call assert_equal(5, line('.')) + Xexpr "" + call assert_fails('Xnext', 'E42:') + call delete('Xqftestfile1') call delete('Xqftestfile2') endfunc @@ -393,6 +410,9 @@ func s:test_xhelpgrep(cchar) call assert_true(w:quickfix_title =~ title_text, w:quickfix_title) " This wipes out the buffer, make sure that doesn't cause trouble. Xclose + + " Search for non existing help string + call assert_fails('Xhelpgrep a1b2c3', 'E480:') endfunc func Test_helpgrep() @@ -586,7 +606,7 @@ func Test_locationlist() wincmd n | only augroup! testgroup - endfunc +endfunc func Test_locationlist_curwin_was_closed() augroup testgroup @@ -605,7 +625,7 @@ func Test_locationlist_curwin_was_closed() call assert_fails('lrewind', 'E924:') augroup! testgroup - endfunc +endfunc func Test_locationlist_cross_tab_jump() call writefile(['loclistfoo'], 'loclistfoo') @@ -742,7 +762,7 @@ func Test_efm1() call delete('Xerrorfile1') call delete('Xerrorfile2') call delete('Xtestfile') - endfunc +endfunc " Test for quickfix directory stack support func s:dir_stack_tests(cchar) @@ -901,20 +921,26 @@ func Test_efm2() call assert_equal(l[0].pattern, '^\VLine search text\$') call assert_equal(l[0].lnum, 0) + let l = split(execute('clist', ''), "\n") + call assert_equal([' 1 Xtestfile:^\VLine search text\$: '], l) + " Test for %P, %Q and %t format specifiers let lines=["[Xtestfile1]", \ "(1,17) error: ';' missing", \ "(21,2) warning: variable 'z' not defined", \ "(67,3) error: end of file found before string ended", + \ "--", \ "", \ "[Xtestfile2]", + \ "--", \ "", \ "[Xtestfile3]", \ "NEW compiler v1.1", \ "(2,2) warning: variable 'x' not defined", - \ "(67,3) warning: 's' already defined" + \ "(67,3) warning: 's' already defined", + \ "--" \] - set efm=%+P[%f],(%l\\,%c)%*[\ ]%t%*[^:]:\ %m,%-Q + set efm=%+P[%f]%r,(%l\\,%c)%*[\ ]%t%*[^:]:\ %m,%+Q--%r " To exercise the push/pop file functionality in quickfix, the test files " need to be created. call writefile(['Line1'], 'Xtestfile1') @@ -925,7 +951,7 @@ func Test_efm2() caddexpr l endfor let l = getqflist() - call assert_equal(9, len(l)) + call assert_equal(12, len(l)) call assert_equal(21, l[2].lnum) call assert_equal(2, l[2].col) call assert_equal('w', l[2].type) @@ -1080,6 +1106,13 @@ func SetXlistTests(cchar, bnum) call g:Xsetlist([]) let l = g:Xgetlist() call assert_equal(0, len(l)) + + " Error cases: + " Refer to a non-existing buffer and pass a non-dictionary type + call assert_fails("call g:Xsetlist([{'bufnr':998, 'lnum':4}," . + \ " {'bufnr':999, 'lnum':5}])", 'E92:') + call g:Xsetlist([[1, 2,3]]) + call assert_equal(0, len(g:Xgetlist())) endfunc func Test_setqflist() @@ -1098,7 +1131,8 @@ func Xlist_empty_middle(cchar) call s:setup_commands(a:cchar) " create three quickfix lists - Xvimgrep Test_ test_quickfix.vim + let @/ = 'Test_' + Xvimgrep // test_quickfix.vim let testlen = len(g:Xgetlist()) call assert_true(testlen > 0) Xvimgrep empty test_quickfix.vim @@ -1591,6 +1625,22 @@ func Xproperty_tests(cchar) call g:Xsetlist([], ' ', {'title' : 'N3'}) call assert_equal('N2', g:Xgetlist({'nr':2, 'title':1}).title) + " Changing the title of an earlier quickfix list + call g:Xsetlist([], ' ', {'title' : 'NewTitle', 'nr' : 2}) + call assert_equal('NewTitle', g:Xgetlist({'nr':2, 'title':1}).title) + + " Changing the title of an invalid quickfix list + call assert_equal(-1, g:Xsetlist([], ' ', + \ {'title' : 'SomeTitle', 'nr' : 99})) + call assert_equal(-1, g:Xsetlist([], ' ', + \ {'title' : 'SomeTitle', 'nr' : 'abc'})) + + if a:cchar == 'c' + copen + call assert_equal({'winid':win_getid()}, getqflist({'winid':1})) + cclose + endif + " Invalid arguments call assert_fails('call g:Xgetlist([])', 'E715') call assert_fails('call g:Xsetlist([], "a", [])', 'E715') @@ -1598,16 +1648,18 @@ func Xproperty_tests(cchar) call assert_equal(-1, s) call assert_equal({}, g:Xgetlist({'abc':1})) + call assert_equal({}, g:Xgetlist({'nr':99, 'title':1})) + call assert_equal({}, g:Xgetlist({'nr':[], 'title':1})) if a:cchar == 'l' - call assert_equal({}, getloclist(99, {'title': 1})) + call assert_equal({}, getloclist(99, {'title': 1})) endif - endfunc +endfunc func Test_qf_property() call Xproperty_tests('c') call Xproperty_tests('l') - endfunc +endfunc " Tests for the QuickFixCmdPre/QuickFixCmdPost autocommands func QfAutoCmdHandler(loc, cmd) @@ -1756,3 +1808,55 @@ func Test_cwindow_jump() enew | only set efm&vim endfunc + +func XvimgrepTests(cchar) + call s:setup_commands(a:cchar) + + call writefile(['Editor:VIM vim', + \ 'Editor:Emacs EmAcS', + \ 'Editor:Notepad NOTEPAD'], 'Xtestfile1') + call writefile(['Linux', 'MacOS', 'MS-Windows'], 'Xtestfile2') + + " Error cases + call assert_fails('Xvimgrep /abc *', 'E682:') + + let @/='' + call assert_fails('Xvimgrep // *', 'E35:') + + call assert_fails('Xvimgrep abc', 'E683:') + call assert_fails('Xvimgrep a1b2c3 Xtestfile1', 'E480:') + call assert_fails('Xvimgrep pat Xa1b2c3', 'E480:') + + Xexpr "" + Xvimgrepadd Notepad Xtestfile1 + Xvimgrepadd MacOS Xtestfile2 + let l = g:Xgetlist() + call assert_equal(2, len(l)) + call assert_equal('Editor:Notepad NOTEPAD', l[0].text) + + Xvimgrep #\cvim#g Xtestfile? + let l = g:Xgetlist() + call assert_equal(2, len(l)) + call assert_equal(8, l[0].col) + call assert_equal(12, l[1].col) + + 1Xvimgrep ?Editor? Xtestfile* + let l = g:Xgetlist() + call assert_equal(1, len(l)) + call assert_equal('Editor:VIM vim', l[0].text) + + edit +3 Xtestfile2 + Xvimgrep +\cemacs+j Xtestfile1 + let l = g:Xgetlist() + call assert_equal('Xtestfile2', bufname('')) + call assert_equal('Editor:Emacs EmAcS', l[0].text) + + call delete('Xtestfile1') + call delete('Xtestfile2') +endfunc + +" Tests for the :vimgrep command +func Test_vimgrep() + call XvimgrepTests('c') + call XvimgrepTests('l') +endfunc -- cgit From fb8592b7ba673f230f144dc8c29e5dffc33fc50a Mon Sep 17 00:00:00 2001 From: James McCoy Date: Mon, 18 Dec 2017 14:26:49 -0500 Subject: vim-patch:8.0.0517: there is no way to remove quickfix lists Problem: There is no way to remove quickfix lists (for testing). Solution: Add the 'f' action to setqflist(). Add tests. (Yegappan Lakshmanan) https://github.com/vim/vim/commit/b6fa30ccc39cdb7f1d07b99fe2f4c6b61671dac2 --- runtime/doc/eval.txt | 19 +++++++++------- src/nvim/eval.c | 3 ++- src/nvim/quickfix.c | 15 ++++++++++++- src/nvim/testdir/test_quickfix.vim | 46 +++++++++++++++++++++++++++++++++----- 4 files changed, 67 insertions(+), 16 deletions(-) diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index e337c5d6d5..efc8775d0a 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -6855,16 +6855,19 @@ setqflist({list} [, {action}[, {what}]]) *setqflist()* Note that the list is not exactly the same as what |getqflist()| returns. - *E927* - If {action} is set to 'a', then the items from {list} are - added to the existing quickfix list. If there is no existing - list, then a new list is created. + {action} values: *E927* + 'a' The items from {list} are added to the existing + quickfix list. If there is no existing list, then a + new list is created. - If {action} is set to 'r', then the items from the current - quickfix list are replaced with the items from {list}. This - can also be used to clear the list: > - :call setqflist([], 'r') + 'r' The items from the current quickfix list are replaced + with the items from {list}. This can also be used to + clear the list: > + :call setqflist([], 'r') < + 'f' All the quickfix lists in the quickfix stack are + freed. + If {action} is not present or is set to ' ', then a new list is created. diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 7fa9f7563e..1461ddb8f9 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -14594,7 +14594,8 @@ static void set_qf_ll_list(win_T *wp, typval_T *args, typval_T *rettv) return; } const char *const act = tv_get_string_chk(action_arg); - if ((*act == 'a' || *act == 'r' || *act == ' ') && act[1] == NUL) { + if ((*act == 'a' || *act == 'r' || *act == ' ' || *act == 'f') + && act[1] == NUL) { action = *act; } else { EMSG2(_(e_invact), act); diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 6d59d15515..c7326c46fe 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -4267,6 +4267,16 @@ static int qf_set_properties(qf_info_T *qi, dict_T *what, int action) return retval; } +static void qf_free_stack(win_T *wp, qf_info_T *qi) +{ + qf_free_all(wp); + if (wp == NULL) { + // quickfix list + qi->qf_curlist = 0; + qi->qf_listcount = 0; + } +} + // Populate the quickfix list with the items supplied in the list // of dictionaries. "title" will be copied to w:quickfix_title // "action" is 'a' for add, 'r' for replace. Otherwise create a new list. @@ -4280,7 +4290,10 @@ int set_errorlist(win_T *wp, list_T *list, int action, char_u *title, qi = ll_get_or_alloc_list(wp); } - if (what != NULL) { + if (action == 'f') { + // Free the entire quickfix or location list stack + qf_free_stack(wp, qi); + } else if (what != NULL) { retval = qf_set_properties(qi, what, action); } else { retval = qf_add_entries(qi, list, title, action); diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim index 3d8d18cf88..b953df3fc8 100644 --- a/src/nvim/testdir/test_quickfix.vim +++ b/src/nvim/testdir/test_quickfix.vim @@ -38,6 +38,7 @@ func s:setup_commands(cchar) command! -nargs=* Xhelpgrep helpgrep let g:Xgetlist = function('getqflist') let g:Xsetlist = function('setqflist') + call setqflist([], 'f') else command! -nargs=* -bang Xlist llist command! -nargs=* Xgetexpr lgetexpr @@ -69,6 +70,7 @@ func s:setup_commands(cchar) command! -nargs=* Xhelpgrep lhelpgrep let g:Xgetlist = function('getloclist', [0]) let g:Xsetlist = function('setloclist', [0]) + call setloclist(0, [], 'f') endif endfunc @@ -76,6 +78,9 @@ endfunc func XlistTests(cchar) call s:setup_commands(a:cchar) + if a:cchar == 'l' + call assert_fails('llist', 'E776:') + endif " With an empty list, command should return error Xgetexpr [] silent! Xlist @@ -146,6 +151,9 @@ endfunc func XageTests(cchar) call s:setup_commands(a:cchar) + let list = [{'bufnr': 1, 'lnum': 1}] + call g:Xsetlist(list) + " Jumping to a non existent list should return error silent! Xolder 99 call assert_true(v:errmsg ==# 'E380: At bottom of quickfix stack') @@ -179,11 +187,7 @@ func XageTests(cchar) endfunc func Test_cage() - let list = [{'bufnr': 1, 'lnum': 1}] - call setqflist(list) call XageTests('c') - - call setloclist(0, list) call XageTests('l') endfunc @@ -192,6 +196,11 @@ endfunc func XwindowTests(cchar) call s:setup_commands(a:cchar) + " Opening the location list window without any errors should fail + if a:cchar == 'l' + call assert_fails('lopen', 'E776:') + endif + " Create a list with no valid entries Xgetexpr ['non-error 1', 'non-error 2', 'non-error 3'] @@ -232,6 +241,19 @@ func XwindowTests(cchar) " Calling cwindow should close the quickfix window with no valid errors Xwindow call assert_true(winnr('$') == 1) + + if a:cchar == 'c' + " Opening the quickfix window in multiple tab pages should reuse the + " quickfix buffer + Xgetexpr ['Xtestfile1:1:3:Line1', 'Xtestfile2:2:2:Line2', + \ 'Xtestfile3:3:1:Line3'] + Xopen + let qfbufnum = bufnr('%') + tabnew + Xopen + call assert_equal(qfbufnum, bufnr('%')) + new | only | tabonly + endif endfunc func Test_cwindow() @@ -360,6 +382,13 @@ endfunc func Xtest_browse(cchar) call s:setup_commands(a:cchar) + " Jumping to first or next location list entry without any error should + " result in failure + if a:cchar == 'l' + call assert_fails('lfirst', 'E776:') + call assert_fails('lnext', 'E776:') + endif + call s:create_test_file('Xqftestfile1') call s:create_test_file('Xqftestfile2') @@ -1532,6 +1561,11 @@ endfunc func XbottomTests(cchar) call s:setup_commands(a:cchar) + " Calling lbottom without any errors should fail + if a:cchar == 'l' + call assert_fails('lbottom', 'E776:') + endif + call g:Xsetlist([{'filename': 'foo', 'lnum': 42}]) Xopen let wid = win_getid() @@ -1553,10 +1587,9 @@ endfunc func HistoryTest(cchar) call s:setup_commands(a:cchar) - call assert_fails(a:cchar . 'older 99', 'E380:') " clear all lists after the first one, then replace the first one. call g:Xsetlist([]) - Xolder + call assert_fails('Xolder 99', 'E380:') let entry = {'filename': 'foo', 'lnum': 42} call g:Xsetlist([entry], 'r') call g:Xsetlist([entry, entry]) @@ -1599,6 +1632,7 @@ func Xproperty_tests(cchar) call assert_fails('call g:Xsetlist([], "a", [])', 'E715:') " Set and get the title + call g:Xsetlist([]) Xopen wincmd p call g:Xsetlist([{'filename':'foo', 'lnum':27}]) -- cgit From f0bd2bc39aeb7797e3dd5c14797606dac45c8a75 Mon Sep 17 00:00:00 2001 From: James McCoy Date: Mon, 18 Dec 2017 14:36:13 -0500 Subject: vim-patch:8.0.0536: quickfix window not updated when freeing quickfix stack Problem: Quickfix window not updated when freeing quickfix stack. Solution: Update the quickfix window. (Yegappan Lakshmanan) https://github.com/vim/vim/commit/69f40be64555d50f603c6f22722cf762aaa6bbc1 --- src/nvim/quickfix.c | 44 ++++++++++++++++++++++++++ src/nvim/testdir/test_quickfix.vim | 63 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+) diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index c7326c46fe..9a4341f3a4 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -4267,13 +4267,57 @@ static int qf_set_properties(qf_info_T *qi, dict_T *what, int action) return retval; } +// Find the non-location list window with the specified location list. +static win_T * find_win_with_ll(qf_info_T *qi) +{ + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if ((wp->w_llist == qi) && !bt_quickfix(wp->w_buffer)) { + return wp; + } + } + + return NULL; +} + +// Free the entire quickfix/location list stack. +// If the quickfix/location list window is open, then clear it. static void qf_free_stack(win_T *wp, qf_info_T *qi) { + win_T *qfwin = qf_find_win(qi); + + if (qfwin != NULL) { + // If the quickfix/location list window is open, then clear it + if (qi->qf_curlist < qi->qf_listcount) { + qf_free(qi, qi->qf_curlist); + } + qf_update_buffer(qi, NULL); + } + + win_T *llwin = NULL; + win_T *orig_wp = wp; + if (wp != NULL && IS_LL_WINDOW(wp)) { + // If in the location list window, then use the non-location list + // window with this location list (if present) + llwin = find_win_with_ll(qi); + if (llwin != NULL) { + wp = llwin; + } + } + qf_free_all(wp); if (wp == NULL) { // quickfix list qi->qf_curlist = 0; qi->qf_listcount = 0; + } else if (IS_LL_WINDOW(orig_wp)) { + // If the location list window is open, then create a new empty location + // list + qf_info_T *new_ll = ll_new_list(); + orig_wp->w_llist_ref = new_ll; + if (llwin != NULL) { + llwin->w_llist = new_ll; + new_ll->qf_refcount++; + } } } diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim index b953df3fc8..40ad387dee 100644 --- a/src/nvim/testdir/test_quickfix.vim +++ b/src/nvim/testdir/test_quickfix.vim @@ -1894,3 +1894,66 @@ func Test_vimgrep() call XvimgrepTests('c') call XvimgrepTests('l') endfunc + +func XfreeTests(cchar) + call s:setup_commands(a:cchar) + + enew | only + + " Deleting the quickfix stack should work even When the current list is + " somewhere in the middle of the stack + Xexpr ['Xfile1:10:10:Line 10', 'Xfile1:15:15:Line 15'] + Xexpr ['Xfile2:20:20:Line 20', 'Xfile2:25:25:Line 25'] + Xexpr ['Xfile3:30:30:Line 30', 'Xfile3:35:35:Line 35'] + Xolder + call g:Xsetlist([], 'f') + call assert_equal(0, len(g:Xgetlist())) + + " After deleting the stack, adding a new list should create a stack with a + " single list. + Xexpr ['Xfile1:10:10:Line 10', 'Xfile1:15:15:Line 15'] + call assert_equal(1, g:Xgetlist({'all':1}).nr) + + " Deleting the stack from a quickfix window should update/clear the + " quickfix/location list window. + Xexpr ['Xfile1:10:10:Line 10', 'Xfile1:15:15:Line 15'] + Xexpr ['Xfile2:20:20:Line 20', 'Xfile2:25:25:Line 25'] + Xexpr ['Xfile3:30:30:Line 30', 'Xfile3:35:35:Line 35'] + Xolder + Xwindow + call g:Xsetlist([], 'f') + call assert_equal(2, winnr('$')) + call assert_equal(1, line('$')) + Xclose + + " Deleting the stack from a non-quickfix window should update/clear the + " quickfix/location list window. + Xexpr ['Xfile1:10:10:Line 10', 'Xfile1:15:15:Line 15'] + Xexpr ['Xfile2:20:20:Line 20', 'Xfile2:25:25:Line 25'] + Xexpr ['Xfile3:30:30:Line 30', 'Xfile3:35:35:Line 35'] + Xolder + Xwindow + wincmd p + call g:Xsetlist([], 'f') + call assert_equal(0, len(g:Xgetlist())) + wincmd p + call assert_equal(2, winnr('$')) + call assert_equal(1, line('$')) + + " After deleting the location list stack, if the location list window is + " opened, then a new location list should be created. So opening the + " location list window again should not create a new window. + if a:cchar == 'l' + lexpr ['Xfile1:10:10:Line 10', 'Xfile1:15:15:Line 15'] + wincmd p + lopen + call assert_equal(2, winnr('$')) + endif + Xclose +endfunc + +" Tests for the quickifx free functionality +func Test_qf_free() + call XfreeTests('c') + call XfreeTests('l') +endfunc -- cgit From 765ff94b5be90bc33eb8eba3fdfe3e910124450d Mon Sep 17 00:00:00 2001 From: James McCoy Date: Mon, 18 Dec 2017 14:56:49 -0500 Subject: vim-patch:8.0.0584: memory leak when executing quickfix tests Problem: Memory leak when executing quickfix tests. Solution: Free the list reference. (Yegappan Lakshmanan) https://github.com/vim/vim/commit/d788f6fe89c77262c474de323f5dab6d1c814e27 --- src/nvim/quickfix.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 9a4341f3a4..73ad14ab22 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -4313,6 +4313,10 @@ static void qf_free_stack(win_T *wp, qf_info_T *qi) // If the location list window is open, then create a new empty location // list qf_info_T *new_ll = ll_new_list(); + + // first free the list reference in the location list window + ll_free_all(&orig_wp->w_llist_ref); + orig_wp->w_llist_ref = new_ll; if (llwin != NULL) { llwin->w_llist = new_ll; -- cgit From c01a84e34407a87752db8fba20a0edabc8a8bf2f Mon Sep 17 00:00:00 2001 From: Issam Maghni Date: Mon, 18 Dec 2017 18:53:53 -0500 Subject: Updating to latest UNIBILIUM (#7745) Update to unibilium 1.2.1 --- third-party/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/third-party/CMakeLists.txt b/third-party/CMakeLists.txt index ab9ff1f60d..66f921ffcc 100644 --- a/third-party/CMakeLists.txt +++ b/third-party/CMakeLists.txt @@ -103,8 +103,8 @@ set(LUA_SHA256 2640fc56a795f29d28ef15e13c34a47e223960b0240e8cb0a82d9b0738695333) set(LUAROCKS_URL https://github.com/luarocks/luarocks/archive/v2.4.2.tar.gz) set(LUAROCKS_SHA256 eef88c2429c715a7beb921e4b1ba571dddb7c74a250fbb0d3cc0d4be7a5865d9) -set(UNIBILIUM_URL https://github.com/mauke/unibilium/archive/v1.2.0.tar.gz) -set(UNIBILIUM_SHA256 623af1099515e673abfd3cae5f2fa808a09ca55dda1c65a7b5c9424eb304ead8) +set(UNIBILIUM_URL https://github.com/mauke/unibilium/archive/v1.2.1.tar.gz) +set(UNIBILIUM_SHA256 6045b4f6adca7b1123284007675ca71f718f70942d3a93d8b9fa5bd442006ec1) if(WIN32) set(LIBTERMKEY_URL https://github.com/equalsraf/libtermkey/archive/tb-windows.zip) -- cgit From 1b2d386a852fcdf1f7509dc29f2a26ab8e5b59ca Mon Sep 17 00:00:00 2001 From: James McCoy Date: Mon, 18 Dec 2017 21:35:40 -0500 Subject: vim-patch:8.0.0565: using freed memory in :caddbuf Problem: Using freed memory in :caddbuf after clearing quickfix list. (Dominique Pelle) Solution: Set qf_last to NULL. https://github.com/vim/vim/commit/31bdd13c335533c749993b57dcd980a87373139e --- src/nvim/quickfix.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 73ad14ab22..be998077c0 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -2411,6 +2411,7 @@ static void qf_free(qf_info_T *qi, int idx) qi->qf_lists[idx].qf_ptr = NULL; qi->qf_lists[idx].qf_title = NULL; qi->qf_lists[idx].qf_index = 0; + qi->qf_lists[idx].qf_last = NULL; qf_clean_dir_stack(&qi->qf_dir_stack); qi->qf_directory = NULL; -- cgit From dd2739286195c8953a31346908f02a85563a3fa2 Mon Sep 17 00:00:00 2001 From: James McCoy Date: Mon, 18 Dec 2017 21:37:11 -0500 Subject: vim-patch:8.0.0574: get only one quickfix list after :caddbuf Problem: Get only one quickfix list after :caddbuf. Solution: Reset qf_multiline. (Yegappan Lakshmanan) https://github.com/vim/vim/commit/99895eac1cf71be43ece7e14b50e206e041fbe9f --- src/nvim/quickfix.c | 6 +++ src/nvim/testdir/test_quickfix.vim | 89 +++++++++++++++++++++++++++++++------- 2 files changed, 80 insertions(+), 15 deletions(-) diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index be998077c0..ee3bb1dfba 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -2411,12 +2411,18 @@ static void qf_free(qf_info_T *qi, int idx) qi->qf_lists[idx].qf_ptr = NULL; qi->qf_lists[idx].qf_title = NULL; qi->qf_lists[idx].qf_index = 0; + qi->qf_lists[idx].qf_start = NULL; qi->qf_lists[idx].qf_last = NULL; + qi->qf_lists[idx].qf_ptr = NULL; + qi->qf_lists[idx].qf_nonevalid = true; qf_clean_dir_stack(&qi->qf_dir_stack); qi->qf_directory = NULL; qf_clean_dir_stack(&qi->qf_file_stack); qi->qf_currfile = NULL; + qi->qf_multiline = false; + qi->qf_multiignore = false; + qi->qf_multiscan = false; } /* diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim index 40ad387dee..e729290c1e 100644 --- a/src/nvim/testdir/test_quickfix.vim +++ b/src/nvim/testdir/test_quickfix.vim @@ -24,8 +24,8 @@ func s:setup_commands(cchar) command! -nargs=* Xgetbuffer cgetbuffer command! -nargs=* Xaddbuffer caddbuffer command! -nargs=* Xrewind crewind - command! -nargs=* -bang Xnext cnext - command! -nargs=* -bang Xprev cprev + command! -count -nargs=* -bang Xnext cnext + command! -count -nargs=* -bang Xprev cprev command! -nargs=* -bang Xfirst cfirst command! -nargs=* -bang Xlast clast command! -nargs=* -bang Xnfile cnfile @@ -56,8 +56,8 @@ func s:setup_commands(cchar) command! -nargs=* Xgetbuffer lgetbuffer command! -nargs=* Xaddbuffer laddbuffer command! -nargs=* Xrewind lrewind - command! -nargs=* -bang Xnext lnext - command! -nargs=* -bang Xprev lprev + command! -count -nargs=* -bang Xnext lnext + command! -count -nargs=* -bang Xprev lprev command! -nargs=* -bang Xfirst lfirst command! -nargs=* -bang Xlast llast command! -nargs=* -bang Xnfile lnfile @@ -395,7 +395,9 @@ func Xtest_browse(cchar) Xgetexpr ['Xqftestfile1:5:Line5', \ 'Xqftestfile1:6:Line6', \ 'Xqftestfile2:10:Line10', - \ 'Xqftestfile2:11:Line11'] + \ 'Xqftestfile2:11:Line11', + \ 'RegularLine1', + \ 'RegularLine2'] Xfirst call assert_fails('Xprev', 'E553') @@ -407,6 +409,7 @@ func Xtest_browse(cchar) call assert_equal('Xqftestfile1', bufname('%')) call assert_equal(6, line('.')) Xlast + Xprev call assert_equal('Xqftestfile2', bufname('%')) call assert_equal(11, line('.')) call assert_fails('Xnext', 'E553') @@ -415,6 +418,13 @@ func Xtest_browse(cchar) call assert_equal('Xqftestfile1', bufname('%')) call assert_equal(5, line('.')) + 10Xnext + call assert_equal('Xqftestfile2', bufname('%')) + call assert_equal(11, line('.')) + 10Xprev + call assert_equal('Xqftestfile1', bufname('%')) + call assert_equal(5, line('.')) + Xexpr "" call assert_fails('Xnext', 'E42:') @@ -437,9 +447,30 @@ func s:test_xhelpgrep(cchar) let title_text = ':lhelpgrep quickfix' endif call assert_true(w:quickfix_title =~ title_text, w:quickfix_title) + + " Jumping to a help topic should open the help window + only + Xnext + call assert_true(&buftype == 'help') + call assert_true(winnr('$') == 2) + " Jumping to the next match should reuse the help window + Xnext + call assert_true(&buftype == 'help') + call assert_true(winnr() == 1) + call assert_true(winnr('$') == 2) + " Jumping to the next match from the quickfix window should reuse the help + " window + Xopen + Xnext + call assert_true(&buftype == 'help') + call assert_true(winnr() == 1) + call assert_true(winnr('$') == 2) + " This wipes out the buffer, make sure that doesn't cause trouble. Xclose + new | only + " Search for non existing help string call assert_fails('Xhelpgrep a1b2c3', 'E480:') endfunc @@ -578,10 +609,7 @@ func Test_locationlist() lrewind enew lopen - lnext - lnext - lnext - lnext + 4lnext vert split wincmd L lopen @@ -1039,6 +1067,25 @@ func Test_efm2() call assert_equal(1, l[4].valid) call assert_equal('unittests/dbfacadeTest.py', bufname(l[4].bufnr)) + " The following sequence of commands used to crash Vim + set efm=%W%m + cgetexpr ['msg1'] + let l = getqflist() + call assert_equal(1, len(l), string(l)) + call assert_equal('msg1', l[0].text) + set efm=%C%m + lexpr 'msg2' + let l = getloclist(0) + call assert_equal(1, len(l), string(l)) + call assert_equal('msg2', l[0].text) + lopen + call setqflist([], 'r') + caddbuf + let l = getqflist() + call assert_equal(1, len(l), string(l)) + call assert_equal('|| msg2', l[0].text) + + new | only let &efm = save_efm endfunc @@ -1369,18 +1416,18 @@ func Test_switchbuf() let winid = win_getid() cfirst | cnext call assert_equal(winid, win_getid()) - cnext | cnext + 2cnext call assert_equal(winid, win_getid()) - cnext | cnext + 2cnext call assert_equal(winid, win_getid()) enew set switchbuf=useopen cfirst | cnext call assert_equal(file1_winid, win_getid()) - cnext | cnext + 2cnext call assert_equal(file2_winid, win_getid()) - cnext | cnext + 2cnext call assert_equal(file2_winid, win_getid()) enew | only @@ -1390,9 +1437,9 @@ func Test_switchbuf() tabfirst cfirst | cnext call assert_equal(2, tabpagenr()) - cnext | cnext + 2cnext call assert_equal(3, tabpagenr()) - cnext | cnext + 2cnext call assert_equal(3, tabpagenr()) tabfirst | tabonly | enew @@ -1957,3 +2004,15 @@ func Test_qf_free() call XfreeTests('c') call XfreeTests('l') endfunc + +func Test_no_reuse_mem() + set efm=E,%W%m, + cgetexpr ['C'] + set efm=%C%m + lexpr '0' + lopen + call setqflist([], 'r') + caddbuf + + set efm& +endfunc -- cgit From 9fb7926a0dbc08daf19fca5eb93f95ae83a6303c Mon Sep 17 00:00:00 2001 From: James McCoy Date: Mon, 18 Dec 2017 21:40:08 -0500 Subject: vim-patch:8.0.0579: duplicate test case for quickfix Problem: Duplicate test case for quickfix. Solution: Remove the function. (Yegappan Lakshmanan) https://github.com/vim/vim/commit/9b77016545d5ef1a1f4a90c9bb4b7a6693af8918 --- src/nvim/testdir/test_quickfix.vim | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim index e729290c1e..7e4100cca0 100644 --- a/src/nvim/testdir/test_quickfix.vim +++ b/src/nvim/testdir/test_quickfix.vim @@ -2004,15 +2004,3 @@ func Test_qf_free() call XfreeTests('c') call XfreeTests('l') endfunc - -func Test_no_reuse_mem() - set efm=E,%W%m, - cgetexpr ['C'] - set efm=%C%m - lexpr '0' - lopen - call setqflist([], 'r') - caddbuf - - set efm& -endfunc -- cgit From 4d2d844c12e1a94a5d3cbe93794bb04ef6fb8377 Mon Sep 17 00:00:00 2001 From: James McCoy Date: Mon, 18 Dec 2017 21:40:34 -0500 Subject: vim-patch:8.0.0580: cannot set the valid flag with setqflist() Problem: Cannot set the valid flag with setqflist(). Solution: Add the "valid" argument. (Yegappan Lakshmanan, closes vim/vim#1642) https://github.com/vim/vim/commit/f1d21c8cc83f40c815b6bf13cd2043152db533ee --- runtime/doc/eval.txt | 3 +++ src/nvim/quickfix.c | 5 +++++ src/nvim/testdir/test_quickfix.vim | 19 +++++++++++++++++++ 3 files changed, 27 insertions(+) diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index efc8775d0a..0b50340dee 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -6841,6 +6841,7 @@ setqflist({list} [, {action}[, {what}]]) *setqflist()* nr error number text description of the error type single-character error type, 'E', 'W', etc. + valid recognized error message The "col", "vcol", "nr", "type" and "text" entries are optional. Either "lnum" or "pattern" entry can be used to @@ -6850,6 +6851,8 @@ setqflist({list} [, {action}[, {what}]]) *setqflist()* item will not be handled as an error line. If both "pattern" and "lnum" are present then "pattern" will be used. + If the "valid" entry is not supplied, then the valid flag is + set when "bufnr" is a valid buffer or "filename" exists. If you supply an empty {list}, the quickfix list will be cleared. Note that the list is not exactly the same as what diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index ee3bb1dfba..d77cec4fd8 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -4189,6 +4189,11 @@ static int qf_add_entries(qf_info_T *qi, list_T *list, char_u *title, bufnum = 0; } + // If the 'valid' field is present it overrules the detected value. + if (tv_dict_find(d, "valid", -1) != NULL) { + valid = (int)tv_dict_get_number(d, "valid"); + } + int status = qf_add_entry(qi, NULL, // dir (char_u *)filename, diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim index 7e4100cca0..dd64e37ce3 100644 --- a/src/nvim/testdir/test_quickfix.vim +++ b/src/nvim/testdir/test_quickfix.vim @@ -1183,6 +1183,25 @@ func SetXlistTests(cchar, bnum) let l = g:Xgetlist() call assert_equal(0, len(l)) + " Tests for setting the 'valid' flag + call g:Xsetlist([{'bufnr':a:bnum, 'lnum':4, 'valid':0}]) + Xwindow + call assert_equal(1, winnr('$')) + let l = g:Xgetlist() + call g:Xsetlist(l) + call assert_equal(0, g:Xgetlist()[0].valid) + call g:Xsetlist([{'text':'Text1', 'valid':1}]) + Xwindow + call assert_equal(2, winnr('$')) + Xclose + let save_efm = &efm + set efm=%m + Xgetexpr 'TestMessage' + let l = g:Xgetlist() + call g:Xsetlist(l) + call assert_equal(1, g:Xgetlist()[0].valid) + let &efm = save_efm + " Error cases: " Refer to a non-existing buffer and pass a non-dictionary type call assert_fails("call g:Xsetlist([{'bufnr':998, 'lnum':4}," . -- cgit From 20708a07bfc1de2c56657123c41bb52919a728d8 Mon Sep 17 00:00:00 2001 From: James McCoy Date: Tue, 19 Dec 2017 08:39:09 -0500 Subject: vim-patch:8.0.0590: cannot add a context to locations Problem: Cannot add a context to locations. Solution: Add the "context" entry in location entries. (Yegappan Lakshmanan, closes vim/vim#1012) https://github.com/vim/vim/commit/8f77c5a4ec756f3f866bd6b18feb6fca6f2a2e91 --- src/nvim/eval.c | 2 ++ src/nvim/quickfix.c | 71 ++++++++++++++++++++++++++++++++++++++ src/nvim/testdir/test_quickfix.vim | 32 +++++++++++++++++ 3 files changed, 105 insertions(+) diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 1461ddb8f9..2355c8c813 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -5173,6 +5173,8 @@ bool garbage_collect(bool testing) ABORTING(set_ref_list)(sub.additional_elements, copyID); } + ABORTING(set_ref_in_quickfix)(copyID); + bool did_free = false; if (!abort) { // 2. Free lists and dictionaries that are not referenced. diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index d77cec4fd8..e7d11df833 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -85,6 +85,7 @@ typedef struct qf_list_S { int qf_nonevalid; // TRUE if not a single valid entry found char_u *qf_title; // title derived from the command that created // the error list + typval_T *qf_ctx; // context set by setqflist/setloclist } qf_list_T; struct qf_info_S { @@ -1409,6 +1410,13 @@ void copy_loclist(win_T *from, win_T *to) else to_qfl->qf_title = NULL; + if (from_qfl->qf_ctx != NULL) { + to_qfl->qf_ctx = xcalloc(1, sizeof(typval_T)); + tv_copy(from_qfl->qf_ctx, to_qfl->qf_ctx); + } else { + to_qfl->qf_ctx = NULL; + } + if (from_qfl->qf_count) { qfline_T *from_qfp; qfline_T *prevp; @@ -2410,6 +2418,8 @@ static void qf_free(qf_info_T *qi, int idx) qi->qf_lists[idx].qf_start = NULL; qi->qf_lists[idx].qf_ptr = NULL; qi->qf_lists[idx].qf_title = NULL; + tv_free(qi->qf_lists[idx].qf_ctx); + qi->qf_lists[idx].qf_ctx = NULL; qi->qf_lists[idx].qf_index = 0; qi->qf_lists[idx].qf_start = NULL; qi->qf_lists[idx].qf_last = NULL; @@ -4060,6 +4070,7 @@ enum { QF_GETLIST_ITEMS = 0x2, QF_GETLIST_NR = 0x4, QF_GETLIST_WINID = 0x8, + QF_GETLIST_CONTEXT = 0x10, QF_GETLIST_ALL = 0xFF }; @@ -4110,6 +4121,10 @@ int get_errorlist_properties(win_T *wp, dict_T *what, dict_T *retdict) flags |= QF_GETLIST_WINID; } + if (tv_dict_find(what, S_LEN("context")) != NULL) { + flags |= QF_GETLIST_CONTEXT; + } + if (flags & QF_GETLIST_TITLE) { char_u *t = qi->qf_lists[qf_idx].qf_title; if (t == NULL) { @@ -4127,6 +4142,18 @@ int get_errorlist_properties(win_T *wp, dict_T *what, dict_T *retdict) } } + if ((status == OK) && (flags & QF_GETLIST_CONTEXT)) { + if (qi->qf_lists[qf_idx].qf_ctx != NULL) { + di = tv_dict_item_alloc_len(S_LEN("context")); + if (di != NULL) { + tv_copy(qi->qf_lists[qf_idx].qf_ctx, &di->di_tv); + tv_dict_add(retdict, di); + } + } else { + status = tv_dict_add_str(retdict, S_LEN("context"), ""); + } + } + return status; } @@ -4276,6 +4303,14 @@ static int qf_set_properties(qf_info_T *qi, dict_T *what, int action) } } + if ((di = tv_dict_find(what, S_LEN("context"))) != NULL) { + tv_free(qi->qf_lists[qi->qf_curlist].qf_ctx); + + typval_T *ctx = xcalloc(1, sizeof(typval_T)); + tv_copy(&di->di_tv, ctx); + qi->qf_lists[qi->qf_curlist].qf_ctx = ctx; + } + return retval; } @@ -4362,6 +4397,42 @@ int set_errorlist(win_T *wp, list_T *list, int action, char_u *title, return retval; } +static bool mark_quickfix_ctx(qf_info_T *qi, int copyID) +{ + bool abort = false; + + for (int i = 0; i < LISTCOUNT && !abort; i++) { + typval_T *ctx = qi->qf_lists[i].qf_ctx; + if (ctx != NULL && ctx->v_type != VAR_NUMBER + && ctx->v_type != VAR_STRING && ctx->v_type != VAR_FLOAT) { + abort = set_ref_in_item(ctx, copyID, NULL, NULL); + } + } + + return abort; +} + +/// Mark the context of the quickfix list and the location lists (if present) as +/// "in use". So that garabage collection doesn't free the context. +bool set_ref_in_quickfix(int copyID) +{ + bool abort = mark_quickfix_ctx(&ql_info, copyID); + if (abort) { + return abort; + } + + FOR_ALL_TAB_WINDOWS(tp, win) { + if (win->w_llist != NULL) { + abort = mark_quickfix_ctx(win->w_llist, copyID); + if (abort) { + return abort; + } + } + } + + return abort; +} + /* * ":[range]cbuffer [bufnr]" command. * ":[range]caddbuffer [bufnr]" command. diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim index dd64e37ce3..1d17da31ed 100644 --- a/src/nvim/testdir/test_quickfix.vim +++ b/src/nvim/testdir/test_quickfix.vim @@ -1754,6 +1754,38 @@ func Xproperty_tests(cchar) if a:cchar == 'l' call assert_equal({}, getloclist(99, {'title': 1})) endif + + " Context related tests + call g:Xsetlist([], 'a', {'context':[1,2,3]}) + call test_garbagecollect_now() + let d = g:Xgetlist({'context':1}) + call assert_equal([1,2,3], d.context) + call g:Xsetlist([], 'a', {'context':{'color':'green'}}) + let d = g:Xgetlist({'context':1}) + call assert_equal({'color':'green'}, d.context) + call g:Xsetlist([], 'a', {'context':"Context info"}) + let d = g:Xgetlist({'context':1}) + call assert_equal("Context info", d.context) + call g:Xsetlist([], 'a', {'context':246}) + let d = g:Xgetlist({'context':1}) + call assert_equal(246, d.context) + if a:cchar == 'l' + " Test for copying context across two different location lists + new | only + let w1_id = win_getid() + let l = [1] + call setloclist(0, [], 'a', {'context':l}) + new + let w2_id = win_getid() + call add(l, 2) + call assert_equal([1, 2], getloclist(w1_id, {'context':1}).context) + call assert_equal([1, 2], getloclist(w2_id, {'context':1}).context) + unlet! l + call assert_equal([1, 2], getloclist(w2_id, {'context':1}).context) + only + call setloclist(0, [], 'f') + call assert_equal({}, getloclist(0, {'context':1})) + endif endfunc func Test_qf_property() -- cgit From 6fcadab3ce16ff29be53176f8658e61819691df1 Mon Sep 17 00:00:00 2001 From: James McCoy Date: Tue, 19 Dec 2017 10:41:50 -0500 Subject: vim-patch:8.0.0595: Coverity warning for not checking return value Problem: Coverity warning for not checking return value of dict_add(). Solution: Check the return value for FAIL. https://github.com/vim/vim/commit/beb9cb19c660484488a71a25eda46ab0fa579278 --- src/nvim/quickfix.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index e7d11df833..df735e32a9 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -4147,7 +4147,9 @@ int get_errorlist_properties(win_T *wp, dict_T *what, dict_T *retdict) di = tv_dict_item_alloc_len(S_LEN("context")); if (di != NULL) { tv_copy(qi->qf_lists[qf_idx].qf_ctx, &di->di_tv); - tv_dict_add(retdict, di); + if (tv_dict_add(retdict, di) == FAIL) { + tv_dict_item_free(di); + } } } else { status = tv_dict_add_str(retdict, S_LEN("context"), ""); -- cgit From cdd86f42cf93135d1c4dddcc2ba13b9798034350 Mon Sep 17 00:00:00 2001 From: James McCoy Date: Tue, 19 Dec 2017 10:48:31 -0500 Subject: vim-patch:8.0.0597: off-by-one error in size computation Problem: Off-by-one error in buffer size computation. Solution: Use ">=" instead of ">". (Lemonboy, closes vim/vim#1694) https://github.com/vim/vim/commit/253f9128779f315ea670f9b4a17446b7b4c74927 --- src/nvim/quickfix.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index df735e32a9..3608a41a6e 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -801,7 +801,7 @@ restofline: fields->type = *regmatch.startp[i]; } if (fmt_ptr->flags == '+' && !qi->qf_multiscan) { // %+ - if (linelen > fields->errmsglen) { + if (linelen >= fields->errmsglen) { // linelen + null terminator fields->errmsg = xrealloc(fields->errmsg, linelen + 1); fields->errmsglen = linelen + 1; @@ -812,7 +812,7 @@ restofline: continue; } len = (size_t)(regmatch.endp[i] - regmatch.startp[i]); - if (len > fields->errmsglen) { + if (len >= fields->errmsglen) { // len + null terminator fields->errmsg = xrealloc(fields->errmsg, len + 1); fields->errmsglen = len + 1; @@ -889,7 +889,7 @@ restofline: fields->namebuf[0] = NUL; // no match found, remove file name fields->lnum = 0; // don't jump to this line fields->valid = false; - if (linelen > fields->errmsglen) { + if (linelen >= fields->errmsglen) { // linelen + null terminator fields->errmsg = xrealloc(fields->errmsg, linelen + 1); fields->errmsglen = linelen + 1; -- cgit From 190814bdae496c0ce4dfb524f9bb5ee7a09b4f60 Mon Sep 17 00:00:00 2001 From: James McCoy Date: Tue, 19 Dec 2017 10:53:59 -0500 Subject: vim-patch:8.0.0606: cannot set the context for a specified quickfix list Problem: Cannot set the context for a specified quickfix list. Solution: Use the list index instead of the current list. (Yegappan Lakshmanan) https://github.com/vim/vim/commit/6e62da3e14d32f76f60d5cc8b267059923842f17 --- src/nvim/quickfix.c | 9 +++++--- src/nvim/testdir/test_quickfix.vim | 47 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 3 deletions(-) diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 3608a41a6e..c5d03e73f1 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -4278,7 +4278,10 @@ static int qf_set_properties(qf_info_T *qi, dict_T *what, int action) if ((di = tv_dict_find(what, S_LEN("nr"))) != NULL) { // Use the specified quickfix/location list if (di->di_tv.v_type == VAR_NUMBER) { - qf_idx = (int)di->di_tv.vval.v_number - 1; + // for zero use the current list + if (di->di_tv.vval.v_number != 0) { + qf_idx = (int)di->di_tv.vval.v_number - 1; + } if (qf_idx < 0 || qf_idx >= qi->qf_listcount) { return FAIL; } @@ -4306,11 +4309,11 @@ static int qf_set_properties(qf_info_T *qi, dict_T *what, int action) } if ((di = tv_dict_find(what, S_LEN("context"))) != NULL) { - tv_free(qi->qf_lists[qi->qf_curlist].qf_ctx); + tv_free(qi->qf_lists[qf_idx].qf_ctx); typval_T *ctx = xcalloc(1, sizeof(typval_T)); tv_copy(&di->di_tv, ctx); - qi->qf_lists[qi->qf_curlist].qf_ctx = ctx; + qi->qf_lists[qf_idx].qf_ctx = ctx; } return retval; diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim index 1d17da31ed..30023dddc9 100644 --- a/src/nvim/testdir/test_quickfix.vim +++ b/src/nvim/testdir/test_quickfix.vim @@ -1786,6 +1786,37 @@ func Xproperty_tests(cchar) call setloclist(0, [], 'f') call assert_equal({}, getloclist(0, {'context':1})) endif + + " Test for changing the context of previous quickfix lists + call g:Xsetlist([], 'f') + Xexpr "One" + Xexpr "Two" + Xexpr "Three" + call g:Xsetlist([], ' ', {'context' : [1], 'nr' : 1}) + call g:Xsetlist([], ' ', {'context' : [2], 'nr' : 2}) + " Also, check for setting the context using quickfix list number zero. + call g:Xsetlist([], ' ', {'context' : [3], 'nr' : 0}) + call test_garbagecollect_now() + let l = g:Xgetlist({'nr' : 1, 'context' : 1}) + call assert_equal([1], l.context) + let l = g:Xgetlist({'nr' : 2, 'context' : 1}) + call assert_equal([2], l.context) + let l = g:Xgetlist({'nr' : 3, 'context' : 1}) + call assert_equal([3], l.context) + + " Test for changing the context through reference and for garbage + " collection of quickfix context + let l = ["red"] + call g:Xsetlist([], ' ', {'context' : l}) + call add(l, "blue") + let x = g:Xgetlist({'context' : 1}) + call add(x.context, "green") + call assert_equal(["red", "blue", "green"], l) + call assert_equal(["red", "blue", "green"], x.context) + unlet l + call test_garbagecollect_now() + let m = g:Xgetlist({'context' : 1}) + call assert_equal(["red", "blue", "green"], m.context) endfunc func Test_qf_property() @@ -2055,3 +2086,19 @@ func Test_qf_free() call XfreeTests('c') call XfreeTests('l') endfunc + +" Test for buffer overflow when parsing lines and adding new entries to +" the quickfix list. +func Test_bufoverflow() + set efm=%f:%l:%m + cgetexpr ['File1:100:' . repeat('x', 1025)] + + set efm=%+GCompiler:\ %.%#,%f:%l:%m + cgetexpr ['Compiler: ' . repeat('a', 1015), 'File1:10:Hello World'] + + set efm=%DEntering\ directory\ %f,%f:%l:%m + cgetexpr ['Entering directory ' . repeat('a', 1006), + \ 'File1:10:Hello World'] + set efm&vim +endfunc + -- cgit From 6c731d33f6729a272c5168d040ee4b86942c599b Mon Sep 17 00:00:00 2001 From: ckelsel Date: Thu, 21 Dec 2017 18:31:26 +0800 Subject: vim-patch:8.0.0314: getcmd*() functions are not tested Problem: getcmdtype(), getcmdpos() and getcmdline() are not tested. Solution: Add tests. (Yegappan Lakshmanan) https://github.com/vim/vim/commit/65189a1294307abf007faab7385dc0145ba72b06 --- src/nvim/testdir/test_cmdline.vim | 32 ++++++++++++++++++++++++++++++++ src/nvim/version.c | 2 +- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/nvim/testdir/test_cmdline.vim b/src/nvim/testdir/test_cmdline.vim index ac44e09a5a..dc9790a39c 100644 --- a/src/nvim/testdir/test_cmdline.vim +++ b/src/nvim/testdir/test_cmdline.vim @@ -330,4 +330,36 @@ func Test_cmdline_search_range() bwipe! endfunc +" Tests for getcmdline(), getcmdpos() and getcmdtype() +func Check_cmdline(cmdtype) + call assert_equal('MyCmd a', getcmdline()) + call assert_equal(8, getcmdpos()) + call assert_equal(a:cmdtype, getcmdtype()) + return '' +endfunc + +func Test_getcmdtype() + call feedkeys(":MyCmd a\=Check_cmdline(':')\\", "xt") + + let cmdtype = '' + debuggreedy + call feedkeys(":debug echo 'test'\", "t") + call feedkeys("let cmdtype = \=string(getcmdtype())\\", "t") + call feedkeys("cont\", "xt") + 0debuggreedy + call assert_equal('>', cmdtype) + + call feedkeys("/MyCmd a\=Check_cmdline('/')\\", "xt") + call feedkeys("?MyCmd a\=Check_cmdline('?')\\", "xt") + + call feedkeys(":call input('Answer?')\", "t") + call feedkeys("MyCmd a\=Check_cmdline('@')\\", "xt") + + call feedkeys(":insert\MyCmd a\=Check_cmdline('-')\\", "xt") + + cnoremap Check_cmdline('=') + call feedkeys("a\=MyCmd a\\\", "xt") + cunmap +endfunc + set cpo& diff --git a/src/nvim/version.c b/src/nvim/version.c index 8ab9fc1a4b..26e15e3b15 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -942,7 +942,7 @@ static const int included_patches[] = { // 317, // 316, // 315, - // 314, + 314, // 313, // 312, 311, -- cgit From eb95b88156aa7ee022db4cf5d48656c1b5fd5a06 Mon Sep 17 00:00:00 2001 From: ckelsel Date: Thu, 21 Dec 2017 18:40:40 +0800 Subject: vim-patch:8.0.0315: :help :[range] does not work Problem: ":help :[range]" does not work. (Tony Mechelynck) Solution: Translate to insert a backslash. https://github.com/vim/vim/commit/a76f59d817e2da31d83b4f0e978b52abe81e0ae9 --- src/nvim/ex_cmds.c | 6 ++++-- src/nvim/version.c | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 8616508d88..7188be5407 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -4619,7 +4619,8 @@ int find_help_tags(char_u *arg, int *num_matches, char_u ***matches, int keep_la "/\\(\\)", "/\\%(\\)", "?", ":?", "?", "g?", "g?g?", "g??", "z?", "/\\?", "/\\z(\\)", "\\=", ":s\\=", - "[count]", "[quotex]", "[range]", + "[count]", "[quotex]", + "[range]", ":[range]", "[pattern]", "\\|", "\\%$", "s/\\~", "s/\\U", "s/\\L", "s/\\1", "s/\\2", "s/\\3", "s/\\9"}; @@ -4628,7 +4629,8 @@ int find_help_tags(char_u *arg, int *num_matches, char_u ***matches, int keep_la "/\\\\(\\\\)", "/\\\\%(\\\\)", "?", ":?", "?", "g?", "g?g?", "g??", "z?", "/\\\\?", "/\\\\z(\\\\)", "\\\\=", ":s\\\\=", - "\\[count]", "\\[quotex]", "\\[range]", + "\\[count]", "\\[quotex]", + "\\[range]", ":\\[range]", "\\[pattern]", "\\\\bar", "/\\\\%\\$", "s/\\\\\\~", "s/\\\\U", "s/\\\\L", "s/\\\\1", "s/\\\\2", "s/\\\\3", "s/\\\\9"}; diff --git a/src/nvim/version.c b/src/nvim/version.c index 26e15e3b15..d511ccb0d6 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -941,7 +941,7 @@ static const int included_patches[] = { // 318, // 317, // 316, - // 315, + 315, 314, // 313, // 312, -- cgit From d2c01d529fb618889ffc25083ba14461ef22f57a Mon Sep 17 00:00:00 2001 From: ZyX Date: Sat, 23 Dec 2017 15:47:04 +0300 Subject: regexp: Fix linter errors --- src/nvim/regexp.c | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c index a1332a43dd..ddc3681867 100644 --- a/src/nvim/regexp.c +++ b/src/nvim/regexp.c @@ -232,17 +232,17 @@ #define LAST_NL NUPPER + ADD_NL #define WITH_NL(op) ((op) >= FIRST_NL && (op) <= LAST_NL) -#define MOPEN 80 /* -89 Mark this point in input as start of - * \( subexpr. MOPEN + 0 marks start of - * match. */ -#define MCLOSE 90 /* -99 Analogous to MOPEN. MCLOSE + 0 marks - * end of match. */ -#define BACKREF 100 /* -109 node Match same string again \1-\9 */ - -# define ZOPEN 110 /* -119 Mark this point in input as start of - * \z( subexpr. */ -# define ZCLOSE 120 /* -129 Analogous to ZOPEN. */ -# define ZREF 130 /* -139 node Match external submatch \z1-\z9 */ +#define MOPEN 80 // -89 Mark this point in input as start of + // \( … \) subexpr. MOPEN + 0 marks start of + // match. +#define MCLOSE 90 // -99 Analogous to MOPEN. MCLOSE + 0 marks + // end of match. +#define BACKREF 100 // -109 node Match same string again \1-\9. + +# define ZOPEN 110 // -119 Mark this point in input as start of + // \z( … \) subexpr. +# define ZCLOSE 120 // -129 Analogous to ZOPEN. +# define ZREF 130 // -139 node Match external submatch \z1-\z9 #define BRACE_COMPLEX 140 /* -149 node Match nodes between m & n times */ @@ -462,11 +462,11 @@ static int toggle_Magic(int x) #define IEMSG_RET_NULL(m) return (IEMSG(m), rc_did_emsg = true, (void *)NULL) #define EMSG_RET_FAIL(m) return (EMSG(m), rc_did_emsg = true, FAIL) #define EMSG2_RET_NULL(m, c) \ - return (EMSG2((m), (c) ? "" : "\\"), rc_did_emsg = true, (void *)NULL) + return (EMSG2((m), (c) ? "" : "\\"), rc_did_emsg = true, (void *)NULL) #define EMSG2_RET_FAIL(m, c) \ - return (EMSG2((m), (c) ? "" : "\\"), rc_did_emsg = true, FAIL) + return (EMSG2((m), (c) ? "" : "\\"), rc_did_emsg = true, FAIL) #define EMSG_ONE_RET_NULL EMSG2_RET_NULL(_( \ - "E369: invalid item in %s%%[]"), reg_magic == MAGIC_ALL) + "E369: invalid item in %s%%[]"), reg_magic == MAGIC_ALL) #define MAX_LIMIT (32767L << 16L) -- cgit From 5cb7a709e7f60b0e7bcde70a1aa9fea5f060fe0f Mon Sep 17 00:00:00 2001 From: ZyX Date: Sat, 23 Dec 2017 15:47:23 +0300 Subject: clint: Make linter report line where it found opening brace --- src/clint.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/clint.py b/src/clint.py index 8426807c80..79ab91ebe1 100755 --- a/src/clint.py +++ b/src/clint.py @@ -2124,8 +2124,10 @@ def CheckExpressionAlignment(filename, clean_lines, linenum, error, startpos=0): + (level_starts[depth][2] == '{')): if depth not in ignore_error_levels: error(filename, linenum, 'whitespace/alignment', 2, - 'Inner expression should be aligned ' - 'as opening brace + 1 (+ 2 in case of {)') + ('Inner expression should be aligned ' + 'as opening brace + 1 (+ 2 in case of {{). ' + 'Relevant opening is on line {0!r}').format( + level_starts[depth][3])) prev_line_start = pos elif brace == 'e': pass @@ -2142,7 +2144,8 @@ def CheckExpressionAlignment(filename, clean_lines, linenum, error, startpos=0): ignore_error_levels.add(depth) line_ended_with_opening = ( pos == len(line) - 2 * (line.endswith(' \\')) - 1) - level_starts[depth] = (pos, line_ended_with_opening, brace) + level_starts[depth] = (pos, line_ended_with_opening, brace, + linenum) if line_ended_with_opening: depth_line_starts[depth] = (prev_line_start, brace) else: -- cgit From 308dd53783314103710a37c9809eddb78279eab4 Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Mon, 27 Nov 2017 13:01:49 +0100 Subject: channel: check for existance before trying to set key This avoids an error message in async context, where it is not safe. --- runtime/doc/channel.txt | 8 ++++++-- src/nvim/channel.c | 23 +++++++++++++++++++---- src/nvim/globals.h | 3 +++ test/functional/core/channels_spec.lua | 17 ++++++++++++++++- 4 files changed, 44 insertions(+), 7 deletions(-) diff --git a/runtime/doc/channel.txt b/runtime/doc/channel.txt index c94c64eb84..c4f7eb1ff1 100644 --- a/runtime/doc/channel.txt +++ b/runtime/doc/channel.txt @@ -43,7 +43,7 @@ bytes. Additionally, for a job channel using rpc, bytes can still be read over its stderr. Similarily, only bytes can be written to nvim's own stderr. *channel-callback* *buffered* - *on_stdout* *on_stderr* *on_stdin* *on_data* + *E5210* *on_stdout* *on_stderr* *on_stdin* *on_data* A callback function `on_{stream}` will be invoked with data read from the channel. By default, the callback will be invoked immediately when data is available, to facilitate interactive communication. The same callback will @@ -52,7 +52,11 @@ Alternatively the `{stream}_buffered` option can be set to invoke the callback only when the underlying stream reaches EOF, and will then be passed in complete output. This is helpful when only the complete output is useful, and not partial data. Futhermore if `{stream}_buffered` is set but not a callback, -the data is saved in the options dict, with the stream name as key. +the data is saved in the options dict, with the stream name as key. For this +to work a new options dict must be used for each opened channel. If a script +uses a global `s:job_opts` dict, it can be copied with |copy()| before supplying +it to |jobstart()|. If a dict is reused, so that the dict key already is +occupied, error `E5210` will be raised. - The arguments passed to the callback function are: diff --git a/src/nvim/channel.c b/src/nvim/channel.c index 40af470bde..3807f2b3cd 100644 --- a/src/nvim/channel.c +++ b/src/nvim/channel.c @@ -599,6 +599,7 @@ static void on_stdio_input(Stream *stream, RBuffer *buf, size_t count, on_channel_output(stream, chan, buf, count, eof, &chan->on_stdout, "stdin"); } +/// @param type must have static lifetime static void on_channel_output(Stream *stream, Channel *chan, RBuffer *buf, size_t count, bool eof, CallbackReader *reader, const char *type) @@ -613,14 +614,20 @@ static void on_channel_output(Stream *stream, Channel *chan, RBuffer *buf, if (reader->cb.type != kCallbackNone) { process_channel_event(chan, &reader->cb, type, reader->buffer.ga_data, (size_t)reader->buffer.ga_len, 0); - ga_clear(&reader->buffer); } else if (reader->self) { - list_T *data = buffer_to_tv_list(reader->buffer.ga_data, - (size_t)reader->buffer.ga_len); - tv_dict_add_list(reader->self, type, strlen(type), data); + if (tv_dict_find(reader->self, type, -1) == NULL) { + list_T *data = buffer_to_tv_list(reader->buffer.ga_data, + (size_t)reader->buffer.ga_len); + tv_dict_add_list(reader->self, type, strlen(type), data); + } else { + // can't display error message now, defer it. + channel_incref(chan); + multiqueue_put(chan->events, on_buffered_error, 2, chan, type); + } } else { abort(); } + ga_clear(&reader->buffer); } else if (reader->cb.type != kCallbackNone) { process_channel_event(chan, &reader->cb, type, ptr, 0, 0); } @@ -641,6 +648,14 @@ static void on_channel_output(Stream *stream, Channel *chan, RBuffer *buf, } } +static void on_buffered_error(void **args) +{ + Channel *chan = (Channel *)args[0]; + const char *stream = (const char *)args[1]; + EMSG3(_(e_streamkey), stream, chan->id); + channel_decref(chan); +} + static void channel_process_exit_cb(Process *proc, int status, void *data) { Channel *chan = data; diff --git a/src/nvim/globals.h b/src/nvim/globals.h index dd216f177f..3df201b6bf 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -1063,6 +1063,9 @@ EXTERN char_u e_stdiochan2[] INIT(= N_( EXTERN char_u e_invstream[] INIT(= N_("E906: invalid stream for channel")); EXTERN char_u e_invstreamrpc[] INIT(= N_( "E906: invalid stream for rpc channel, use 'rpc'")); +EXTERN char_u e_streamkey[] INIT(= N_( + "E5210: dict key '%s' already set for buffered stream in channel %" + PRIu64)); EXTERN char_u e_libcall[] INIT(= N_("E364: Library call failed for \"%s()\"")); EXTERN char_u e_mkdir[] INIT(= N_("E739: Cannot create directory %s: %s")); EXTERN char_u e_markinval[] INIT(= N_("E19: Mark has invalid line number")); diff --git a/test/functional/core/channels_spec.lua b/test/functional/core/channels_spec.lua index 765e3c5919..e9fc88c01b 100644 --- a/test/functional/core/channels_spec.lua +++ b/test/functional/core/channels_spec.lua @@ -246,6 +246,22 @@ describe('channels', function() eq({"notification", "exit", {id, 0, {'10 PRINT "NVIM"', '20 GOTO 10', ''}}}, next_msg()) + -- if dict is reused the new value is not stored, + -- but nvim also does not crash + command("let id = jobstart(['cat'], g:job_opts)") + id = eval("g:id") + + command([[call chansend(id, "cat text\n")]]) + sleep(10) + command("call chanclose(id, 'stdin')") + + -- old value was not overwritten + eq({"notification", "exit", {id, 0, {'10 PRINT "NVIM"', + '20 GOTO 10', ''}}}, next_msg()) + + -- and an error was thrown. + eq("E5210: dict key 'stdout' already set for buffered stream in channel "..id, eval('v:errmsg')) + -- reset dictionary source([[ let g:job_opts = { @@ -261,6 +277,5 @@ describe('channels', function() -- works correctly with no output eq({"notification", "exit", {id, 1, {''}}}, next_msg()) - end) end) -- cgit From 61ba3c5e31bf5bf491046098e4d9922dd6c3c082 Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Sat, 25 Nov 2017 10:37:41 +0100 Subject: provider: delete vimL stderr collector, now that it exists builtin --- runtime/autoload/provider.vim | 20 -------------------- runtime/autoload/provider/clipboard.vim | 13 ++++++------- runtime/autoload/provider/node.vim | 9 ++++----- runtime/autoload/provider/pythonx.vim | 9 ++++----- 4 files changed, 14 insertions(+), 37 deletions(-) delete mode 100644 runtime/autoload/provider.vim diff --git a/runtime/autoload/provider.vim b/runtime/autoload/provider.vim deleted file mode 100644 index e6514f5ba8..0000000000 --- a/runtime/autoload/provider.vim +++ /dev/null @@ -1,20 +0,0 @@ -" Common functionality for providers - -let s:stderr = {} - -function! provider#stderr_collector(chan_id, data, event) - let stderr = get(s:stderr, a:chan_id, ['']) - let stderr[-1] .= a:data[0] - call extend(stderr, a:data[1:]) - let s:stderr[a:chan_id] = stderr -endfunction - -function! provider#clear_stderr(chan_id) - if has_key(s:stderr, a:chan_id) - call remove(s:stderr, a:chan_id) - endif -endfunction - -function! provider#get_stderr(chan_id) - return get(s:stderr, a:chan_id, []) -endfunction diff --git a/runtime/autoload/provider/clipboard.vim b/runtime/autoload/provider/clipboard.vim index 6454a01c2a..e5a6e4748a 100644 --- a/runtime/autoload/provider/clipboard.vim +++ b/runtime/autoload/provider/clipboard.vim @@ -7,7 +7,7 @@ let s:clipboard = {} " When caching is enabled, store the jobid of the xclip/xsel process keeping " ownership of the selection, so we know how long the cache is valid. -let s:selection = { 'owner': 0, 'data': [], 'on_stderr': function('provider#stderr_collector') } +let s:selection = { 'owner': 0, 'data': [], 'stderr_buffered': v:true } function! s:selection.on_exit(jobid, data, event) abort " At this point this nvim instance might already have launched @@ -16,12 +16,10 @@ function! s:selection.on_exit(jobid, data, event) abort let self.owner = 0 endif if a:data != 0 - let stderr = provider#get_stderr(a:jobid) echohl WarningMsg - echomsg 'clipboard: error invoking '.get(self.argv, 0, '?').': '.join(stderr) + echomsg 'clipboard: error invoking '.get(self.argv, 0, '?').': '.join(self.stderr) echohl None endif - call provider#clear_stderr(a:jobid) endfunction let s:selections = { '*': s:selection, '+': copy(s:selection) } @@ -142,12 +140,13 @@ function! s:clipboard.set(lines, regtype, reg) abort return 0 end - let selection = s:selections[a:reg] - if selection.owner > 0 + if s:selections[a:reg].owner > 0 " The previous provider instance should exit when the new one takes " ownership, but kill it to be sure we don't fill up the job table. - call jobstop(selection.owner) + call jobstop(s:selections[a:reg].owner) end + let s:selections[a:reg] = copy(s:selection) + let selection = s:selections[a:reg] let selection.data = [a:lines, a:regtype] let argv = split(s:copy[a:reg], " ") let selection.argv = argv diff --git a/runtime/autoload/provider/node.vim b/runtime/autoload/provider/node.vim index 4e737fb51c..adcc926074 100644 --- a/runtime/autoload/provider/node.vim +++ b/runtime/autoload/provider/node.vim @@ -3,7 +3,7 @@ if exists('g:loaded_node_provider') endif let g:loaded_node_provider = 1 -let s:job_opts = {'rpc': v:true, 'on_stderr': function('provider#stderr_collector')} +let s:job_opts = {'rpc': v:true, 'stderr_buffered': v:true} function! s:is_minimum_version(version, min_major, min_minor) abort if empty(a:version) @@ -73,19 +73,18 @@ function! provider#node#Require(host) abort call add(args, provider#node#Prog()) try - let channel_id = jobstart(args, s:job_opts) + let job = copy(s:job_opts) + let channel_id = jobstart(args, job) if rpcrequest(channel_id, 'poll') ==# 'ok' return channel_id endif catch echomsg v:throwpoint echomsg v:exception - for row in provider#get_stderr(channel_id) + for row in job.stderr echomsg row endfor endtry - finally - call provider#clear_stderr(channel_id) endtry throw remote#host#LoadErrorForHost(a:host.orig_name, '$NVIM_NODE_LOG_FILE') endfunction diff --git a/runtime/autoload/provider/pythonx.vim b/runtime/autoload/provider/pythonx.vim index 7285ed43ea..1c77eabe23 100644 --- a/runtime/autoload/provider/pythonx.vim +++ b/runtime/autoload/provider/pythonx.vim @@ -5,7 +5,7 @@ endif let s:loaded_pythonx_provider = 1 -let s:job_opts = {'rpc': v:true, 'on_stderr': function('provider#stderr_collector')} +let s:job_opts = {'rpc': v:true, 'stderr_buffered': v:true} function! provider#pythonx#Require(host) abort let ver = (a:host.orig_name ==# 'python') ? 2 : 3 @@ -21,18 +21,17 @@ function! provider#pythonx#Require(host) abort endfor try - let channel_id = jobstart(args, s:job_opts) + let job = copy(s:job_opts) + let channel_id = jobstart(args, job) if rpcrequest(channel_id, 'poll') ==# 'ok' return channel_id endif catch echomsg v:throwpoint echomsg v:exception - for row in provider#get_stderr(channel_id) + for row in job.stderr echomsg row endfor - finally - call provider#clear_stderr(channel_id) endtry throw remote#host#LoadErrorForHost(a:host.orig_name, \ '$NVIM_PYTHON_LOG_FILE') -- cgit From 6b45dbca0429c52cb70908497bc7a0fed9db2d8d Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 24 Dec 2017 00:29:38 +0300 Subject: mark: Make sure that jumplist item will not have zero lnum Fixes #7169 --- src/nvim/mark.c | 4 ++++ src/nvim/shada.c | 6 ++++++ test/functional/shada/helpers.lua | 26 ++++++++++++++++++-------- test/functional/shada/marks_spec.lua | 23 +++++++++++++++++++++++ 4 files changed, 51 insertions(+), 8 deletions(-) diff --git a/src/nvim/mark.c b/src/nvim/mark.c index 1ba400972c..3cd26a5bf7 100644 --- a/src/nvim/mark.c +++ b/src/nvim/mark.c @@ -171,6 +171,10 @@ void setpcmark(void) curwin->w_prev_pcmark = curwin->w_pcmark; curwin->w_pcmark = curwin->w_cursor; + if (curwin->w_pcmark.lnum == 0) { + curwin->w_pcmark.lnum = 1; + } + /* If jumplist is full: remove oldest entry */ if (++curwin->w_jumplistlen > JUMPLISTSIZE) { curwin->w_jumplistlen = JUMPLISTSIZE; diff --git a/src/nvim/shada.c b/src/nvim/shada.c index 6bf816bb74..ce9303f14d 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -2557,6 +2557,12 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, xfmark_T fm; jump_iter = mark_jumplist_iter(jump_iter, curwin, &fm); + if (fm.fmark.mark.lnum == 0) { + iemsgf("ShaDa: mark lnum zero (ji:%p, js:%p, len:%i)", + (void *)jump_iter, (void *)&curwin->w_jumplist[0], + curwin->w_jumplistlen); + continue; + } const buf_T *const buf = (fm.fmark.fnum == 0 ? NULL : buflist_findnr(fm.fmark.fnum)); diff --git a/test/functional/shada/helpers.lua b/test/functional/shada/helpers.lua index 8e2c0cc1f6..b77e59682f 100644 --- a/test/functional/shada/helpers.lua +++ b/test/functional/shada/helpers.lua @@ -6,15 +6,14 @@ local write_file, merge_args = helpers.write_file, helpers.merge_args local mpack = require('mpack') local tmpname = helpers.tmpname() -local additional_cmd = '' +local append_argv = nil -local function nvim_argv(shada_file) +local function nvim_argv(shada_file, embed) local argv = {nvim_prog, '-u', 'NONE', '-i', shada_file or tmpname, '-N', '--cmd', 'set shortmess+=I background=light noswapfile', - '--cmd', additional_cmd, - '--embed'} - if helpers.prepend_argv then - return merge_args(helpers.prepend_argv, argv) + embed or '--embed'} + if helpers.prepend_argv or append_argv then + return merge_args(helpers.prepend_argv, argv, append_argv) else return argv end @@ -26,12 +25,21 @@ local reset = function(shada_file) end local set_additional_cmd = function(s) - additional_cmd = s + append_argv = {'--cmd', s} +end + +local function add_argv(...) + if select('#', ...) == 0 then + append_argv = nil + else + append_argv = {...} + end end local clear = function() + os.execute('cp ' .. tmpname .. ' /tmp/test.shada') os.remove(tmpname) - set_additional_cmd('') + append_argv = nil end local get_shada_rw = function(fname) @@ -80,7 +88,9 @@ end return { reset=reset, set_additional_cmd=set_additional_cmd, + add_argv=add_argv, clear=clear, get_shada_rw=get_shada_rw, read_shada_file=read_shada_file, + nvim_argv=nvim_argv, } diff --git a/test/functional/shada/marks_spec.lua b/test/functional/shada/marks_spec.lua index fa760ceb5b..4cceae1aa3 100644 --- a/test/functional/shada/marks_spec.lua +++ b/test/functional/shada/marks_spec.lua @@ -9,6 +9,8 @@ local shada_helpers = require('test.functional.shada.helpers') local reset, set_additional_cmd, clear = shada_helpers.reset, shada_helpers.set_additional_cmd, shada_helpers.clear +local add_argv = shada_helpers.add_argv +local nvim_argv = shada_helpers.nvim_argv local nvim_current_line = function() return curwinmeths.get_cursor()[1] @@ -17,8 +19,10 @@ end describe('ShaDa support code', function() local testfilename = 'Xtestfile-functional-shada-marks' local testfilename_2 = 'Xtestfile-functional-shada-marks-2' + local non_existent_testfilename = testfilename .. '.nonexistent' before_each(function() reset() + os.remove(non_existent_testfilename) local fd = io.open(testfilename, 'w') fd:write('test\n') fd:write('test2\n') @@ -214,4 +218,23 @@ describe('ShaDa support code', function() nvim_command('" sync 2') eq(2, nvim_current_line()) end) + + -- -c temporary sets lnum to zero to make `+/pat` work, so calling setpcmark() + -- during -c used to add item with zero lnum to jump list. + it('does not create incorrect file for non-existent buffers when writing from -c', + function() + add_argv('--cmd', 'silent edit ' .. non_existent_testfilename, '-c', 'qall') + local argv = nvim_argv(nil, '--headless') + eq('', funcs.system(argv)) + eq(0, exc_exec('rshada')) + end) + + it('does not create incorrect file for non-existent buffers opened from -c', + function() + add_argv('-c', 'silent edit ' .. non_existent_testfilename, + '-c', 'autocmd VimEnter * qall') + local argv = nvim_argv(nil, '--headless') + eq('', funcs.system(argv)) + eq(0, exc_exec('rshada')) + end) end) -- cgit From 0c533a488fbe453ae39017a586eff7813da8ed8a Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 24 Dec 2017 01:43:42 +0300 Subject: *: Remove most calls to tv_list_item_alloc Still left calls in eval/typval.c and test/unit/eval/helpers.lua. Latter is the only reason why function did not receive `static` modifier. --- src/nvim/api/private/helpers.c | 8 ++--- src/nvim/eval.c | 75 +++++++++++++++++------------------------ src/nvim/eval/decode.c | 40 ++++++++++------------ src/nvim/eval/typval.c | 76 ++++++++++++++++++++++++------------------ src/nvim/lua/converter.c | 36 ++++++++++++++------ test/unit/eval/typval_spec.lua | 60 +++++++++++++++++++++++++++++++++ 6 files changed, 179 insertions(+), 116 deletions(-) diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 4492f8bb93..26ad7ac1a6 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -787,16 +787,14 @@ bool object_to_vim(Object obj, typval_T *tv, Error *err) for (uint32_t i = 0; i < obj.data.array.size; i++) { Object item = obj.data.array.items[i]; - listitem_T *li = tv_list_item_alloc(); + typval_T li_tv; - if (!object_to_vim(item, TV_LIST_ITEM_TV(li), err)) { - // cleanup - tv_list_item_free(li); + if (!object_to_vim(item, &li_tv, err)) { tv_list_free(list); return false; } - tv_list_append(list, li); + tv_list_append_owned_tv(list, li_tv); } tv_list_ref(list); diff --git a/src/nvim/eval.c b/src/nvim/eval.c index e6cb8cdec0..fac15770a6 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -4874,8 +4874,6 @@ void partial_unref(partial_T *pt) static int get_list_tv(char_u **arg, typval_T *rettv, int evaluate) { list_T *l = NULL; - typval_T tv; - listitem_T *item; if (evaluate) { l = tv_list_alloc(); @@ -4883,13 +4881,13 @@ static int get_list_tv(char_u **arg, typval_T *rettv, int evaluate) *arg = skipwhite(*arg + 1); while (**arg != ']' && **arg != NUL) { - if (eval1(arg, &tv, evaluate) == FAIL) /* recursive! */ + typval_T tv; + if (eval1(arg, &tv, evaluate) == FAIL) { // Recursive! goto failret; + } if (evaluate) { - item = tv_list_item_alloc(); - *TV_LIST_ITEM_TV(item) = tv; - TV_LIST_ITEM_TV(item)->v_lock = VAR_UNLOCKED; - tv_list_append(l, item); + tv.v_lock = VAR_UNLOCKED; + tv_list_append_owned_tv(l, tv); } if (**arg == ']') { @@ -11441,40 +11439,38 @@ static void dict_list(typval_T *const tv, typval_T *const rettv, tv_list_alloc_ret(rettv); TV_DICT_ITER(tv->vval.v_dict, di, { - listitem_T *const li = tv_list_item_alloc(); - tv_list_append(rettv->vval.v_list, li); + typval_T tv = { .v_lock = VAR_UNLOCKED }; switch (what) { case kDictListKeys: { - TV_LIST_ITEM_TV(li)->v_type = VAR_STRING; - TV_LIST_ITEM_TV(li)->v_lock = VAR_UNLOCKED; - TV_LIST_ITEM_TV(li)->vval.v_string = vim_strsave(di->di_key); + tv.v_type = VAR_STRING; + tv.vval.v_string = vim_strsave(di->di_key); break; } case kDictListValues: { - tv_copy(&di->di_tv, TV_LIST_ITEM_TV(li)); + tv_copy(&di->di_tv, &tv); break; } case kDictListItems: { // items() list_T *const sub_l = tv_list_alloc(); - TV_LIST_ITEM_TV(li)->v_type = VAR_LIST; - TV_LIST_ITEM_TV(li)->v_lock = VAR_UNLOCKED; - TV_LIST_ITEM_TV(li)->vval.v_list = sub_l; + tv.v_type = VAR_LIST; + tv.vval.v_list = sub_l; tv_list_ref(sub_l); - listitem_T *sub_li = tv_list_item_alloc(); - tv_list_append(sub_l, sub_li); - TV_LIST_ITEM_TV(sub_li)->v_type = VAR_STRING; - TV_LIST_ITEM_TV(sub_li)->v_lock = VAR_UNLOCKED; - TV_LIST_ITEM_TV(sub_li)->vval.v_string = vim_strsave(di->di_key); + tv_list_append_owned_tv(sub_l, (typval_T) { + .v_type = VAR_STRING, + .v_lock = VAR_UNLOCKED, + .vval.v_string = (char_u *)xstrdup((const char *)di->di_key), + }); + + tv_list_append_tv(sub_l, &di->di_tv); - sub_li = tv_list_item_alloc(); - tv_list_append(sub_l, sub_li); - tv_copy(&di->di_tv, TV_LIST_ITEM_TV(sub_li)); break; } } + + tv_list_append_owned_tv(rettv->vval.v_list, tv); }); } @@ -12762,13 +12758,12 @@ static void f_msgpackparse(typval_T *argvars, typval_T *rettv, FunPtr fptr) goto f_msgpackparse_exit; } if (result == MSGPACK_UNPACK_SUCCESS) { - listitem_T *li = tv_list_item_alloc(); - TV_LIST_ITEM_TV(li)->v_type = VAR_UNKNOWN; - tv_list_append(ret_list, li); - if (msgpack_to_vim(unpacked.data, TV_LIST_ITEM_TV(li)) == FAIL) { + typval_T tv = { .v_type = VAR_UNKNOWN }; + if (msgpack_to_vim(unpacked.data, &tv) == FAIL) { EMSG2(_(e_invarg2), "Failed to convert msgpack string"); goto f_msgpackparse_exit; } + tv_list_append_owned_tv(ret_list, tv); } if (result == MSGPACK_UNPACK_CONTINUE) { if (rlret == OK) { @@ -13030,7 +13025,6 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) p < buf + readlen || (readlen <= 0 && (prevlen > 0 || binary)); ++p) { if (*p == '\n' || readlen <= 0) { - listitem_T *li; char_u *s = NULL; size_t len = p - start; @@ -13057,11 +13051,11 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) prevlen = prevsize = 0; } - li = tv_list_item_alloc(); - TV_LIST_ITEM_TV(li)->v_type = VAR_STRING; - TV_LIST_ITEM_TV(li)->v_lock = VAR_UNLOCKED; - TV_LIST_ITEM_TV(li)->vval.v_string = s; - tv_list_append(rettv->vval.v_list, li); + tv_list_append_owned_tv(rettv->vval.v_list, (typval_T) { + .v_type = VAR_STRING, + .v_lock = VAR_UNLOCKED, + .vval.v_string = s, + }); start = p + 1; /* step over newline */ if ((++cnt >= maxline && maxline >= 0) || readlen <= 0) @@ -14310,11 +14304,7 @@ static void f_serverlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) // Copy addrs into a linked list. list_T *l = tv_list_alloc_ret(rettv); for (size_t i = 0; i < n; i++) { - listitem_T *li = tv_list_item_alloc(); - TV_LIST_ITEM_TV(li)->v_type = VAR_STRING; - TV_LIST_ITEM_TV(li)->v_lock = VAR_UNLOCKED; - TV_LIST_ITEM_TV(li)->vval.v_string = (char_u *)addrs[i]; - tv_list_append(l, li); + tv_list_append_allocated_string(l, addrs[i]); } xfree(addrs); } @@ -15619,12 +15609,7 @@ static void f_spellsuggest(typval_T *argvars, typval_T *rettv, FunPtr fptr) for (int i = 0; i < ga.ga_len; i++) { char *p = ((char **)ga.ga_data)[i]; - - listitem_T *const li = tv_list_item_alloc(); - TV_LIST_ITEM_TV(li)->v_type = VAR_STRING; - TV_LIST_ITEM_TV(li)->v_lock = VAR_LOCKED; - TV_LIST_ITEM_TV(li)->vval.v_string = (char_u *)p; - tv_list_append(rettv->vval.v_list, li); + tv_list_append_allocated_string(rettv->vval.v_list, p); } ga_clear(&ga); } diff --git a/src/nvim/eval/decode.c b/src/nvim/eval/decode.c index d5c65ebe81..af4e055d23 100644 --- a/src/nvim/eval/decode.c +++ b/src/nvim/eval/decode.c @@ -127,9 +127,7 @@ static inline int json_decoder_pop(ValuesStackItem obj, return FAIL; } assert(last_container.special_val == NULL); - listitem_T *obj_li = tv_list_item_alloc(); - *TV_LIST_ITEM_TV(obj_li) = obj.val; - tv_list_append(last_container.container.vval.v_list, obj_li); + tv_list_append_owned_tv(last_container.container.vval.v_list, obj.val); } else if (last_container.stack_index == kv_size(*stack) - 2) { if (!obj.didcolon) { EMSG2(_("E474: Expected colon before dictionary value: %s"), @@ -154,12 +152,8 @@ static inline int json_decoder_pop(ValuesStackItem obj, } else { list_T *const kv_pair = tv_list_alloc(); tv_list_append_list(last_container.special_val, kv_pair); - listitem_T *const key_li = tv_list_item_alloc(); - *TV_LIST_ITEM_TV(key_li) = key.val; - tv_list_append(kv_pair, key_li); - listitem_T *const val_li = tv_list_item_alloc(); - *TV_LIST_ITEM_TV(val_li) = obj.val; - tv_list_append(kv_pair, val_li); + tv_list_append_owned_tv(kv_pair, key.val); + tv_list_append_owned_tv(kv_pair, obj.val); } } else { // Object with key only @@ -1047,10 +1041,10 @@ int msgpack_to_vim(const msgpack_object mobj, typval_T *const rettv) .vval = { .v_list = list }, }; for (size_t i = 0; i < mobj.via.array.size; i++) { - listitem_T *const li = tv_list_item_alloc(); - TV_LIST_ITEM_TV(li)->v_type = VAR_UNKNOWN; - tv_list_append(list, li); - if (msgpack_to_vim(mobj.via.array.ptr[i], TV_LIST_ITEM_TV(li)) + // Not populated yet, need to create list item to push. + tv_list_append_owned_tv(list, (typval_T) { .v_type = VAR_UNKNOWN }); + if (msgpack_to_vim(mobj.via.array.ptr[i], + TV_LIST_ITEM_TV(tv_list_last(list))) == FAIL) { return FAIL; } @@ -1095,20 +1089,20 @@ msgpack_to_vim_generic_map: {} for (size_t i = 0; i < mobj.via.map.size; i++) { list_T *const kv_pair = tv_list_alloc(); tv_list_append_list(list, kv_pair); - listitem_T *const key_li = tv_list_item_alloc(); - TV_LIST_ITEM_TV(key_li)->v_type = VAR_UNKNOWN; - tv_list_append(kv_pair, key_li); - listitem_T *const val_li = tv_list_item_alloc(); - TV_LIST_ITEM_TV(val_li)->v_type = VAR_UNKNOWN; - tv_list_append(kv_pair, val_li); - if (msgpack_to_vim(mobj.via.map.ptr[i].key, TV_LIST_ITEM_TV(key_li)) - == FAIL) { + + typval_T key_tv = { .v_type = VAR_UNKNOWN }; + if (msgpack_to_vim(mobj.via.map.ptr[i].key, &key_tv) == FAIL) { + tv_clear(&key_tv); return FAIL; } - if (msgpack_to_vim(mobj.via.map.ptr[i].val, TV_LIST_ITEM_TV(val_li)) - == FAIL) { + tv_list_append_owned_tv(kv_pair, key_tv); + + typval_T val_tv = { .v_type = VAR_UNKNOWN }; + if (msgpack_to_vim(mobj.via.map.ptr[i].val, &val_tv) == FAIL) { + tv_clear(&val_tv); return FAIL; } + tv_list_append_owned_tv(kv_pair, val_tv); } break; } diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 53c56d0ffd..0755507f91 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -393,19 +393,30 @@ void tv_list_append_tv(list_T *const l, typval_T *const tv) tv_list_append(l, li); } +/// Like tv_list_append_tv(), but tv is moved to a list +/// +/// This means that it is no longer valid to use contents of the typval_T after +/// function exits. +void tv_list_append_owned_tv(list_T *const l, typval_T tv) + FUNC_ATTR_NONNULL_ALL +{ + listitem_T *const li = tv_list_item_alloc(); + *TV_LIST_ITEM_TV(li) = tv; + tv_list_append(l, li); +} + /// Append a list to a list as one item /// /// @param[out] l List to append to. /// @param[in,out] itemlist List to append. Reference count is increased. -void tv_list_append_list(list_T *const list, list_T *const itemlist) +void tv_list_append_list(list_T *const l, list_T *const itemlist) FUNC_ATTR_NONNULL_ARG(1) { - listitem_T *const li = tv_list_item_alloc(); - - TV_LIST_ITEM_TV(li)->v_type = VAR_LIST; - TV_LIST_ITEM_TV(li)->v_lock = VAR_UNLOCKED; - TV_LIST_ITEM_TV(li)->vval.v_list = itemlist; - tv_list_append(list, li); + tv_list_append_owned_tv(l, (typval_T) { + .v_type = VAR_LIST, + .v_lock = VAR_UNLOCKED, + .vval.v_list = itemlist, + }); tv_list_ref(itemlist); } @@ -413,15 +424,14 @@ void tv_list_append_list(list_T *const list, list_T *const itemlist) /// /// @param[out] l List to append to. /// @param[in,out] dict Dictionary to append. Reference count is increased. -void tv_list_append_dict(list_T *const list, dict_T *const dict) +void tv_list_append_dict(list_T *const l, dict_T *const dict) FUNC_ATTR_NONNULL_ARG(1) { - listitem_T *const li = tv_list_item_alloc(); - - TV_LIST_ITEM_TV(li)->v_type = VAR_DICT; - TV_LIST_ITEM_TV(li)->v_lock = VAR_UNLOCKED; - TV_LIST_ITEM_TV(li)->vval.v_dict = dict; - tv_list_append(list, li); + tv_list_append_owned_tv(l, (typval_T) { + .v_type = VAR_DICT, + .v_lock = VAR_UNLOCKED, + .vval.v_dict = dict, + }); if (dict != NULL) { dict->dv_refcount++; } @@ -438,14 +448,15 @@ void tv_list_append_string(list_T *const l, const char *const str, const ssize_t len) FUNC_ATTR_NONNULL_ARG(1) { - if (str == NULL) { - assert(len == 0 || len == -1); - tv_list_append_allocated_string(l, NULL); - } else { - tv_list_append_allocated_string(l, (len >= 0 - ? xmemdupz(str, (size_t)len) - : xstrdup(str))); - } + tv_list_append_owned_tv(l, (typval_T) { + .v_type = VAR_STRING, + .v_lock = VAR_UNLOCKED, + .vval.v_string = (str == NULL + ? NULL + : (len >= 0 + ? xmemdupz(str, (size_t)len) + : xstrdup(str))), + }); } /// Append given string to the list @@ -457,12 +468,11 @@ void tv_list_append_string(list_T *const l, const char *const str, void tv_list_append_allocated_string(list_T *const l, char *const str) FUNC_ATTR_NONNULL_ARG(1) { - listitem_T *const li = tv_list_item_alloc(); - - tv_list_append(l, li); - TV_LIST_ITEM_TV(li)->v_type = VAR_STRING; - TV_LIST_ITEM_TV(li)->v_lock = VAR_UNLOCKED; - TV_LIST_ITEM_TV(li)->vval.v_string = (char_u *)str; + tv_list_append_owned_tv(l, (typval_T) { + .v_type = VAR_STRING, + .v_lock = VAR_UNLOCKED, + .vval.v_string = (char_u *)str, + }); } /// Append number to the list @@ -472,11 +482,11 @@ void tv_list_append_allocated_string(list_T *const l, char *const str) /// listitem_T. void tv_list_append_number(list_T *const l, const varnumber_T n) { - listitem_T *const li = tv_list_item_alloc(); - TV_LIST_ITEM_TV(li)->v_type = VAR_NUMBER; - TV_LIST_ITEM_TV(li)->v_lock = VAR_UNLOCKED; - TV_LIST_ITEM_TV(li)->vval.v_number = n; - tv_list_append(l, li); + tv_list_append_owned_tv(l, (typval_T) { + .v_type = VAR_NUMBER, + .v_lock = VAR_UNLOCKED, + .vval.v_number = n, + }); } //{{{2 Operations on the whole list diff --git a/src/nvim/lua/converter.c b/src/nvim/lua/converter.c index 8303ecdd37..89fedae73a 100644 --- a/src/nvim/lua/converter.c +++ b/src/nvim/lua/converter.c @@ -212,19 +212,27 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) const char *s = lua_tolstring(lstate, -2, &len); if (cur.special) { list_T *const kv_pair = tv_list_alloc(); + tv_list_append_list(cur.tv->vval.v_list, kv_pair); - listitem_T *const key = tv_list_item_alloc(); - *TV_LIST_ITEM_TV(key) = decode_string(s, len, kTrue, false, false); - tv_list_append(kv_pair, key); - if (TV_LIST_ITEM_TV(key)->v_type == VAR_UNKNOWN) { + typval_T s_tv = decode_string(s, len, kTrue, false, false); + if (s_tv.v_type == VAR_UNKNOWN) { ret = false; tv_list_unref(kv_pair); continue; } - listitem_T *const val = tv_list_item_alloc(); - tv_list_append(kv_pair, val); + tv_list_append_owned_tv(kv_pair, s_tv); + + // Value: not populated yet, need to create list item to push. + tv_list_append_owned_tv(kv_pair, (typval_T) { + .v_type = VAR_UNKNOWN, + }); kv_push(stack, cur); - cur = (TVPopStackItem) { TV_LIST_ITEM_TV(val), false, false, 0 }; + cur = (TVPopStackItem) { + .tv = TV_LIST_ITEM_TV(tv_list_last(kv_pair)), + .container = false, + .special = false, + .idx = 0, + }; } else { dictitem_T *const di = tv_dict_item_alloc_len(s, len); if (tv_dict_add(cur.tv->vval.v_dict, di) == FAIL) { @@ -244,10 +252,18 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) lua_pop(lstate, 2); continue; } - listitem_T *const li = tv_list_item_alloc(); - tv_list_append(cur.tv->vval.v_list, li); + // Not populated yet, need to create list item to push. + tv_list_append_owned_tv(cur.tv->vval.v_list, (typval_T) { + .v_type = VAR_UNKNOWN, + }); kv_push(stack, cur); - cur = (TVPopStackItem) { TV_LIST_ITEM_TV(li), false, false, 0 }; + // TODO(ZyX-I): Use indexes, here list item *will* be reallocated. + cur = (TVPopStackItem) { + .tv = TV_LIST_ITEM_TV(tv_list_last(cur.tv->vval.v_list)), + .container = false, + .special = false, + .idx = 0, + }; } } assert(!cur.container); diff --git a/test/unit/eval/typval_spec.lua b/test/unit/eval/typval_spec.lua index bec74f05fc..2ae69ec04b 100644 --- a/test/unit/eval/typval_spec.lua +++ b/test/unit/eval/typval_spec.lua @@ -678,6 +678,66 @@ describe('typval.c', function() eq({int(-100500), int(100500)}, typvalt2lua(l_tv)) end) end) + describe('tv()', function() + itp('works', function() + local l_tv = lua2typvalt(empty_list) + local l = l_tv.vval.v_list + + local l_l_tv = lua2typvalt(empty_list) + alloc_log:clear() + local l_l = l_l_tv.vval.v_list + eq(1, l_l.lv_refcount) + lib.tv_list_append_tv(l, l_l_tv) + eq(2, l_l.lv_refcount) + eq(l_l, l.lv_first.li_tv.vval.v_list) + alloc_log:check({ + a.li(l.lv_first), + }) + + local l_s_tv = lua2typvalt('test') + alloc_log:check({ + a.str(l_s_tv.vval.v_string, 'test'), + }) + lib.tv_list_append_tv(l, l_s_tv) + alloc_log:check({ + a.li(l.lv_last), + a.str(l.lv_last.li_tv.vval.v_string, 'test'), + }) + + eq({empty_list, 'test'}, typvalt2lua(l_tv)) + end) + end) + describe('owned tv()', function() + itp('works', function() + local l_tv = lua2typvalt(empty_list) + local l = l_tv.vval.v_list + + local l_l_tv = lua2typvalt(empty_list) + alloc_log:clear() + local l_l = l_l_tv.vval.v_list + eq(1, l_l.lv_refcount) + lib.tv_list_append_owned_tv(l, l_l_tv) + eq(1, l_l.lv_refcount) + l_l.lv_refcount = l_l.lv_refcount + 1 + eq(l_l, l.lv_first.li_tv.vval.v_list) + alloc_log:check({ + a.li(l.lv_first), + }) + + local l_s_tv = ffi.gc(lua2typvalt('test'), nil) + alloc_log:check({ + a.str(l_s_tv.vval.v_string, 'test'), + }) + lib.tv_list_append_owned_tv(l, l_s_tv) + eq(l_s_tv.vval.v_string, l.lv_last.li_tv.vval.v_string) + l_s_tv.vval.v_string = nil + alloc_log:check({ + a.li(l.lv_last), + }) + + eq({empty_list, 'test'}, typvalt2lua(l_tv)) + end) + end) end) describe('copy()', function() local function tv_list_copy(...) -- cgit From 6bf3dc77c484f757d37c22694e1278abc014278f Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 24 Dec 2017 01:52:11 +0300 Subject: eval/typval: Make tv_list_item_alloc static Better write this bit in lua then make reviewers or clint filter out tv_list_item_alloc(). --- src/nvim/eval/typval.c | 2 +- test/unit/eval/helpers.lua | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 0755507f91..751f1e7aec 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -52,7 +52,7 @@ const char *const tv_empty_string = ""; /// and specifically set lv_lock. /// /// @return [allocated] new list item. -listitem_T *tv_list_item_alloc(void) +static listitem_T *tv_list_item_alloc(void) FUNC_ATTR_NONNULL_RET FUNC_ATTR_MALLOC { return xmalloc(sizeof(listitem_T)); diff --git a/test/unit/eval/helpers.lua b/test/unit/eval/helpers.lua index d7399182f7..1a3210e1fd 100644 --- a/test/unit/eval/helpers.lua +++ b/test/unit/eval/helpers.lua @@ -7,7 +7,7 @@ local ffi = helpers.ffi local eq = helpers.eq local eval = cimport('./src/nvim/eval.h', './src/nvim/eval/typval.h', - './src/nvim/hashtab.h') + './src/nvim/hashtab.h', './src/nvim/memory.h') local null_string = {[true]='NULL string'} local null_list = {[true]='NULL list'} @@ -24,10 +24,14 @@ local nil_value = {[true]='nil'} local lua2typvalt +local function tv_list_item_alloc() + return ffi.cast('listitem_T*', eval.xmalloc(ffi.sizeof('listitem_T'))) +end + local function li_alloc(nogc) local gcfunc = eval.tv_list_item_free if nogc then gcfunc = nil end - local li = ffi.gc(eval.tv_list_item_alloc(), gcfunc) + local li = ffi.gc(tv_list_item_alloc(), gcfunc) li.li_next = nil li.li_prev = nil li.li_tv = {v_type=eval.VAR_UNKNOWN, v_lock=eval.VAR_UNLOCKED} @@ -41,7 +45,7 @@ local function populate_list(l, lua_l, processed) processed[lua_l] = l for i = 1, #lua_l do local item_tv = ffi.gc(lua2typvalt(lua_l[i], processed), nil) - local item_li = eval.tv_list_item_alloc() + local item_li = tv_list_item_alloc() item_li.li_tv = item_tv eval.tv_list_append(l, item_li) end -- cgit From 608c3d7baf2745b188bc42f9f45ad1bb188a4828 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 24 Dec 2017 02:11:46 +0300 Subject: eval/typval: Remove tv_list_item_free() as it is unused --- src/nvim/eval/typval.c | 15 +----- test/unit/eval/helpers.lua | 8 ++- test/unit/eval/typval_spec.lua | 115 +++++++++++++---------------------------- 3 files changed, 46 insertions(+), 92 deletions(-) diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 751f1e7aec..a49c34e957 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -58,18 +58,6 @@ static listitem_T *tv_list_item_alloc(void) return xmalloc(sizeof(listitem_T)); } -/// Free a list item -/// -/// Also clears the value. Does not touch watchers. -/// -/// @param[out] item Item to free. -void tv_list_item_free(listitem_T *const item) - FUNC_ATTR_NONNULL_ALL -{ - tv_clear(TV_LIST_ITEM_TV(item)); - xfree(item); -} - /// Remove a list item from a List and free it /// /// Also clears the value. @@ -80,7 +68,8 @@ void tv_list_item_remove(list_T *const l, listitem_T *const item) FUNC_ATTR_NONNULL_ALL { tv_list_remove_items(l, item, item); - tv_list_item_free(item); + tv_clear(TV_LIST_ITEM_TV(item)); + xfree(item); } //{{{2 List watchers diff --git a/test/unit/eval/helpers.lua b/test/unit/eval/helpers.lua index 1a3210e1fd..6babd4be77 100644 --- a/test/unit/eval/helpers.lua +++ b/test/unit/eval/helpers.lua @@ -28,8 +28,13 @@ local function tv_list_item_alloc() return ffi.cast('listitem_T*', eval.xmalloc(ffi.sizeof('listitem_T'))) end +local function tv_list_item_free(li) + eval.tv_clear(li.li_tv) + eval.xfree(li) +end + local function li_alloc(nogc) - local gcfunc = eval.tv_list_item_free + local gcfunc = tv_list_item_free if nogc then gcfunc = nil end local li = ffi.gc(tv_list_item_alloc(), gcfunc) li.li_next = nil @@ -537,6 +542,7 @@ return { typvalt=typvalt, li_alloc=li_alloc, + tv_list_item_free=tv_list_item_free, dict_iter=dict_iter, list_iter=list_iter, diff --git a/test/unit/eval/typval_spec.lua b/test/unit/eval/typval_spec.lua index 2ae69ec04b..0536839b71 100644 --- a/test/unit/eval/typval_spec.lua +++ b/test/unit/eval/typval_spec.lua @@ -41,6 +41,7 @@ local tbl2callback = eval_helpers.tbl2callback local dict_watchers = eval_helpers.dict_watchers local concat_tables = global_helpers.concat_tables +local map = global_helpers.map local lib = cimport('./src/nvim/eval/typval.h', './src/nvim/memory.h', './src/nvim/mbyte.h', './src/nvim/garray.h', @@ -121,118 +122,76 @@ end describe('typval.c', function() describe('list', function() describe('item', function() - describe('alloc()/free()', function() + describe('remove()', function() itp('works', function() - local li = li_alloc(true) - neq(nil, li) - lib.tv_list_item_free(li) - alloc_log:check({ - a.li(li), - a.freed(li), - }) - end) - itp('also frees the value', function() - local li - local s - local l - local tv - li = li_alloc(true) - li.li_tv.v_type = lib.VAR_NUMBER - li.li_tv.vval.v_number = 10 - lib.tv_list_item_free(li) - alloc_log:check({ - a.li(li), - a.freed(li), - }) - - li = li_alloc(true) - li.li_tv.v_type = lib.VAR_FLOAT - li.li_tv.vval.v_float = 10.5 - lib.tv_list_item_free(li) - alloc_log:check({ - a.li(li), - a.freed(li), - }) - - li = li_alloc(true) - li.li_tv.v_type = lib.VAR_STRING - li.li_tv.vval.v_string = nil - lib.tv_list_item_free(li) + local l = list(1, 2, 3, 4, 5, 6, 7) + neq(nil, l) + local lis = list_items(l) alloc_log:check({ - a.li(li), - a.freed(alloc_log.null), - a.freed(li), + a.list(l), + a.li(lis[1]), + a.li(lis[2]), + a.li(lis[3]), + a.li(lis[4]), + a.li(lis[5]), + a.li(lis[6]), + a.li(lis[7]), }) - li = li_alloc(true) - li.li_tv.v_type = lib.VAR_STRING - s = to_cstr_nofree('test') - li.li_tv.vval.v_string = s - lib.tv_list_item_free(li) + lib.tv_list_item_remove(l, lis[1]) alloc_log:check({ - a.li(li), - a.str(s, #('test')), - a.freed(s), - a.freed(li), + a.freed(table.remove(lis, 1)), }) + eq(lis, list_items(l)) - li = li_alloc(true) - li.li_tv.v_type = lib.VAR_LIST - l = ffi.gc(list(), nil) - l.lv_refcount = 2 - li.li_tv.vval.v_list = l - lib.tv_list_item_free(li) + lib.tv_list_item_remove(l, lis[6]) alloc_log:check({ - a.li(li), - a.list(l), - a.freed(li), + a.freed(table.remove(lis)), }) - eq(1, l.lv_refcount) + eq(lis, list_items(l)) - li = li_alloc(true) - tv = lua2typvalt({}) - tv.vval.v_dict.dv_refcount = 2 - li.li_tv = tv - lib.tv_list_item_free(li) + lib.tv_list_item_remove(l, lis[3]) alloc_log:check({ - a.li(li), - a.dict(tv.vval.v_dict), - a.freed(li), + a.freed(table.remove(lis, 3)), }) - eq(1, tv.vval.v_dict.dv_refcount) + eq(lis, list_items(l)) end) - end) - describe('remove()', function() - itp('works', function() - local l = list(1, 2, 3, 4, 5, 6, 7) + itp('also frees the value', function() + local l = list('a', 'b', 'c', 'd') neq(nil, l) local lis = list_items(l) alloc_log:check({ a.list(l), + a.str(lis[1].li_tv.vval.v_string, 1), a.li(lis[1]), + a.str(lis[2].li_tv.vval.v_string, 1), a.li(lis[2]), + a.str(lis[3].li_tv.vval.v_string, 1), a.li(lis[3]), + a.str(lis[4].li_tv.vval.v_string, 1), a.li(lis[4]), - a.li(lis[5]), - a.li(lis[6]), - a.li(lis[7]), }) + local strings = map(function(li) return li.li_tv.vval.v_string end, + lis) lib.tv_list_item_remove(l, lis[1]) alloc_log:check({ + a.freed(table.remove(strings, 1)), a.freed(table.remove(lis, 1)), }) eq(lis, list_items(l)) - lib.tv_list_item_remove(l, lis[6]) + lib.tv_list_item_remove(l, lis[2]) alloc_log:check({ - a.freed(table.remove(lis)), + a.freed(table.remove(strings, 2)), + a.freed(table.remove(lis, 2)), }) eq(lis, list_items(l)) - lib.tv_list_item_remove(l, lis[3]) + lib.tv_list_item_remove(l, lis[2]) alloc_log:check({ - a.freed(table.remove(lis, 3)), + a.freed(table.remove(strings, 2)), + a.freed(table.remove(lis, 2)), }) eq(lis, list_items(l)) end) -- cgit From ac55558c97d02f18b9a99cf2dd279451481c4a2f Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 24 Dec 2017 02:41:34 +0300 Subject: eval/typval: Make tv_list_item_remove return pointer to the next item --- src/nvim/eval.c | 19 ++++++++----------- src/nvim/eval/typval.c | 7 ++++++- test/unit/eval/typval_spec.lua | 20 ++++++++++---------- 3 files changed, 24 insertions(+), 22 deletions(-) diff --git a/src/nvim/eval.c b/src/nvim/eval.c index fac15770a6..0080eded98 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -2904,9 +2904,7 @@ static int do_unlet_var(lval_T *const lp, char_u *const name_end, int forceit) // Delete a range of List items. while (lp->ll_li != NULL && (lp->ll_empty2 || lp->ll_n2 >= lp->ll_n1)) { - li = TV_LIST_ITEM_NEXT(lp->ll_list, lp->ll_li); - tv_list_item_remove(lp->ll_list, lp->ll_li); - lp->ll_li = li; + lp->ll_li = tv_list_item_remove(lp->ll_list, lp->ll_li); lp->ll_n1++; } } else { @@ -8467,7 +8465,6 @@ static void findfilendir(typval_T *argvars, typval_T *rettv, int find_what) static void filter_map(typval_T *argvars, typval_T *rettv, int map) { typval_T *expr; - listitem_T *li, *nli; list_T *l = NULL; dictitem_T *di; hashtab_T *ht; @@ -8551,20 +8548,21 @@ static void filter_map(typval_T *argvars, typval_T *rettv, int map) } else { vimvars[VV_KEY].vv_type = VAR_NUMBER; - for (li = tv_list_first(l); li != NULL; li = nli) { + for (listitem_T *li = tv_list_first(l); li != NULL;) { if (map && tv_check_lock(TV_LIST_ITEM_TV(li)->v_lock, arg_errmsg, TV_TRANSLATE)) { break; } - nli = TV_LIST_ITEM_NEXT(l, li); vimvars[VV_KEY].vv_nr = idx; if (filter_map_one(TV_LIST_ITEM_TV(li), expr, map, &rem) == FAIL || did_emsg) { break; } if (!map && rem) { - tv_list_item_remove(l, li); + li = tv_list_item_remove(l, li); + } else { + li = TV_LIST_ITEM_NEXT(l, li); } idx++; } @@ -15440,18 +15438,17 @@ static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort) int idx = 0; for (listitem_T *li = TV_LIST_ITEM_NEXT(l, tv_list_first(l)) - ; li != NULL - ; li = TV_LIST_ITEM_NEXT(l, li)) { + ; li != NULL;) { listitem_T *const prev_li = TV_LIST_ITEM_PREV(l, li); if (item_compare_func_ptr(&prev_li, &li) == 0) { if (info.item_compare_func_err) { EMSG(_("E882: Uniq compare function failed")); break; } - tv_list_item_remove(l, li); - li = tv_list_find(l, idx); + li = tv_list_item_remove(l, li); } else { idx++; + li = TV_LIST_ITEM_NEXT(l, li); } } } diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index a49c34e957..4ebe12104f 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -64,12 +64,17 @@ static listitem_T *tv_list_item_alloc(void) /// /// @param[out] l List to remove item from. /// @param[in,out] item Item to remove. -void tv_list_item_remove(list_T *const l, listitem_T *const item) +/// +/// @return Pointer to the list item just after removed one, NULL if removed +/// item was the last one. +listitem_T *tv_list_item_remove(list_T *const l, listitem_T *const item) FUNC_ATTR_NONNULL_ALL { + listitem_T *const next_item = TV_LIST_ITEM_NEXT(l, item); tv_list_remove_items(l, item, item); tv_clear(TV_LIST_ITEM_TV(item)); xfree(item); + return next_item; } //{{{2 List watchers diff --git a/test/unit/eval/typval_spec.lua b/test/unit/eval/typval_spec.lua index 0536839b71..35b5596c63 100644 --- a/test/unit/eval/typval_spec.lua +++ b/test/unit/eval/typval_spec.lua @@ -138,19 +138,19 @@ describe('typval.c', function() a.li(lis[7]), }) - lib.tv_list_item_remove(l, lis[1]) + eq(lis[2], lib.tv_list_item_remove(l, lis[1])) alloc_log:check({ a.freed(table.remove(lis, 1)), }) eq(lis, list_items(l)) - lib.tv_list_item_remove(l, lis[6]) + eq(lis[7], lib.tv_list_item_remove(l, lis[6])) alloc_log:check({ a.freed(table.remove(lis)), }) eq(lis, list_items(l)) - lib.tv_list_item_remove(l, lis[3]) + eq(lis[4], lib.tv_list_item_remove(l, lis[3])) alloc_log:check({ a.freed(table.remove(lis, 3)), }) @@ -174,21 +174,21 @@ describe('typval.c', function() local strings = map(function(li) return li.li_tv.vval.v_string end, lis) - lib.tv_list_item_remove(l, lis[1]) + eq(lis[2], lib.tv_list_item_remove(l, lis[1])) alloc_log:check({ a.freed(table.remove(strings, 1)), a.freed(table.remove(lis, 1)), }) eq(lis, list_items(l)) - lib.tv_list_item_remove(l, lis[2]) + eq(lis[3], lib.tv_list_item_remove(l, lis[2])) alloc_log:check({ a.freed(table.remove(strings, 2)), a.freed(table.remove(lis, 2)), }) eq(lis, list_items(l)) - lib.tv_list_item_remove(l, lis[2]) + eq(nil, lib.tv_list_item_remove(l, lis[2])) alloc_log:check({ a.freed(table.remove(strings, 2)), a.freed(table.remove(lis, 2)), @@ -216,19 +216,19 @@ describe('typval.c', function() a.li(lis[7]), }) - lib.tv_list_item_remove(l, lis[4]) + eq(lis[5], lib.tv_list_item_remove(l, lis[4])) alloc_log:check({a.freed(lis[4])}) eq({lis[1], lis[5], lis[7]}, {lws[1].lw_item, lws[2].lw_item, lws[3].lw_item}) - lib.tv_list_item_remove(l, lis[2]) + eq(lis[3], lib.tv_list_item_remove(l, lis[2])) alloc_log:check({a.freed(lis[2])}) eq({lis[1], lis[5], lis[7]}, {lws[1].lw_item, lws[2].lw_item, lws[3].lw_item}) - lib.tv_list_item_remove(l, lis[7]) + eq(nil, lib.tv_list_item_remove(l, lis[7])) alloc_log:check({a.freed(lis[7])}) eq({lis[1], lis[5], nil}, {lws[1].lw_item, lws[2].lw_item, lws[3].lw_item == nil and nil}) - lib.tv_list_item_remove(l, lis[1]) + eq(lis[3], lib.tv_list_item_remove(l, lis[1])) alloc_log:check({a.freed(lis[1])}) eq({lis[3], lis[5], nil}, {lws[1].lw_item, lws[2].lw_item, lws[3].lw_item == nil and nil}) -- cgit From 67fa9e5237526b092c1ac672504e259db7f14126 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 24 Dec 2017 16:38:30 +0300 Subject: eval: Rename tv_list_remove_items() to tv_list_drop_items() tv_list_remove_items() may cause confusion with tv_list_item_remove() --- src/nvim/eval.c | 10 +++++----- src/nvim/eval/typval.c | 8 ++++---- test/unit/eval/typval_spec.lua | 10 +++++----- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 0080eded98..c5f4b78198 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -1534,8 +1534,8 @@ ex_let_vars ( EMSG(_("E688: More targets than List items")); return FAIL; } - // l may actually be NULL, but it should fail with E688 or even earlier if you - // try to do ":let [] = v:_null_list". + // lt may actually be NULL, but it should fail with E688 or even earlier if + // you try to do ":let [] = v:_null_list". assert(l != NULL); listitem_T *item = tv_list_first(l); @@ -1543,11 +1543,11 @@ ex_let_vars ( arg = skipwhite(arg + 1); arg = ex_let_one(arg, TV_LIST_ITEM_TV(item), true, (const char_u *)",;]", nextchars); - item = TV_LIST_ITEM_NEXT(l, item); if (arg == NULL) { return FAIL; } + item = TV_LIST_ITEM_NEXT(l, item); arg = skipwhite(arg); if (*arg == ';') { /* Put the rest of the list (may be empty) in the var after ';'. @@ -1559,7 +1559,7 @@ ex_let_vars ( } ltv.v_type = VAR_LIST; - ltv.v_lock = 0; + ltv.v_lock = VAR_UNLOCKED; ltv.vval.v_list = rest_list; tv_list_ref(rest_list); @@ -13283,7 +13283,7 @@ static void f_remove(typval_T *argvars, typval_T *rettv, FunPtr fptr) } else { if (argvars[2].v_type == VAR_UNKNOWN) { // Remove one item, return its value. - tv_list_remove_items(l, item, item); + tv_list_drop_items(l, item, item); *rettv = *TV_LIST_ITEM_TV(item); xfree(item); } else { diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 4ebe12104f..4f717250cd 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -71,7 +71,7 @@ listitem_T *tv_list_item_remove(list_T *const l, listitem_T *const item) FUNC_ATTR_NONNULL_ALL { listitem_T *const next_item = TV_LIST_ITEM_NEXT(l, item); - tv_list_remove_items(l, item, item); + tv_list_drop_items(l, item, item); tv_clear(TV_LIST_ITEM_TV(item)); xfree(item); return next_item; @@ -261,8 +261,8 @@ void tv_list_unref(list_T *const l) /// @param[out] l List to remove from. /// @param[in] item First item to remove. /// @param[in] item2 Last item to remove. -void tv_list_remove_items(list_T *const l, listitem_T *const item, - listitem_T *const item2) +void tv_list_drop_items(list_T *const l, listitem_T *const item, + listitem_T *const item2) FUNC_ATTR_NONNULL_ALL { // Notify watchers. @@ -296,7 +296,7 @@ void tv_list_move_items(list_T *const l, listitem_T *const item, const int cnt) FUNC_ATTR_NONNULL_ALL { - tv_list_remove_items(l, item, item2); + tv_list_drop_items(l, item, item2); item->li_prev = tgt_l->lv_last; item2->li_next = NULL; if (tgt_l->lv_last == NULL) { diff --git a/test/unit/eval/typval_spec.lua b/test/unit/eval/typval_spec.lua index 35b5596c63..0b6c4e04cc 100644 --- a/test/unit/eval/typval_spec.lua +++ b/test/unit/eval/typval_spec.lua @@ -408,7 +408,7 @@ describe('typval.c', function() }) end) end) - describe('remove_items()', function() + describe('drop_items()', function() itp('works', function() local l_tv = lua2typvalt({1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13}) local l = l_tv.vval.v_list @@ -421,19 +421,19 @@ describe('typval.c', function() } alloc_log:clear() - lib.tv_list_remove_items(l, lis[1], lis[3]) + lib.tv_list_drop_items(l, lis[1], lis[3]) eq({4, 5, 6, 7, 8, 9, 10, 11, 12, 13}, typvalt2lua(l_tv)) eq({lis[4], lis[7], lis[13]}, {lws[1].lw_item, lws[2].lw_item, lws[3].lw_item}) - lib.tv_list_remove_items(l, lis[11], lis[13]) + lib.tv_list_drop_items(l, lis[11], lis[13]) eq({4, 5, 6, 7, 8, 9, 10}, typvalt2lua(l_tv)) eq({lis[4], lis[7], nil}, {lws[1].lw_item, lws[2].lw_item, lws[3].lw_item == nil and nil}) - lib.tv_list_remove_items(l, lis[6], lis[8]) + lib.tv_list_drop_items(l, lis[6], lis[8]) eq({4, 5, 9, 10}, typvalt2lua(l_tv)) eq({lis[4], lis[9], nil}, {lws[1].lw_item, lws[2].lw_item, lws[3].lw_item == nil and nil}) - lib.tv_list_remove_items(l, lis[4], lis[10]) + lib.tv_list_drop_items(l, lis[4], lis[10]) eq(empty_list, typvalt2lua(l_tv)) eq({true, true, true}, {lws[1].lw_item == nil, lws[2].lw_item == nil, lws[3].lw_item == nil}) -- cgit From 32689aa5bed472ca981f323439b98e0911b403b9 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 24 Dec 2017 17:13:42 +0300 Subject: unittests: Remove start of trace, not end --- test/unit/helpers.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/unit/helpers.lua b/test/unit/helpers.lua index b1e709c444..589e026e5f 100644 --- a/test/unit/helpers.lua +++ b/test/unit/helpers.lua @@ -690,7 +690,9 @@ local function check_child_err(rd) break end trace[#trace + 1] = traceline - table.remove(trace, maxtrace + 1) + if #trace > maxtrace then + table.remove(trace, 1) + end end local res = sc.read(rd, 2) if #res ~= 2 then -- cgit From 2923e8533d1e28f9e771d2b4a7d279bfa266a480 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 24 Dec 2017 17:42:23 +0300 Subject: unittests: Do gc after reporting error, not before Reason: test may contain cleanup at the endwhich is needed for GC to work properly, but is not done if test fails. With collectgarbage() in former position it would crash when collecting garbage. --- test/unit/helpers.lua | 73 ++++++++++++++++++++++++++------------------------- 1 file changed, 37 insertions(+), 36 deletions(-) diff --git a/test/unit/helpers.lua b/test/unit/helpers.lua index 589e026e5f..87c838dece 100644 --- a/test/unit/helpers.lua +++ b/test/unit/helpers.lua @@ -650,8 +650,6 @@ local function itp_child(wr, func) collectgarbage('stop') child_sethook(wr) err, emsg = pcall(func) - collectgarbage('restart') - collectgarbage() debug.sethook() end emsg = tostring(emsg) @@ -662,14 +660,15 @@ local function itp_child(wr, func) end sc.write(wr, ('-\n%05u\n%s'):format(#emsg, emsg)) deinit() - sc.close(wr) - sc.exit(1) else sc.write(wr, '+\n') deinit() - sc.close(wr) - sc.exit(0) end + collectgarbage('restart') + collectgarbage() + sc.write(wr, '$\n') + sc.close(wr) + sc.exit(err and 0 or 1) end local function check_child_err(rd) @@ -695,41 +694,43 @@ local function check_child_err(rd) end end local res = sc.read(rd, 2) - if #res ~= 2 then - local error - if #trace == 0 then - error = '\nTest crashed, no trace available\n' - else - error = '\nTest crashed, trace:\n' .. tracehelp - for i = 1, #trace do - error = error .. trace[i] + if #res == 2 then + local err = '' + if res ~= '+\n' then + eq('-\n', res) + local len_s = sc.read(rd, 5) + local len = tonumber(len_s) + neq(0, len) + if os.getenv('NVIM_TEST_TRACE_ON_ERROR') == '1' and #trace ~= 0 then + err = '\nTest failed, trace:\n' .. tracehelp + for _, traceline in ipairs(trace) do + err = err .. traceline + end end + err = err .. sc.read(rd, len + 1) end - if not did_traceline then - error = error .. '\nNo end of trace occurred' - end - local cc_err, cc_emsg = pcall(check_cores, Paths.test_luajit_prg, true) - if not cc_err then - error = error .. '\ncheck_cores failed: ' .. cc_emsg + local eres = sc.read(rd, 2) + if eres ~= '$\n' then + if #trace == 0 then + err = '\nTest crashed, no trace available\n' + else + err = '\nTest crashed, trace:\n' .. tracehelp + for i = 1, #trace do + err = err .. trace[i] + end + end + if not did_traceline then + err = err .. '\nNo end of trace occurred' + end + local cc_err, cc_emsg = pcall(check_cores, Paths.test_luajit_prg, true) + if not cc_err then + err = err .. '\ncheck_cores failed: ' .. cc_emsg + end end - assert.just_fail(error) - end - if res == '+\n' then - return - end - eq('-\n', res) - local len_s = sc.read(rd, 5) - local len = tonumber(len_s) - neq(0, len) - local err = '' - if os.getenv('NVIM_TEST_TRACE_ON_ERROR') == '1' and #trace ~= 0 then - err = '\nTest failed, trace:\n' .. tracehelp - for _, traceline in ipairs(trace) do - err = err .. traceline + if err ~= '' then + assert.just_fail(err) end end - err = err .. sc.read(rd, len + 1) - assert.just_fail(err) end local function itp_parent(rd, pid, allow_failure) -- cgit From 7997147245da8c9e9fca4e1862d71bd6b28e1c06 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 24 Dec 2017 17:52:04 +0300 Subject: eval: Replace some tv_list_item_remove() calls There is nothing wrong with them, just it is generally better to remove a range then to remove items individually. --- src/nvim/eval.c | 44 ++++++++++++-------------- src/nvim/eval/typval.c | 17 ++++++++++ test/unit/eval/typval_spec.lua | 71 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 108 insertions(+), 24 deletions(-) diff --git a/src/nvim/eval.c b/src/nvim/eval.c index c5f4b78198..c878f0481d 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -2887,26 +2887,25 @@ static int do_unlet_var(lval_T *const lp, char_u *const name_end, int forceit) lp->ll_name_len))) { return FAIL; } else if (lp->ll_range) { - listitem_T *li; - listitem_T *ll_li = lp->ll_li; - int ll_n1 = lp->ll_n1; - - while (ll_li != NULL && (lp->ll_empty2 || lp->ll_n2 >= ll_n1)) { - li = TV_LIST_ITEM_NEXT(lp->ll_list, ll_li); - if (tv_check_lock(TV_LIST_ITEM_TV(ll_li)->v_lock, + // Delete a range of List items. + listitem_T *const first_li = lp->ll_li; + listitem_T *last_li = first_li; + for (;;) { + listitem_T *const li = TV_LIST_ITEM_NEXT(lp->ll_list, lp->ll_li); + if (tv_check_lock(TV_LIST_ITEM_TV(lp->ll_li)->v_lock, (const char *)lp->ll_name, lp->ll_name_len)) { return false; } - ll_li = li; - ll_n1++; - } - - // Delete a range of List items. - while (lp->ll_li != NULL && (lp->ll_empty2 || lp->ll_n2 >= lp->ll_n1)) { - lp->ll_li = tv_list_item_remove(lp->ll_list, lp->ll_li); + lp->ll_li = li; lp->ll_n1++; + if (lp->ll_li == NULL || (!lp->ll_empty2 && lp->ll_n2 < lp->ll_n1)) { + break; + } else { + last_li = lp->ll_li; + } } + tv_list_remove_items(lp->ll_list, first_li, last_li); } else { if (lp->ll_list != NULL) { // unlet a List item. @@ -13123,16 +13122,13 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } /* while */ - /* - * For a negative line count use only the lines at the end of the file, - * free the rest. - */ - if (maxline < 0) - while (cnt > -maxline) { - tv_list_item_remove(rettv->vval.v_list, - tv_list_first(rettv->vval.v_list)); - cnt--; - } + // For a negative line count use only the lines at the end of the file, + // free the rest. + if (maxline < -tv_list_len(rettv->vval.v_list)) { + listitem_T *const first_li = tv_list_find(rettv->vval.v_list, maxline); + listitem_T *const last_li = tv_list_last(rettv->vval.v_list); + tv_list_remove_items(rettv->vval.v_list, first_li, last_li); + } xfree(prev); fclose(fd); diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 4f717250cd..5c9005595d 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -284,6 +284,23 @@ void tv_list_drop_items(list_T *const l, listitem_T *const item, l->lv_idx_item = NULL; } +/// Like tv_list_drop_items, but also frees all removed items +void tv_list_remove_items(list_T *const l, listitem_T *const item, + listitem_T *const item2) + FUNC_ATTR_NONNULL_ALL +{ + tv_list_drop_items(l, item, item2); + for(listitem_T *li = item;;) { + tv_clear(TV_LIST_ITEM_TV(li)); + listitem_T *const nli = li->li_next; + xfree(li); + if (li == item2) { + break; + } + li = nli; + } +} + /// Move items "item" to "item2" from list "l" to the end of the list "tgt_l" /// /// @param[out] l List to move from. diff --git a/test/unit/eval/typval_spec.lua b/test/unit/eval/typval_spec.lua index 0b6c4e04cc..6ca51fed85 100644 --- a/test/unit/eval/typval_spec.lua +++ b/test/unit/eval/typval_spec.lua @@ -444,6 +444,77 @@ describe('typval.c', function() alloc_log:check({}) end) end) + describe('remove_items()', function() + itp('works', function() + local l_tv = lua2typvalt({'1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13'}) + local l = l_tv.vval.v_list + local lis = list_items(l) + local strings = map(function(li) return li.li_tv.vval.v_string end, lis) + -- Three watchers: pointing to first, middle and last elements. + local lws = { + list_watch(l, lis[1]), + list_watch(l, lis[7]), + list_watch(l, lis[13]), + } + alloc_log:clear() + + lib.tv_list_remove_items(l, lis[1], lis[3]) + eq({'4', '5', '6', '7', '8', '9', '10', '11', '12', '13'}, typvalt2lua(l_tv)) + eq({lis[4], lis[7], lis[13]}, {lws[1].lw_item, lws[2].lw_item, lws[3].lw_item}) + alloc_log:check({ + a.freed(strings[1]), + a.freed(lis[1]), + a.freed(strings[2]), + a.freed(lis[2]), + a.freed(strings[3]), + a.freed(lis[3]), + }) + + lib.tv_list_remove_items(l, lis[11], lis[13]) + eq({'4', '5', '6', '7', '8', '9', '10'}, typvalt2lua(l_tv)) + eq({lis[4], lis[7], nil}, {lws[1].lw_item, lws[2].lw_item, lws[3].lw_item == nil and nil}) + alloc_log:check({ + a.freed(strings[11]), + a.freed(lis[11]), + a.freed(strings[12]), + a.freed(lis[12]), + a.freed(strings[13]), + a.freed(lis[13]), + }) + + lib.tv_list_remove_items(l, lis[6], lis[8]) + eq({'4', '5', '9', '10'}, typvalt2lua(l_tv)) + eq({lis[4], lis[9], nil}, {lws[1].lw_item, lws[2].lw_item, lws[3].lw_item == nil and nil}) + alloc_log:check({ + a.freed(strings[6]), + a.freed(lis[6]), + a.freed(strings[7]), + a.freed(lis[7]), + a.freed(strings[8]), + a.freed(lis[8]), + }) + + lib.tv_list_remove_items(l, lis[4], lis[10]) + eq(empty_list, typvalt2lua(l_tv)) + eq({true, true, true}, {lws[1].lw_item == nil, lws[2].lw_item == nil, lws[3].lw_item == nil}) + alloc_log:check({ + a.freed(strings[4]), + a.freed(lis[4]), + a.freed(strings[5]), + a.freed(lis[5]), + a.freed(strings[9]), + a.freed(lis[9]), + a.freed(strings[10]), + a.freed(lis[10]), + }) + + lib.tv_list_watch_remove(l, lws[1]) + lib.tv_list_watch_remove(l, lws[2]) + lib.tv_list_watch_remove(l, lws[3]) + + alloc_log:check({}) + end) + end) describe('insert', function() describe('()', function() itp('works', function() -- cgit From bc52ec61105160eb2b648af239e44cc594529fbf Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 24 Dec 2017 23:09:26 +0300 Subject: *: Fix linter errors --- src/nvim/eval/typval.c | 2 +- test/unit/eval/typval_spec.lua | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 5c9005595d..fba9e9c843 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -290,7 +290,7 @@ void tv_list_remove_items(list_T *const l, listitem_T *const item, FUNC_ATTR_NONNULL_ALL { tv_list_drop_items(l, item, item2); - for(listitem_T *li = item;;) { + for (listitem_T *li = item;;) { tv_clear(TV_LIST_ITEM_TV(li)); listitem_T *const nli = li->li_next; xfree(li); diff --git a/test/unit/eval/typval_spec.lua b/test/unit/eval/typval_spec.lua index 6ca51fed85..b668144175 100644 --- a/test/unit/eval/typval_spec.lua +++ b/test/unit/eval/typval_spec.lua @@ -81,8 +81,6 @@ local function get_alloc_rets(exp_log, res) return exp_log end -local to_cstr_nofree = function(v) return lib.xstrdup(v) end - local alloc_log = alloc_log_new() before_each(function() -- cgit From 6ab5eb347bb437c4aed82e329e65244c63566530 Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 25 Dec 2017 01:08:58 +0300 Subject: eval: Remove magic numbers from find_some_match() type argument --- src/nvim/eval.c | 155 +++++++++++++++++++++++++++++++++----------------------- 1 file changed, 93 insertions(+), 62 deletions(-) diff --git a/src/nvim/eval.c b/src/nvim/eval.c index c878f0481d..d0f8ced18b 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -215,6 +215,15 @@ static garray_T ga_scripts = {0, 0, sizeof(scriptvar_T *), 4, NULL}; static int echo_attr = 0; /* attributes used for ":echo" */ +/// Describe data to return from find_some_match() +typedef enum { + kSomeMatch, ///< Data for match(). + kSomeMatchEnd, ///< Data for matchend(). + kSomeMatchList, ///< Data for matchlist(). + kSomeMatchStr, ///< Data for matchstr(). + kSomeMatchStrPos, ///< Data for matchstrpos(). +} SomeMatchType; + /// trans_function_name() flags typedef enum { TFN_INT = 1, ///< May use internal function name @@ -12183,7 +12192,8 @@ static void f_mapcheck(typval_T *argvars, typval_T *rettv, FunPtr fptr) } -static void find_some_match(typval_T *argvars, typval_T *rettv, int type) +static void find_some_match(typval_T *const argvars, typval_T *const rettv, + const SomeMatchType type) { char_u *str = NULL; long len = 0; @@ -12204,24 +12214,36 @@ static void find_some_match(typval_T *argvars, typval_T *rettv, int type) p_cpo = (char_u *)""; rettv->vval.v_number = -1; - if (type == 3 || type == 4) { - // type 3: return empty list when there are no matches. - // type 4: return ["", -1, -1, -1] - tv_list_alloc_ret(rettv); - if (type == 4) { + switch (type) { + // matchlist(): return empty list when there are no matches. + case kSomeMatchList: { + tv_list_alloc_ret(rettv); + FALLTHROUGH; + } + // matchstrpos(): return ["", -1, -1, -1] + case kSomeMatchStrPos: { tv_list_append_string(rettv->vval.v_list, "", 0); tv_list_append_number(rettv->vval.v_list, -1); tv_list_append_number(rettv->vval.v_list, -1); tv_list_append_number(rettv->vval.v_list, -1); + break; + } + case kSomeMatchStr: { + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + break; + } + case kSomeMatch: + case kSomeMatchEnd: { + // Do nothing: zero is default. + break; } - } else if (type == 2) { - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; } if (argvars[0].v_type == VAR_LIST) { - if ((l = argvars[0].vval.v_list) == NULL) + if ((l = argvars[0].vval.v_list) == NULL) { goto theend; + } li = tv_list_first(l); } else { expr = str = (char_u *)tv_get_string(&argvars[0]); @@ -12311,63 +12333,72 @@ static void find_some_match(typval_T *argvars, typval_T *rettv, int type) } if (match) { - if (type == 4) { - list_T *const ret_l = rettv->vval.v_list; - listitem_T *li1 = tv_list_first(ret_l); - listitem_T *li2 = TV_LIST_ITEM_NEXT(ret_l, li1); - listitem_T *li3 = TV_LIST_ITEM_NEXT(ret_l, li2); - listitem_T *li4 = TV_LIST_ITEM_NEXT(ret_l, li3); - xfree(TV_LIST_ITEM_TV(li1)->vval.v_string); - - const size_t rd = (size_t)(regmatch.endp[0] - regmatch.startp[0]); - TV_LIST_ITEM_TV(li1)->vval.v_string = xmemdupz( - (const char *)regmatch.startp[0], rd); - TV_LIST_ITEM_TV(li3)->vval.v_number = (varnumber_T)( - regmatch.startp[0] - expr); - TV_LIST_ITEM_TV(li4)->vval.v_number = (varnumber_T)( - regmatch.endp[0] - expr); - if (l != NULL) { - TV_LIST_ITEM_TV(li2)->vval.v_number = (varnumber_T)idx; + switch (type) { + case kSomeMatchStrPos: { + list_T *const ret_l = rettv->vval.v_list; + listitem_T *li1 = tv_list_first(ret_l); + listitem_T *li2 = TV_LIST_ITEM_NEXT(ret_l, li1); + listitem_T *li3 = TV_LIST_ITEM_NEXT(ret_l, li2); + listitem_T *li4 = TV_LIST_ITEM_NEXT(ret_l, li3); + xfree(TV_LIST_ITEM_TV(li1)->vval.v_string); + + const size_t rd = (size_t)(regmatch.endp[0] - regmatch.startp[0]); + TV_LIST_ITEM_TV(li1)->vval.v_string = xmemdupz( + (const char *)regmatch.startp[0], rd); + TV_LIST_ITEM_TV(li3)->vval.v_number = (varnumber_T)( + regmatch.startp[0] - expr); + TV_LIST_ITEM_TV(li4)->vval.v_number = (varnumber_T)( + regmatch.endp[0] - expr); + if (l != NULL) { + TV_LIST_ITEM_TV(li2)->vval.v_number = (varnumber_T)idx; + } + break; } - } else if (type == 3) { - int i; - - /* return list with matched string and submatches */ - for (i = 0; i < NSUBEXP; ++i) { - if (regmatch.endp[i] == NULL) { - tv_list_append_string(rettv->vval.v_list, NULL, 0); - } else { - tv_list_append_string(rettv->vval.v_list, - (const char *)regmatch.startp[i], - (regmatch.endp[i] - regmatch.startp[i])); + case kSomeMatchList: { + // Return list with matched string and submatches. + for (int i = 0; i < NSUBEXP; ++i) { + if (regmatch.endp[i] == NULL) { + tv_list_append_string(rettv->vval.v_list, NULL, 0); + } else { + tv_list_append_string(rettv->vval.v_list, + (const char *)regmatch.startp[i], + (regmatch.endp[i] - regmatch.startp[i])); + } } + break; } - } else if (type == 2) { - // Return matched string. - if (l != NULL) { - tv_copy(TV_LIST_ITEM_TV(li), rettv); - } else { - rettv->vval.v_string = (char_u *)xmemdupz( - (const char *)regmatch.startp[0], - (size_t)(regmatch.endp[0] - regmatch.startp[0])); + case kSomeMatchStr: { + // Return matched string. + if (l != NULL) { + tv_copy(TV_LIST_ITEM_TV(li), rettv); + } else { + rettv->vval.v_string = (char_u *)xmemdupz( + (const char *)regmatch.startp[0], + (size_t)(regmatch.endp[0] - regmatch.startp[0])); + } + break; } - } else if (l != NULL) { - rettv->vval.v_number = idx; - } else { - if (type != 0) { - rettv->vval.v_number = - (varnumber_T)(regmatch.startp[0] - str); - } else { - rettv->vval.v_number = - (varnumber_T)(regmatch.endp[0] - str); + case kSomeMatch: + case kSomeMatchEnd: { + if (l != NULL) { + rettv->vval.v_number = idx; + } else { + if (type == kSomeMatchEnd) { + rettv->vval.v_number = + (varnumber_T)(regmatch.startp[0] - str); + } else { + rettv->vval.v_number = + (varnumber_T)(regmatch.endp[0] - str); + } + rettv->vval.v_number += (varnumber_T)(str - expr); + } } - rettv->vval.v_number += (varnumber_T)(str - expr); } } vim_regfree(regmatch.regprog); } - if (type == 4 && l == NULL) { + if (type == kSomeMatchStrPos && l == NULL) { // matchstrpos() without a list: drop the second item list_T *const ret_l = rettv->vval.v_list; tv_list_item_remove(ret_l, TV_LIST_ITEM_NEXT(ret_l, tv_list_first(ret_l))); @@ -12383,7 +12414,7 @@ theend: */ static void f_match(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - find_some_match(argvars, rettv, 1); + find_some_match(argvars, rettv, kSomeMatch); } /* @@ -12528,7 +12559,7 @@ static void f_matchdelete(typval_T *argvars, typval_T *rettv, FunPtr fptr) */ static void f_matchend(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - find_some_match(argvars, rettv, 0); + find_some_match(argvars, rettv, kSomeMatchEnd); } /* @@ -12536,7 +12567,7 @@ static void f_matchend(typval_T *argvars, typval_T *rettv, FunPtr fptr) */ static void f_matchlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - find_some_match(argvars, rettv, 3); + find_some_match(argvars, rettv, kSomeMatchList); } /* @@ -12544,13 +12575,13 @@ static void f_matchlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) */ static void f_matchstr(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - find_some_match(argvars, rettv, 2); + find_some_match(argvars, rettv, kSomeMatchStr); } /// "matchstrpos()" function static void f_matchstrpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - find_some_match(argvars, rettv, 4); + find_some_match(argvars, rettv, kSomeMatchStrPos); } /// Get maximal/minimal number value in a list or dictionary -- cgit From b6ee90a2433276175462b81106378009b4893e04 Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 25 Dec 2017 01:44:44 +0300 Subject: eval: Refactor some potentially dangerous list appends --- src/nvim/eval.c | 71 ++++++++++++++++---------------------------------- src/nvim/eval/typval.c | 42 +++++++++++++++++++++++++++++ src/nvim/eval/typval.h | 22 ++++++---------- 3 files changed, 73 insertions(+), 62 deletions(-) diff --git a/src/nvim/eval.c b/src/nvim/eval.c index d0f8ced18b..370d4f0c0b 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -2421,9 +2421,11 @@ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv, if (TV_LIST_ITEM_NEXT(lp->ll_list, lp->ll_li) == NULL) { // Need to add an empty item. tv_list_append_number(lp->ll_list, 0); - assert(TV_LIST_ITEM_NEXT(lp->ll_list, lp->ll_li)); + // ll_li may have become invalid after append, don’t use it. + lp->ll_li = tv_list_last(lp->ll_list); // Valid again. + } else { + lp->ll_li = TV_LIST_ITEM_NEXT(lp->ll_list, lp->ll_li); } - lp->ll_li = TV_LIST_ITEM_NEXT(lp->ll_list, lp->ll_li); lp->ll_n1++; } if (ri != NULL) { @@ -4528,7 +4530,7 @@ eval_index ( item = tv_list_find(rettv->vval.v_list, n1); while (n1++ <= n2) { tv_list_append_tv(l, TV_LIST_ITEM_TV(item)); - item = TV_LIST_ITEM_NEXT(l, item); + item = TV_LIST_ITEM_NEXT(rettv->vval.v_list, item); } tv_clear(rettv); rettv->v_type = VAR_LIST; @@ -12529,12 +12531,12 @@ static void f_matcharg(typval_T *argvars, typval_T *rettv, FunPtr fptr) { tv_list_alloc_ret(rettv); - int id = tv_get_number(&argvars[0]); + const int id = tv_get_number(&argvars[0]); if (id >= 1 && id <= 3) { - matchitem_T *m; + matchitem_T *const m = (matchitem_T *)get_match(curwin, id); - if ((m = (matchitem_T *)get_match(curwin, id)) != NULL) { + if (m != NULL) { tv_list_append_string(rettv->vval.v_list, (const char *)syn_id2name(m->hlg_id), -1); tv_list_append_string(rettv->vval.v_list, (const char *)m->pattern, -1); @@ -15135,12 +15137,6 @@ static void f_sockconnect(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->v_type = VAR_NUMBER; } -/// struct used in the array that's given to qsort() -typedef struct { - listitem_T *item; - int idx; -} sortItem_T; - /// struct storing information about current sort typedef struct { int item_compare_ic; @@ -15161,8 +15157,8 @@ static sortinfo_T *sortinfo = NULL; */ static int item_compare(const void *s1, const void *s2, bool keep_zero) { - sortItem_T *const si1 = (sortItem_T *)s1; - sortItem_T *const si2 = (sortItem_T *)s2; + ListSortItem *const si1 = (ListSortItem *)s1; + ListSortItem *const si2 = (ListSortItem *)s2; typval_T *const tv1 = TV_LIST_ITEM_TV(si1->item); typval_T *const tv2 = TV_LIST_ITEM_TV(si2->item); @@ -15256,7 +15252,7 @@ static int item_compare_not_keeping_zero(const void *s1, const void *s2) static int item_compare2(const void *s1, const void *s2, bool keep_zero) { - sortItem_T *si1, *si2; + ListSortItem *si1, *si2; int res; typval_T rettv; typval_T argv[3]; @@ -15269,8 +15265,8 @@ static int item_compare2(const void *s1, const void *s2, bool keep_zero) return 0; } - si1 = (sortItem_T *)s1; - si2 = (sortItem_T *)s2; + si1 = (ListSortItem *)s1; + si2 = (ListSortItem *)s2; if (partial == NULL) { func_name = sortinfo->item_compare_func; @@ -15327,7 +15323,7 @@ static int item_compare2_not_keeping_zero(const void *s1, const void *s2) */ static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort) { - sortItem_T *ptrs; + ListSortItem *ptrs; long len; long i; @@ -15417,42 +15413,21 @@ static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort) } /* Make an array with each entry pointing to an item in the List. */ - ptrs = xmalloc((size_t)(len * sizeof (sortItem_T))); + ptrs = xmalloc((size_t)(len * sizeof(ListSortItem))); - i = 0; if (sort) { - // sort(): ptrs will be the list to sort. - TV_LIST_ITER(l, li, { - ptrs[i].item = li; - ptrs[i].idx = i; - i++; - }); - info.item_compare_func_err = false; - // Test the compare function. - if ((info.item_compare_func != NULL - || info.item_compare_partial != NULL) - && item_compare2_not_keeping_zero(&ptrs[0], &ptrs[1]) - == ITEM_COMPARE_FAIL) { + tv_list_item_sort(l, ptrs, + ((info.item_compare_func == NULL + && info.item_compare_partial == NULL) + ? item_compare_not_keeping_zero + : item_compare2_not_keeping_zero), + &info.item_compare_func_err); + if (info.item_compare_func_err) { EMSG(_("E702: Sort compare function failed")); - } else { - // Sort the array with item pointers. - qsort(ptrs, (size_t)len, sizeof (sortItem_T), - (info.item_compare_func == NULL - && info.item_compare_partial == NULL ? - item_compare_not_keeping_zero : - item_compare2_not_keeping_zero)); - - if (!info.item_compare_func_err) { - // Clear the list and append the items in the sorted order. - tv_list_clear(l); - for (i = 0; i < len; i++) { - tv_list_append(l, ptrs[i].item); - } - } } } else { - int (*item_compare_func_ptr)(const void *, const void *); + ListSorter item_compare_func_ptr; // f_uniq(): ptrs will be a stack of items to remove. info.item_compare_func_err = false; diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index fba9e9c843..21bb84a945 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -757,6 +758,47 @@ void tv_list_reverse(list_T *const l) l->lv_idx = l->lv_len - l->lv_idx - 1; } +// FIXME Add unit tests for tv_list_item_sort(). + +/// Sort list using libc qsort +/// +/// @param[in,out] l List to sort, will be sorted in-place. +/// @param ptrs Preallocated array of items to sort, must have at least +/// tv_list_len(l) entries. Should not be initialized. +/// @param[in] item_compare_func Function used to compare list items. +/// @param errp Location where information about whether error occurred is +/// saved by item_compare_func. If boolean there appears to be +/// true list will not be modified. Must be initialized to false +/// by the caller. +void tv_list_item_sort(list_T *const l, ListSortItem *const ptrs, + const ListSorter item_compare_func, + bool *errp) + FUNC_ATTR_NONNULL_ARG(3, 4) +{ + const int len = tv_list_len(l); + if (len <= 1) { + return; + } + int i = 0; + TV_LIST_ITER(l, li, { + ptrs[i].item = li; + ptrs[i].idx = i; + i++; + }); + // Sort the array with item pointers. + qsort(ptrs, (size_t)len, sizeof(ListSortItem), item_compare_func); + if (!(*errp)) { + // Clear the list and append the items in the sorted order. + l->lv_first = NULL; + l->lv_last = NULL; + l->lv_idx_item = NULL; + l->lv_len = 0; + for (i = 0; i < len; i++) { + tv_list_append(l, ptrs[i].item); + } + } +} + //{{{2 Indexing/searching /// Locate item with a given index in a list and return it diff --git a/src/nvim/eval/typval.h b/src/nvim/eval/typval.h index 2bce7bd6b2..c9a9a3e7e8 100644 --- a/src/nvim/eval/typval.h +++ b/src/nvim/eval/typval.h @@ -296,6 +296,14 @@ typedef struct list_stack_S { struct list_stack_S *prev; } list_stack_T; +/// Structure representing one list item, used for sort array. +typedef struct { + listitem_T *item; ///< Sorted list item. + int idx; ///< Sorted list item index. +} ListSortItem; + +typedef int (*ListSorter)(const void *, const void *); + // In a hashtab item "hi_key" points to "di_key" in a dictitem. // This avoids adding a pointer to the hashtab item. @@ -403,20 +411,6 @@ static inline list_T *tv_list_latest_copy(const list_T *const l) return l->lv_copylist; } -/// Clear the list without freeing anything at all -/// -/// For use in sort() which saves items to a separate array and readds them back -/// after sorting via a number of tv_list_append() calls. -/// -/// @param[out] l List to clear. -static inline void tv_list_clear(list_T *const l) -{ - l->lv_first = NULL; - l->lv_last = NULL; - l->lv_idx_item = NULL; - l->lv_len = 0; -} - static inline int tv_list_uidx(const list_T *const l, int n) REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; -- cgit From fe60fa9faafd90cfe756c80119abae6f8906fc4b Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sat, 16 Dec 2017 21:33:59 +0100 Subject: doc vim-patch:8.0.1206: no autocmd for entering or leaving the command line (commit a4f6cec7a31ff8dbfa089b9e22227afbeb951e9b) NA patches: vim-patch:8.0.0320: warning for unused variable with small build --- runtime/autoload/health/nvim.vim | 2 +- runtime/doc/api.txt | 44 ++++++++++++++++++++-------------------- runtime/doc/channel.txt | 4 ++-- runtime/doc/develop.txt | 6 +++--- runtime/doc/vim_diff.txt | 1 + scripts/gen_api_vimdoc.py | 2 +- src/nvim/api/vim.c | 2 +- 7 files changed, 31 insertions(+), 30 deletions(-) diff --git a/runtime/autoload/health/nvim.vim b/runtime/autoload/health/nvim.vim index 58033f0405..017c047ef4 100644 --- a/runtime/autoload/health/nvim.vim +++ b/runtime/autoload/health/nvim.vim @@ -174,7 +174,7 @@ function! s:check_terminal() abort \ .(empty(kbs_entry) ? '? (not found)' : kdch1_entry)) endif for env_var in ['XTERM_VERSION', 'VTE_VERSION', 'TERM_PROGRAM', 'COLORTERM', 'SSH_TTY'] - if !exists('$'.env_var) + if exists('$'.env_var) call health#report_info(printf("$%s='%s'", env_var, eval('$'.env_var))) endif endfor diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt index 6c2a3a8632..fd6918de43 100644 --- a/runtime/doc/api.txt +++ b/runtime/doc/api.txt @@ -512,27 +512,27 @@ nvim_parse_expression({expr}, {flags}, {highlight}) [start_col, end_col)). Return:~ - AST: top-level dictionary holds keys "error": Dictionary - with error, present only if parser saw some error. - Contains the following keys: "message": String, error - message in printf format, translated. Must contain exactly - one "%.*s". "arg": String, error message argument. "len": - Amount of bytes successfully parsed. With flags equal to - "" that should be equal to the length of expr string. - @note: “Sucessfully parsed” here means “participated in - AST creation”, not “till the first error”. "ast": AST, - either nil or a dictionary with these keys: "type": node - type, one of the value names from ExprASTNodeType - stringified without "kExprNode" prefix. "start": a pair - [line, column] describing where node is “started” where - "line" is always 0 (will not be 0 if you will be using - nvim_parse_viml() on e.g. ":let", but that is not present - yet). Both elements are Integers. "len": “length” of the - node. This and "start" are there for debugging purposes - primary (debugging parser and providing debug - information). "children": a list of nodes described in - top/"ast". There always is zero, one or two children, key - will not be present if node has no children. Maximum + AST: top-level dictionary with these keys: "error": + Dictionary with error, present only if parser saw some + error. Contains the following keys: "message": String, + error message in printf format, translated. Must contain + exactly one "%.*s". "arg": String, error message argument. + "len": Amount of bytes successfully parsed. With flags + equal to "" that should be equal to the length of expr + string. @note: “Sucessfully parsed” here means + “participated in AST creation”, not “till the first + error”. "ast": AST, either nil or a dictionary with these + keys: "type": node type, one of the value names from + ExprASTNodeType stringified without "kExprNode" prefix. + "start": a pair [line, column] describing where node is + “started” where "line" is always 0 (will not be 0 if you + will be using nvim_parse_viml() on e.g. ":let", but that + is not present yet). Both elements are Integers. "len": + “length” of the node. This and "start" are there for + debugging purposes primary (debugging parser and providing + debug information). "children": a list of nodes described + in top/"ast". There always is zero, one or two children, + key will not be present if node has no children. Maximum number of children may be found in node_maxchildren array. Local values (present only for certain nodes): "scope": a single Integer, specifies scope for "Option" and @@ -1047,4 +1047,4 @@ nvim_ui_try_resize({width}, {height}) *nvim_ui_try_resize()* nvim_ui_set_option({name}, {value}) *nvim_ui_set_option()* TODO: Documentation - vim:tw=78:ts=8:ft=help:norl: \ No newline at end of file + vim:tw=78:ts=8:ft=help:norl: diff --git a/runtime/doc/channel.txt b/runtime/doc/channel.txt index c4f7eb1ff1..eb2bac6fce 100644 --- a/runtime/doc/channel.txt +++ b/runtime/doc/channel.txt @@ -4,9 +4,9 @@ NVIM REFERENCE MANUAL by Thiago de Arruda -Nvim's facilities for async io *channel* +Nvim asynchronous IO *channel* - Type to see the table of contents. + Type |gO| to see the table of contents. ============================================================================== 1. Introduction *channel-intro* diff --git a/runtime/doc/develop.txt b/runtime/doc/develop.txt index 36826e2479..4e77f40035 100644 --- a/runtime/doc/develop.txt +++ b/runtime/doc/develop.txt @@ -270,9 +270,9 @@ External UIs are expected to implement these common features: - Send the "super" key (Windows key, Apple key) as a | - autocmd OptionSet guifont call rpcnotify(42, 'option-changed', 'guifont', &guifont) +- UI-related options ('guifont', 'ambiwidth', …) are published in the + "option_set" |ui-global| event. The event is triggered when the UI first + connects to Nvim and whenever an option is changed by the user or a plugin. vim:tw=78:ts=8:ft=help:norl: diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index 7061f01316..9643777975 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -70,6 +70,7 @@ Providers Ruby plugins |provider-ruby| Shared data |shada| Embedded terminal |terminal| +VimL parser |nvim_parse_expression()| XDG base directories |xdg| USER EXPERIENCE ~ diff --git a/scripts/gen_api_vimdoc.py b/scripts/gen_api_vimdoc.py index 4ddf415f1a..69f70f6e2b 100644 --- a/scripts/gen_api_vimdoc.py +++ b/scripts/gen_api_vimdoc.py @@ -478,7 +478,7 @@ def gen_docs(config): docs += '\n\n\n' docs = docs.rstrip() + '\n\n' - docs += ' vim:tw=78:ts=8:ft=help:norl:' + docs += ' vim:tw=78:ts=8:ft=help:norl:\n' doc_file = os.path.join(base_dir, 'runtime/doc', doc_filename) delete_lines_below(doc_file, section_start_token) diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index c0daac8085..172f2ce18e 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -929,7 +929,7 @@ typedef kvec_withinit_t(ExprASTConvStackItem, 16) ExprASTConvStack; /// starting column and ending column (latter exclusive: /// one should highlight region [start_col, end_col)). /// -/// @return AST: top-level dictionary holds keys +/// @return AST: top-level dictionary with these keys: /// /// "error": Dictionary with error, present only if parser saw some /// error. Contains the following keys: -- cgit From 973bd10a127f468b55c79e57f8d7215f9b6e6073 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Mon, 11 Dec 2017 01:58:55 +0100 Subject: vim-patch.sh: introduce `-V` --- scripts/vim-patch.sh | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/scripts/vim-patch.sh b/scripts/vim-patch.sh index 530701e223..55a4acc830 100755 --- a/scripts/vim-patch.sh +++ b/scripts/vim-patch.sh @@ -32,9 +32,10 @@ usage() { echo " format '7.4.xxx' or a Git commit hash." echo " -s Submit a vim-patch pull request to Neovim." echo " -r {pr-number} Review a vim-patch pull request to Neovim." + echo ' -V Clones the Vim source code to $VIM_SOURCE_DIR.' echo - echo "Set VIM_SOURCE_DIR to change where Vim's sources are stored." - echo "Default is '${VIM_SOURCE_DIR_DEFAULT}'." + echo ' $VIM_SOURCE_DIR controls where Vim sources are found' + echo " (default: '${VIM_SOURCE_DIR_DEFAULT}')" } # Checks if a program is in the user's PATH, and is executable. @@ -481,7 +482,7 @@ review_pr() { clean_files } -while getopts "hlLp:P:g:r:s" opt; do +while getopts "hlLVp:P:g:r:s" opt; do case ${opt} in h) usage @@ -515,6 +516,10 @@ while getopts "hlLp:P:g:r:s" opt; do submit_pr exit 0 ;; + V) + get_vim_sources + exit 0 + ;; *) exit 1 ;; -- cgit From 903ed09a61b15a63c4909d4fad7b098fa2368d1d Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sat, 16 Dec 2017 21:38:53 +0100 Subject: vim-patch.sh: extract list_vimpatch_tokens() Use streams instead of for-loop (20x speedup for list_vimpatch_tokens). --- scripts/vim-patch.sh | 62 +++++++++++++++++++++++++++++++--------------------- 1 file changed, 37 insertions(+), 25 deletions(-) diff --git a/scripts/vim-patch.sh b/scripts/vim-patch.sh index 55a4acc830..b82d21b03e 100755 --- a/scripts/vim-patch.sh +++ b/scripts/vim-patch.sh @@ -174,7 +174,7 @@ preprocess_patch() { "$file" > "$file".tmp && mv "$file".tmp "$file" } -get_vim_patch() { +get_vimpatch() { get_vim_sources assign_commit_details "${1}" @@ -200,7 +200,7 @@ get_vim_patch() { } stage_patch() { - get_vim_patch "$1" + get_vimpatch "$1" local try_apply="${2:-}" local git_remote @@ -329,31 +329,43 @@ submit_pr() { done } -# Prints a newline-delimited list of Vim commits, for use by scripts. -list_vim_patches() { - # Get missing Vim commits - local vim_commits - vim_commits="$(cd "${VIM_SOURCE_DIR}" && git log --reverse --format='%H' v8.0.0000..HEAD)" +# Gets all Vim commits since the "start" commit. +list_vim_commits() { ( + cd "${VIM_SOURCE_DIR}" && git log --reverse --format='%H' v8.0.0000..HEAD +) } - # Find all "vim-patch:xxx" tokens in the Nvim git log. +# Prints all "vim-patch:xxx" tokens found in the Nvim git log. +list_vimpatch_tokens() { local tokens + # Find all "vim-patch:xxx" tokens in the Nvim git log. tokens="$(cd "${NVIM_SOURCE_DIR}" && git log -E --grep='vim-patch:[^ ]+' | grep 'vim-patch')" - tokens="$(for i in $tokens ; do echo "$i" | grep -E 'vim-patch:[^ ]{7}' | sed 's/.*\(vim-patch:[.0-9a-z]\+\).*/\1/' ; done)" + echo "$tokens" | grep -E 'vim-patch:[^ ,{]{7,}' \ + | sed 's/.*\(vim-patch:[.0-9a-z]\+\).*/\1/' \ + | sort \ + | uniq +} + +# Prints a newline-delimited list of Vim commits, for use by scripts. +list_missing_vimpatches() { + local tokens vim_commit vim_commits is_missing vim_tag patch_number + + # Find all "vim-patch:xxx" tokens in the Nvim git log. + tokens="$(list_vimpatch_tokens)" - local vim_commit + # Get missing Vim commits + vim_commits="$(list_vim_commits)" for vim_commit in ${vim_commits}; do - local is_missing - local vim_tag - # This fails for untagged commits (e.g., runtime file updates) so mask the return status - vim_tag="$(cd "${VIM_SOURCE_DIR}" && git describe --tags --exact-match "${vim_commit}" 2>/dev/null)" || true - if [[ -n "${vim_tag}" ]]; then + # Check for vim-patch: (usually runtime updates). + is_missing="$(echo "$tokens" | >/dev/null 2>&1 grep "vim\-patch:${vim_commit:0:7}" && echo false || echo true)" + + if ! [ "$is_missing" = "false" ] \ + && vim_tag="$(cd "${VIM_SOURCE_DIR}" && git describe --tags --exact-match "${vim_commit}" 2>/dev/null)" + then # Vim version number (not commit hash). - local patch_number="${vim_tag:1}" # "v7.4.0001" => "7.4.0001" + # Check for vim-patch: (not commit hash). + patch_number="${vim_tag:1}" # "v7.4.0001" => "7.4.0001" is_missing="$(echo "$tokens" | >/dev/null 2>&1 grep "vim\-patch:${patch_number}" && echo false || echo true)" vim_commit="${vim_tag#v}" - else - # Untagged Vim patch (e.g. runtime updates). - is_missing="$(echo "$tokens" | >/dev/null 2>&1 grep "vim\-patch:${vim_commit:0:7}" && echo false || echo true)" fi if ! [ "$is_missing" = "false" ]; then @@ -363,11 +375,11 @@ list_vim_patches() { } # Prints a human-formatted list of Vim commits, with instructional messages. -show_vim_patches() { +show_vimpatches() { get_vim_sources printf "\nVim patches missing from Neovim:\n" - list_vim_patches | while read vim_commit; do + list_missing_vimpatches | while read vim_commit; do if (cd "${VIM_SOURCE_DIR}" && git --no-pager show --color=never --name-only "v${vim_commit}" 2>/dev/null) | grep -q ^runtime; then printf " • ${vim_commit} (+runtime)\n" else @@ -441,7 +453,7 @@ review_commit() { echo "✔ Saved pull request diff to '${NVIM_SOURCE_DIR}/n${patch_file}'." CREATED_FILES+=("${NVIM_SOURCE_DIR}/n${patch_file}") - get_vim_patch "${vim_version}" + get_vimpatch "${vim_version}" CREATED_FILES+=("${NVIM_SOURCE_DIR}/${patch_file}") echo @@ -489,11 +501,11 @@ while getopts "hlLVp:P:g:r:s" opt; do exit 0 ;; l) - show_vim_patches + show_vimpatches exit 0 ;; L) - list_vim_patches + list_missing_vimpatches exit 0 ;; p) @@ -505,7 +517,7 @@ while getopts "hlLVp:P:g:r:s" opt; do exit 0 ;; g) - get_vim_patch "${OPTARG}" + get_vimpatch "${OPTARG}" exit 0 ;; r) -- cgit From 7773bbd0989d798b29486a669775a81cd4ec6d3b Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Tue, 26 Dec 2017 03:38:42 +0100 Subject: vimpatch.lua: automate version.c Invoke it like this: VIM_SOURCE_DIR=~/neovim/.vim-src/ nvim -i NONE -u NONE --headless +'luafile ./scripts/vimpatch.lua' +q --- scripts/vim-patch.sh | 41 ++++++++++++++++++++------------ scripts/vimpatch.lua | 67 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+), 15 deletions(-) create mode 100755 scripts/vimpatch.lua diff --git a/scripts/vim-patch.sh b/scripts/vim-patch.sh index b82d21b03e..c6ff280bb5 100755 --- a/scripts/vim-patch.sh +++ b/scripts/vim-patch.sh @@ -14,25 +14,23 @@ readonly BRANCH_PREFIX="vim-" CREATED_FILES=() usage() { - echo "Helper script for porting Vim patches. For more information, see" + echo "Port Vim patches to Neovim" echo "https://github.com/neovim/neovim/wiki/Merging-patches-from-upstream-vim" echo echo "Usage: ${BASENAME} [-h | -l | -p vim-revision | -r pr-number]" echo echo "Options:" echo " -h Show this message and exit." - echo " -l Show list of missing Vim patches." - echo " -L Print missing Vim patches in machine-readable form." - echo " -p {vim-revision} Download and generate the specified Vim patch." - echo " vim-revision can be a version number '8.0.xxx'" - echo " or a valid Git ref (hash, tag, etc.)." - echo " -P {vim-revision} Download, generate and apply the Vim patch." - echo " -g {vim-revision} Download the Vim patch vim-revision." - echo " vim-revision can be a version number of the " - echo " format '7.4.xxx' or a Git commit hash." - echo " -s Submit a vim-patch pull request to Neovim." - echo " -r {pr-number} Review a vim-patch pull request to Neovim." - echo ' -V Clones the Vim source code to $VIM_SOURCE_DIR.' + echo " -l List missing Vim patches." + echo " -L List missing Vim patches (for scripts)." + echo " -M List all merged patch-numbers (at current v:version)." + echo " -p {vim-revision} Download and generate a Vim patch. vim-revision" + echo " can be a Vim version (8.0.xxx) or a Git hash." + echo " -P {vim-revision} Download, generate and apply a Vim patch." + echo " -g {vim-revision} Download a Vim patch." + echo " -s Create a vim-patch pull request." + echo " -r {pr-number} Review a vim-patch pull request." + echo ' -V Clone the Vim source code to $VIM_SOURCE_DIR.' echo echo ' $VIM_SOURCE_DIR controls where Vim sources are found' echo " (default: '${VIM_SOURCE_DIR_DEFAULT}')" @@ -334,7 +332,7 @@ list_vim_commits() { ( cd "${VIM_SOURCE_DIR}" && git log --reverse --format='%H' v8.0.0000..HEAD ) } -# Prints all "vim-patch:xxx" tokens found in the Nvim git log. +# Prints all (sorted) "vim-patch:xxx" tokens found in the Nvim git log. list_vimpatch_tokens() { local tokens # Find all "vim-patch:xxx" tokens in the Nvim git log. @@ -345,6 +343,15 @@ list_vimpatch_tokens() { | uniq } +# Prints all patch-numbers (for the current v:version) for which there is +# a "vim-patch:xxx" token in the Nvim git log. +list_vimpatch_numbers() { + # Transform "vim-patch:X.Y.ZZZZ" to "ZZZZ". + list_vimpatch_tokens | while read vimpatch_token; do + echo "$vimpatch_token" | grep '8\.0\.' | sed 's/.*vim-patch:8\.0\.\([0-9a-z]\+\).*/\1/' + done +} + # Prints a newline-delimited list of Vim commits, for use by scripts. list_missing_vimpatches() { local tokens vim_commit vim_commits is_missing vim_tag patch_number @@ -494,7 +501,7 @@ review_pr() { clean_files } -while getopts "hlLVp:P:g:r:s" opt; do +while getopts "hlLMVp:P:g:r:s" opt; do case ${opt} in h) usage @@ -508,6 +515,10 @@ while getopts "hlLVp:P:g:r:s" opt; do list_missing_vimpatches exit 0 ;; + M) + list_vimpatch_numbers + exit 0 + ;; p) stage_patch "${OPTARG}" exit 0 diff --git a/scripts/vimpatch.lua b/scripts/vimpatch.lua new file mode 100755 index 0000000000..0924f3d718 --- /dev/null +++ b/scripts/vimpatch.lua @@ -0,0 +1,67 @@ +-- Updates version.c list of applied Vim patches. +-- +-- Usage: +-- VIM_SOURCE_DIR=~/neovim/.vim-src/ nvim -i NONE -u NONE --headless +'luafile ./scripts/vimpatch.lua' +q + +local nvim = vim.api + +local function pprint(o) + print(nvim.nvim_call_function('string', { o })) +end + +local function systemlist(...) + local rv = nvim.nvim_call_function('systemlist', ...) + local err = nvim.nvim_get_vvar('shell_error') + local args_str = nvim.nvim_call_function('string', ...) + if 0 ~= err then + error('command failed: '..args_str) + end + return rv +end + +local function vimpatch_sh_list_numbers() + return systemlist( { { 'bash', '-c', 'scripts/vim-patch.sh -M', } } ) +end + +-- Generates the lines to be inserted into the src/version.c +-- `included_patches[]` definition. +local function gen_version_c_lines() + -- Set of merged Vim 8.0.zzzz patch numbers. + local merged_patch_numbers = {} + local highest = 0 + for _, n in ipairs(vimpatch_sh_list_numbers()) do + if n then + merged_patch_numbers[tonumber(n)] = true + highest = math.max(highest, n) + end + end + + local lines = {} + for i = highest, 0, -1 do + local is_merged = (nil ~= merged_patch_numbers[i]) + if is_merged then + table.insert(lines, string.format(' %s,', i)) + else + table.insert(lines, string.format(' // %s,', i)) + end + end + + return lines +end + +local function patch_version_c() + local lines = gen_version_c_lines() + + nvim.nvim_command('silent noswapfile noautocmd edit src/nvim/version.c') + nvim.nvim_command('/static const int included_patches') + -- Delete the existing lines. + nvim.nvim_command('silent normal! j0d/};\rk') + -- Insert the lines. + nvim.nvim_call_function('append', { + nvim.nvim_eval('line(".")'), + lines, + }) + nvim.nvim_command('silent write') +end + +patch_version_c() -- cgit From ac2f90f2e1d63e1aa16f377580c815059618fe94 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Tue, 26 Dec 2017 03:49:00 +0100 Subject: version.c: update --- src/nvim/version.c | 455 +++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 317 insertions(+), 138 deletions(-) diff --git a/src/nvim/version.c b/src/nvim/version.c index 8ab9fc1a4b..e35b803b4e 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -146,7 +146,7 @@ static const int included_patches[] = { // 1292, // 1291, // 1290, - // 1289, + 1289, // 1288, // 1287, // 1286, @@ -228,8 +228,187 @@ static const int included_patches[] = { // 1210, // 1209, // 1208, - // 1207, + 1207, 1206, + // 1205, + // 1204, + // 1203, + // 1202, + // 1201, + // 1200, + // 1199, + // 1198, + // 1197, + // 1196, + // 1195, + // 1194, + // 1193, + // 1192, + // 1191, + // 1190, + 1189, + // 1188, + // 1187, + // 1186, + // 1185, + // 1184, + // 1183, + // 1182, + // 1181, + // 1180, + // 1179, + // 1178, + // 1177, + // 1176, + // 1175, + // 1174, + // 1173, + // 1172, + // 1171, + // 1170, + // 1169, + // 1168, + // 1167, + // 1166, + // 1165, + // 1164, + // 1163, + // 1162, + // 1161, + // 1160, + // 1159, + // 1158, + // 1157, + // 1156, + // 1155, + // 1154, + // 1153, + // 1152, + // 1151, + // 1150, + // 1149, + // 1148, + // 1147, + // 1146, + // 1145, + // 1144, + // 1143, + // 1142, + // 1141, + // 1140, + // 1139, + // 1138, + // 1137, + // 1136, + // 1135, + // 1134, + // 1133, + // 1132, + // 1131, + // 1130, + // 1129, + // 1128, + // 1127, + // 1126, + // 1125, + // 1124, + // 1123, + // 1122, + // 1121, + // 1120, + // 1119, + // 1118, + // 1117, + // 1116, + // 1115, + // 1114, + // 1113, + // 1112, + // 1111, + // 1110, + // 1109, + 1108, + // 1107, + // 1106, + // 1105, + // 1104, + // 1103, + // 1102, + // 1101, + // 1100, + // 1099, + // 1098, + // 1097, + // 1096, + // 1095, + // 1094, + // 1093, + // 1092, + // 1091, + // 1090, + // 1089, + // 1088, + // 1087, + // 1086, + // 1085, + // 1084, + // 1083, + // 1082, + // 1081, + // 1080, + // 1079, + // 1078, + // 1077, + // 1076, + // 1075, + // 1074, + // 1073, + // 1072, + // 1071, + // 1070, + // 1069, + // 1068, + // 1067, + // 1066, + // 1065, + // 1064, + // 1063, + // 1062, + // 1061, + // 1060, + // 1059, + // 1058, + // 1057, + // 1056, + // 1055, + // 1054, + // 1053, + // 1052, + // 1051, + // 1050, + // 1049, + // 1048, + // 1047, + // 1046, + // 1045, + // 1044, + // 1043, + // 1042, + // 1041, + // 1040, + // 1039, + // 1038, + // 1037, + // 1036, + // 1035, + // 1034, + // 1033, + // 1032, + // 1031, + // 1030, + // 1029, + // 1028, + // 1027, // 1026, 1025, 1024, @@ -237,7 +416,7 @@ static const int included_patches[] = { // 1022, // 1021, // 1020, - // 1019, + 1019, // 1018, // 1017, // 1016, @@ -294,7 +473,7 @@ static const int included_patches[] = { // 965, // 964, // 963, - // 962, + 962, // 961, // 960, // 959, @@ -650,7 +829,7 @@ static const int included_patches[] = { // 609, // 608, 607, - // 606, + 606, 605, // 604, // 603, @@ -659,30 +838,30 @@ static const int included_patches[] = { // 600, // 599, // 598, - // 597, + 597, // 596, - // 595, + 595, // 594, // 593, // 592, // 591, - // 590, + 590, // 589, // 588, // 587, // 586, // 585, - // 584, + 584, // 583, // 582, // 581, - // 580, - // 579, + 580, + 579, // 578, // 577, // 576, // 575, - // 574, + 574, // 573, // 572, 571, @@ -691,7 +870,7 @@ static const int included_patches[] = { // 568, // 567, // 566, - // 565, + 565, // 564, // 563, // 562, @@ -720,7 +899,7 @@ static const int included_patches[] = { // 539, // 538, // 537, - // 536, + 536, // 535, // 534, // 533, @@ -739,7 +918,7 @@ static const int included_patches[] = { // 520, // 519, 518, - // 517, + 517, // 516, // 515, // 514, @@ -772,7 +951,7 @@ static const int included_patches[] = { 487, 486, 485, - // 484, + 484, 483, 482, // 481, @@ -836,7 +1015,7 @@ static const int included_patches[] = { // 423, // 422, // 421, - // 420, + 420, // 419, // 418, // 417, @@ -851,12 +1030,12 @@ static const int included_patches[] = { 408, 407, // 406, - // 405 NA - // 404, + 405, + 404, // 403, // 402, // 401, - // 400 NA + 400, // 399, // 398, // 397, @@ -877,7 +1056,7 @@ static const int included_patches[] = { // 382, // 381, // 380, - // 379, + 379, 378, 377, 376, @@ -936,7 +1115,7 @@ static const int included_patches[] = { // 323, 322, // 321, - // 320, + 320, 319, // 318, // 317, @@ -946,18 +1125,18 @@ static const int included_patches[] = { // 313, // 312, 311, - // 310, - // 309, + 310, + 309, 308, 307, 306, 305, // 304, // 303, - // 302 NA + 302, // 301, 300, - // 299, + 299, 298, 297, // 296, @@ -968,38 +1147,38 @@ static const int included_patches[] = { 291, 290, 289, - // 288 NA + 288, 287, // 286, - // 285 NA - // 284 NA + 285, + 284, 283, 282, - // 281 NA + 281, 280, - // 279 NA - // 278 NA - // 277 NA - // 276 NA + 279, + 278, + 277, + 276, 275, 274, - // 273 NA - // 272 NA - // 271 NA - // 270 NA - // 269 NA - // 268 NA - // 267 NA + 273, + 272, + 271, + 270, + 269, + 268, + 267, 266, // 265, // 264, // 263, // 262, // 261, - // 260 NA + 260, 259, 258, - // 257 NA + 257, // 256, // 255, // 254, @@ -1007,45 +1186,45 @@ static const int included_patches[] = { // 252, // 251, 250, - // 249 NA - // 248 NA + 249, + 248, 247, - // 246 NA + 246, 245, - // 244 NA + 244, 243, 242, - // 241 NA - // 240 NA - // 239 NA + 241, + 240, + 239, // 238, 237, // 236, 235, // 234, // 233, - // 232 NA + 232, // 231, // 230, 229, // 228, - // 227, + 227, 226, // 225, 224, 223, // 222, - // 221 NA + 221, // 220, 219, 218, - // 217 NA + 217, // 216, - // 215 NA + 215, // 214, - // 213 NA + 213, // 212, - // 211 NA + 211, // 210, 209, 208, @@ -1053,49 +1232,49 @@ static const int included_patches[] = { 206, 205, // 204, - // 203 NA + 203, // 202, // 201, // 200, - // 199 NA + 199, // 198, // 197, 196, 195, 194, - // 193 NA - // 192 NA - // 191 NA + 193, + 192, + 191, 190, 189, 188, - // 187 NA + 187, 186, // 185, // 184, - // 183 NA + 183, 182, 181, - // 180 NA + 180, 179, 178, 177, 176, // 175, 174, - // 173 NA + 173, 172, - // 171 NA - // 170 NA - // 169 NA + 171, + 170, + 169, 168, 167, - // 166 NA + 166, 165, 164, - // 163 NA - // 162 NA - // 161 NA + 163, + 162, + 161, // 160, 159, 158, @@ -1104,21 +1283,21 @@ static const int included_patches[] = { 155, // 154, // 153, - // 152 NA + 152, // 151, 150, 149, 148, 147, 146, - // 145 NA - // 144 NA + 145, + 144, 143, 142, - // 141 NA + 141, 140, - // 139 NA - // 138 NA + 139, + 138, 137, 136, 135, @@ -1126,137 +1305,137 @@ static const int included_patches[] = { 133, 132, 131, - // 130 NA - // 129 NA + 130, + 129, 128, 127, 126, 125, 124, - // 123 NA - // 122 NA + 123, + 122, 121, - // 120 NA + 120, 119, 118, - // 117 NA + 117, 116, - // 115 NA - // 114 NA - // 113 NA + 115, + 114, + 113, 112, 111, 110, - // 109 NA - // 108 NA - // 107 NA + 109, + 108, + 107, 106, - // 105 NA + 105, 104, - // 103 NA + 103, 102, 101, 100, 99, - // 98 NA - // 97 NA + 98, + 97, 96, - // 95 NA - // 94 NA - // 93 NA + 95, + 94, + 93, 92, 91, 90, - // 89 NA + 89, 88, - // 87 NA + 87, 86, 85, 84, 83, - // 82 NA + 82, 81, - // 80 NA + 80, 79, 78, - // 77 NA - // 76 NA + 77, + 76, 75, 74, 73, - // 72 NA - // 71 NA - // 70 NA + 72, + 71, + 70, 69, 68, - // 67 NA + 67, 66, - // 65 NA + 65, 64, - // 63 NA + 63, 62, - // 61 NA + 61, 60, - // 59 NA + 59, 58, 57, 56, - // 55 NA - // 54 NA + 55, + 54, 53, 52, - // 51 NA - // 50 NA + 51, + 50, 49, - // 48 NA + 48, 47, 46, - // 45 NA + 45, 44, 43, 42, 41, 40, - // 39 NA + 39, 38, 37, - // 36 NA + 36, 35, 34, 33, 32, 31, - // 30 NA - // 29 NA - // 28 NA - // 27 NA + 30, + 29, + 28, + 27, 26, 25, - // 24 NA + 24, 23, - // 22 NA - // 21 NA + 22, + 21, 20, 19, - // 18 NA + 18, 17, - // 16 NA - // 15 NA - // 14 NA - // 13 NA + 16, + 15, + 14, + 13, 12, - // 11 NA - // 10 NA - // 9 NA + 11, + 10, + 9, 8, - // 7 NA + 7, 6, - // 5 NA + 5, 4, 3, 2, 1, - 0 + 0, }; // clang-format on -- cgit From 341102fe9f26e3f4207f14e097348d3bd6831b0d Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Wed, 27 Dec 2017 13:00:58 +0100 Subject: health.vim: remove :CheckHealth command For back-compat, :CheckHealth runs :checkhealth. But don't define :CheckHealth explicitly, it adds noise to wildmenu completion. Completion of healthchecks doesn't yet work with :checkhealth, this is a regression but it needs to be implemented for :checkhealth rather than keeping :CheckHealth around. --- runtime/autoload/provider/node.vim | 2 +- runtime/plugin/health.vim | 9 +-------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/runtime/autoload/provider/node.vim b/runtime/autoload/provider/node.vim index adcc926074..3dde18022e 100644 --- a/runtime/autoload/provider/node.vim +++ b/runtime/autoload/provider/node.vim @@ -114,7 +114,7 @@ let s:err = '' let s:prog = provider#node#Detect() if empty(s:prog) - let s:err = 'Cannot find the "neovim" node package. Try :CheckHealth' + let s:err = 'Cannot find the "neovim" node package. Try :checkhealth' endif call remote#host#RegisterPlugin('node-provider', 'node', []) diff --git a/runtime/plugin/health.vim b/runtime/plugin/health.vim index e3482cb0fe..66ae8fb239 100644 --- a/runtime/plugin/health.vim +++ b/runtime/plugin/health.vim @@ -1,8 +1 @@ -function! s:complete(lead, _line, _pos) abort - return sort(filter(map(globpath(&runtimepath, 'autoload/health/*', 1, 1), - \ 'fnamemodify(v:val, ":t:r")'), - \ 'empty(a:lead) || v:val[:strlen(a:lead)-1] ==# a:lead')) -endfunction - -command! -nargs=* -complete=customlist,s:complete CheckHealth - \ call health#check([]) +autocmd CmdUndefined CheckHealth checkhealth -- cgit From 2f3e00171749c5fddc2b76d080d2bb26f1765405 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Wed, 27 Dec 2017 13:53:01 +0100 Subject: health.vim: minor refactor (group related logic) --- runtime/autoload/health/provider.vim | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/runtime/autoload/health/provider.vim b/runtime/autoload/health/provider.vim index 39e592c471..83dee043a0 100644 --- a/runtime/autoload/health/provider.vim +++ b/runtime/autoload/health/provider.vim @@ -370,10 +370,11 @@ function! s:check_python(version) abort let python_bin = '' endif - " Check if $VIRTUAL_ENV is active - let virtualenv_inactive = 0 + " Check if $VIRTUAL_ENV is active. if exists('$VIRTUAL_ENV') + let virtualenv_inactive = 0 + if !empty(pyenv) let pyenv_prefix = resolve(s:trim(s:system([pyenv, 'prefix']))) if $VIRTUAL_ENV != pyenv_prefix @@ -382,13 +383,13 @@ function! s:check_python(version) abort elseif !empty(pyname) && exepath(pyname) !~# '^'.$VIRTUAL_ENV.'/' let virtualenv_inactive = 1 endif - endif - if virtualenv_inactive - call health#report_warn( - \ '$VIRTUAL_ENV exists but appears to be inactive. ' - \ . 'This could lead to unexpected results.', - \ [ 'If you are using Zsh, see: http://vi.stackexchange.com/a/7654/5229' ]) + if virtualenv_inactive + call health#report_warn( + \ '$VIRTUAL_ENV exists but appears to be inactive. ' + \ . 'This could lead to unexpected results.', + \ [ 'If you are using Zsh, see: http://vi.stackexchange.com/a/7654' ]) + endif endif " Diagnostic output -- cgit From 0446d4d6916d27041de5ac24ba0c741ae4ad5a39 Mon Sep 17 00:00:00 2001 From: Gabriel Holodak Date: Fri, 10 Nov 2017 23:27:00 -0500 Subject: Highlight backspaced characters --- runtime/autoload/man.vim | 57 +++++++++++++++++++++++++++++++++++++++++++----- runtime/syntax/man.vim | 12 ++++++++++ 2 files changed, 64 insertions(+), 5 deletions(-) diff --git a/runtime/autoload/man.vim b/runtime/autoload/man.vim index dd71ede680..3ea0b734c0 100644 --- a/runtime/autoload/man.vim +++ b/runtime/autoload/man.vim @@ -148,7 +148,8 @@ function! s:get_page(path) abort let manwidth = empty($MANWIDTH) ? winwidth(0) : $MANWIDTH " Force MANPAGER=cat to ensure Vim is not recursively invoked (by man-db). " http://comments.gmane.org/gmane.editors.vim.devel/29085 - let cmd = ['env', 'MANPAGER=cat', 'MANWIDTH='.manwidth, 'man'] + " Set MAN_KEEP_FORMATTING so Debian man doesn't discard backspaces. + let cmd = ['env', 'MANPAGER=cat', 'MANWIDTH='.manwidth, 'MAN_KEEP_FORMATTING=1', 'man'] return s:system(cmd + (s:localfile_arg ? ['-l', a:path] : [a:path])) endfunction @@ -157,11 +158,10 @@ function! s:put_page(page) abort setlocal noreadonly silent keepjumps %delete _ silent put =a:page - " Remove all backspaced/escape characters. - execute 'silent keeppatterns keepjumps %substitute,.\b\|\e\[\d\+m,,e'.(&gdefault?'':'g') while getline(1) =~# '^\s*$' silent keepjumps 1delete _ endwhile + call man#highlight_backspaced_text() setlocal filetype=man endfunction @@ -370,13 +370,12 @@ function! s:format_candidate(path, psect) abort endfunction function! man#init_pager() abort - " Remove all backspaced/escape characters. - execute 'silent keeppatterns keepjumps %substitute,.\b\|\e\[\d\+m,,e'.(&gdefault?'':'g') if getline(1) =~# '^\s*$' silent keepjumps 1delete _ else keepjumps 1 endif + call man#highlight_backspaced_text() " This is not perfect. See `man glDrawArraysInstanced`. Since the title is " all caps it is impossible to tell what the original capitilization was. let ref = substitute(matchstr(getline(1), '^[^)]\+)'), ' ', '_', 'g') @@ -388,4 +387,52 @@ function! man#init_pager() abort execute 'silent file man://'.fnameescape(ref) endfunction +function! man#highlight_backspaced_text() abort + let l:modifiable = &modifiable + set modifiable + + let l:lines = getline(1, line('$')) + call map(l:lines, function('s:highlight_backspaced_line')) + call setline(1, l:lines) + + let &modifiable = l:modifiable +endfunction + +" This pattern is for "overstruck" text containing backspaces. It matches bold +" text first, so a word beginning with "_^H_" is bold and text such as +" "_^Hf_^Ho_^Ho_^H__^Hb_^Ha_^Hr" is entirely underlined. +" +" Bolded text can also be mixed with whitespace as a performance tweak, since +" it's visually identical. +let s:backspace_pattern = '\v%((.)\b\1\s*)+|%(_\b.)+' + +function! s:highlight_backspaced_line(index, val) abort + let l:line = a:val + let l:search_pos = 0 + + while 1 + " Scanning for the next backspace without matching the entire pattern is + " slightly faster + let l:match_start = stridx(l:line, "\b", l:search_pos) + if l:match_start == -1 + break + endif + + let l:match = matchstrpos(l:line, s:backspace_pattern, l:match_start - 1) + if l:match[0] =~# '^_\b[^_]' + let l:hlgroup = 'manUnderline' + else + let l:hlgroup = 'manBold' + endif + + let l:stripped = substitute(l:match[0], '.\b', '', 'g') + let l:search_pos = l:match[1] + len(l:stripped) + let l:line = strpart(l:line, 0, l:match[1]) . l:stripped . strpart(l:line, l:match[2]) + + call nvim_buf_add_highlight(0, -1, l:hlgroup, a:index, l:match[1], l:search_pos) + endwhile + + return l:line +endfunction + call s:init() diff --git a/runtime/syntax/man.vim b/runtime/syntax/man.vim index 0975b160ae..9eb613169c 100644 --- a/runtime/syntax/man.vim +++ b/runtime/syntax/man.vim @@ -18,6 +18,18 @@ highlight default link manOptionDesc Constant highlight default link manReference PreProc highlight default link manSubHeading Function +function! s:init_highlight_groups() + highlight default manUnderline cterm=underline gui=underline + highlight default manBold cterm=bold gui=bold +endfunction + +augroup man_init_highlight_groups + autocmd! + autocmd ColorScheme * call s:init_highlight_groups() +augroup END + +call s:init_highlight_groups() + if &filetype != 'man' " May have been included by some other filetype. finish -- cgit From c28ce5f6191d233228d1688de300727e1ae42831 Mon Sep 17 00:00:00 2001 From: Gabriel Holodak Date: Mon, 20 Nov 2017 00:19:08 -0500 Subject: Switch to processing in Lua --- runtime/autoload/man.vim | 42 ++------------------------- runtime/lua/man.lua | 75 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 40 deletions(-) create mode 100644 runtime/lua/man.lua diff --git a/runtime/autoload/man.vim b/runtime/autoload/man.vim index 3ea0b734c0..12c1bf3c86 100644 --- a/runtime/autoload/man.vim +++ b/runtime/autoload/man.vim @@ -391,48 +391,10 @@ function! man#highlight_backspaced_text() abort let l:modifiable = &modifiable set modifiable - let l:lines = getline(1, line('$')) - call map(l:lines, function('s:highlight_backspaced_line')) - call setline(1, l:lines) + lua man = require("man") + luado return man.highlight_backspaced(line, linenr) let &modifiable = l:modifiable endfunction -" This pattern is for "overstruck" text containing backspaces. It matches bold -" text first, so a word beginning with "_^H_" is bold and text such as -" "_^Hf_^Ho_^Ho_^H__^Hb_^Ha_^Hr" is entirely underlined. -" -" Bolded text can also be mixed with whitespace as a performance tweak, since -" it's visually identical. -let s:backspace_pattern = '\v%((.)\b\1\s*)+|%(_\b.)+' - -function! s:highlight_backspaced_line(index, val) abort - let l:line = a:val - let l:search_pos = 0 - - while 1 - " Scanning for the next backspace without matching the entire pattern is - " slightly faster - let l:match_start = stridx(l:line, "\b", l:search_pos) - if l:match_start == -1 - break - endif - - let l:match = matchstrpos(l:line, s:backspace_pattern, l:match_start - 1) - if l:match[0] =~# '^_\b[^_]' - let l:hlgroup = 'manUnderline' - else - let l:hlgroup = 'manBold' - endif - - let l:stripped = substitute(l:match[0], '.\b', '', 'g') - let l:search_pos = l:match[1] + len(l:stripped) - let l:line = strpart(l:line, 0, l:match[1]) . l:stripped . strpart(l:line, l:match[2]) - - call nvim_buf_add_highlight(0, -1, l:hlgroup, a:index, l:match[1], l:search_pos) - endwhile - - return l:line -endfunction - call s:init() diff --git a/runtime/lua/man.lua b/runtime/lua/man.lua new file mode 100644 index 0000000000..63c2c14480 --- /dev/null +++ b/runtime/lua/man.lua @@ -0,0 +1,75 @@ +local function highlight_backspaced(line, linenr) + local chars = {} + local prev_char = '' + local overstrike = false + local hls = {} -- Store highlight groups as { attr, start, end } + local NONE, BOLD, UNDERLINE = 0, 1, 2 + local attr = NONE + local byte = 0 -- byte offset + + -- Break input into UTF8 characters + for char in line:gmatch("[^\128-\191][\128-\191]*") do + if overstrike then + local last_hl = hls[#hls] + if char == prev_char then + if char == '_' and attr == UNDERLINE and last_hl and last_hl[3] == byte then + -- This underscore is in the middle of an underlined word + attr = UNDERLINE + else + attr = BOLD + end + elseif prev_char == '_' then + -- char is underlined + attr = UNDERLINE + elseif prev_char == '+' and char == 'o' then + -- bullet (overstrike text '+^Ho') + attr = BOLD + char = [[·]] + elseif prev_char == [[·]] and char == 'o' then + -- bullet (additional handling for '+^H+^Ho^Ho') + attr = BOLD + char = [[·]] + else + -- use plain char + attr = NONE + end + + -- Grow the previous highlight group if possible + if last_hl and last_hl[1] == attr and last_hl[3] == byte then + last_hl[3] = byte + #char + else + hls[#hls + 1] = {attr, byte, byte + #char} + end + + overstrike = false + prev_char = '' + byte = byte + #char + chars[#chars + 1] = char + elseif char == "\b" then + overstrike = true + prev_char = chars[#chars] + byte = byte - #prev_char + chars[#chars] = nil + else + byte = byte + #char + chars[#chars + 1] = char + end + end + + for i, hl in ipairs(hls) do + if hl[1] ~= NONE then + vim.api.nvim_buf_add_highlight( + 0, + -1, + hl[1] == BOLD and "manBold" or "manUnderline", + linenr - 1, + hl[2], + hl[3] + ) + end + end + + return table.concat(chars, '') +end + +return { highlight_backspaced = highlight_backspaced } -- cgit From 6740c94562cc1b271a6ea2458ebb847a6e3665e2 Mon Sep 17 00:00:00 2001 From: Gabriel Holodak Date: Thu, 30 Nov 2017 22:15:39 -0500 Subject: Add support for escape sequences --- runtime/autoload/man.vim | 8 ++--- runtime/lua/man.lua | 76 ++++++++++++++++++++++++++++++++++++++++++++---- runtime/syntax/man.vim | 1 + 3 files changed, 76 insertions(+), 9 deletions(-) diff --git a/runtime/autoload/man.vim b/runtime/autoload/man.vim index 12c1bf3c86..0a502be4c7 100644 --- a/runtime/autoload/man.vim +++ b/runtime/autoload/man.vim @@ -161,7 +161,7 @@ function! s:put_page(page) abort while getline(1) =~# '^\s*$' silent keepjumps 1delete _ endwhile - call man#highlight_backspaced_text() + call man#highlight_formatted_text() setlocal filetype=man endfunction @@ -375,7 +375,7 @@ function! man#init_pager() abort else keepjumps 1 endif - call man#highlight_backspaced_text() + call man#highlight_formatted_text() " This is not perfect. See `man glDrawArraysInstanced`. Since the title is " all caps it is impossible to tell what the original capitilization was. let ref = substitute(matchstr(getline(1), '^[^)]\+)'), ' ', '_', 'g') @@ -387,12 +387,12 @@ function! man#init_pager() abort execute 'silent file man://'.fnameescape(ref) endfunction -function! man#highlight_backspaced_text() abort +function! man#highlight_formatted_text() abort let l:modifiable = &modifiable set modifiable lua man = require("man") - luado return man.highlight_backspaced(line, linenr) + luado return man.highlight_formatted(line, linenr) let &modifiable = l:modifiable endfunction diff --git a/runtime/lua/man.lua b/runtime/lua/man.lua index 63c2c14480..c3d7df2a2a 100644 --- a/runtime/lua/man.lua +++ b/runtime/lua/man.lua @@ -1,12 +1,60 @@ -local function highlight_backspaced(line, linenr) +local function highlight_formatted(line, linenr) local chars = {} local prev_char = '' - local overstrike = false + local overstrike, escape = false, false local hls = {} -- Store highlight groups as { attr, start, end } - local NONE, BOLD, UNDERLINE = 0, 1, 2 + local NONE, BOLD, UNDERLINE, ITALIC = 0, 1, 2, 3 + local hl_groups = {[BOLD]="manBold", [UNDERLINE]="manUnderline", [ITALIC]="manItalic"} local attr = NONE local byte = 0 -- byte offset + local function end_attr_hl(attr) + for i, hl in ipairs(hls) do + if hl[1] == attr and hl[3] == -1 then + hl[3] = byte + hls[i] = hl + end + end + end + + local function add_attr_hl(code) + local on = true + if code == 0 then + attr = NONE + on = false + elseif code == 1 then + attr = BOLD + elseif code == 21 or code == 22 then + attr = BOLD + on = false + elseif code == 3 then + attr = ITALIC + elseif code == 23 then + attr = ITALIC + on = false + elseif code == 4 then + attr = UNDERLINE + elseif code == 24 then + attr = UNDERLINE + on = false + else + attr = NONE + return + end + + if on then + hls[#hls + 1] = {attr, byte, -1} + else + if attr == NONE then + for a, _ in pairs(hl_groups) do + end_attr_hl(a) + end + else + end_attr_hl(attr) + end + end + end + -- Break input into UTF8 characters for char in line:gmatch("[^\128-\191][\128-\191]*") do if overstrike then @@ -45,6 +93,24 @@ local function highlight_backspaced(line, linenr) prev_char = '' byte = byte + #char chars[#chars + 1] = char + elseif escape then + -- Use prev_char to store the escape sequence + prev_char = prev_char .. char + local sgr = prev_char:match("^%[([\020-\063]*)m$") + if sgr then + local match = '' + while sgr and #sgr > 0 do + match, sgr = sgr:match("^(%d*);?(.*)") + add_attr_hl(match + 0) -- coerce to number + end + escape = false + elseif not prev_char:match("^%[[\020-\063]*$") then + -- Stop looking if this isn't a partial CSI sequence + escape = false + end + elseif char == "\027" then + escape = true + prev_char = '' elseif char == "\b" then overstrike = true prev_char = chars[#chars] @@ -61,7 +127,7 @@ local function highlight_backspaced(line, linenr) vim.api.nvim_buf_add_highlight( 0, -1, - hl[1] == BOLD and "manBold" or "manUnderline", + hl_groups[hl[1]], linenr - 1, hl[2], hl[3] @@ -72,4 +138,4 @@ local function highlight_backspaced(line, linenr) return table.concat(chars, '') end -return { highlight_backspaced = highlight_backspaced } +return { highlight_formatted = highlight_formatted } diff --git a/runtime/syntax/man.vim b/runtime/syntax/man.vim index 9eb613169c..0544fb29e2 100644 --- a/runtime/syntax/man.vim +++ b/runtime/syntax/man.vim @@ -21,6 +21,7 @@ highlight default link manSubHeading Function function! s:init_highlight_groups() highlight default manUnderline cterm=underline gui=underline highlight default manBold cterm=bold gui=bold + highlight default manItalic cterm=italic gui=italic endfunction augroup man_init_highlight_groups -- cgit From 134c0f0bdb50b16d443b103fc1a81c9ae5c7c017 Mon Sep 17 00:00:00 2001 From: Gabriel Holodak Date: Fri, 1 Dec 2017 01:06:35 -0500 Subject: Add functional tests for man highlighting --- test/functional/plugin/man_spec.lua | 181 ++++++++++++++++++++++++++++++++++++ 1 file changed, 181 insertions(+) create mode 100644 test/functional/plugin/man_spec.lua diff --git a/test/functional/plugin/man_spec.lua b/test/functional/plugin/man_spec.lua new file mode 100644 index 0000000000..53384a9d47 --- /dev/null +++ b/test/functional/plugin/man_spec.lua @@ -0,0 +1,181 @@ +local helpers = require('test.functional.helpers')(after_each) +local plugin_helpers = require('test.functional.plugin.helpers') + +local Screen = require('test.functional.ui.screen') + +local buffer, command, eval = helpers.buffer, helpers.command, helpers.eval + +before_each(function() + plugin_helpers.reset() + helpers.clear() + command('syntax on') + command('set filetype=man') +end) + +describe('In autoload/man.vim', function() + describe('function man#highlight_formatted_text', function() + local screen + + before_each(function() + command('syntax off') -- Ignore syntax groups + screen = Screen.new(52, 5) + screen:attach() + end) + + after_each(function() + screen:detach() + end) + + local function expect(string) + screen:expect(string, + { + b = { bold = true }, + i = { italic = true }, + u = { underline = true }, + bi = { bold = true, italic = true }, + biu = { bold = true, italic = true, underline = true }, + }, + {{ bold = true, foreground = Screen.colors.Blue }}) + end + + local function expect_without_highlights(string) + screen:expect(string, nil, true) + end + + local function insert_lines(...) + buffer('set_lines', 0, 0, 1, false, { ... }) + end + + it('clears backspaces from text', function() + insert_lines( + "this i\bis\bs a\ba test", + "with _\bo_\bv_\be_\br_\bs_\bt_\br_\bu_\bc_\bk text" + ) + + expect_without_highlights([[ + ^this i^His^Hs a^Ha test | + with _^Ho_^Hv_^He_^Hr_^Hs_^Ht_^Hr_^Hu_^Hc_^Hk text | + ~ | + ~ | + | + ]]) + + eval('man#highlight_formatted_text()') + + expect_without_highlights([[ + ^this is a test | + with overstruck text | + ~ | + ~ | + | + ]]) + end) + + it('clears escape sequences from text', function() + insert_lines( + "this \027[1mis \027[3ma \027[4mtest\027[0m", + "\027[4mwith\027[24m \027[4mescaped\027[24m \027[4mtext\027[24m" + ) + + expect_without_highlights([[ + ^this ^[[1mis ^[[3ma ^[[4mtest^[[0m | + ^[[4mwith^[[24m ^[[4mescaped^[[24m ^[[4mtext^[[24m | + ~ | + ~ | + | + ]]) + + eval('man#highlight_formatted_text()') + + expect_without_highlights([[ + ^this is a test | + with escaped text | + ~ | + ~ | + | + ]]) + end) + + it('highlights overstruck text', function() + insert_lines( + "this i\bis\bs a\ba test", + "with _\bo_\bv_\be_\br_\bs_\bt_\br_\bu_\bc_\bk text" + ) + eval('man#highlight_formatted_text()') + + expect([[ + ^this {b:is} {b:a} test | + with {u:overstruck} text | + ~ | + ~ | + | + ]]) + end) + + it('highlights escape sequences in text', function() + insert_lines( + "this \027[1mis \027[3ma \027[4mtest\027[0m", + "\027[4mwith\027[24m \027[4mescaped\027[24m \027[4mtext\027[24m" + ) + eval('man#highlight_formatted_text()') + + expect([[ + ^this {b:is }{bi:a }{biu:test} | + {u:with} {u:escaped} {u:text} | + ~ | + ~ | + | + ]]) + end) + + it('highlights multibyte text', function() + insert_lines( + "this i\bis\bs あ\bあ test", + "with _\bö_\bv_\be_\br_\bs_\bt_\br_\bu_\bc_\bk te\027[3mxt¶\027[0m" + ) + eval('man#highlight_formatted_text()') + + expect([[ + ^this {b:is} {b:あ} test | + with {u:överstruck} te{i:xt¶} | + ~ | + ~ | + | + ]]) + end) + + it('highlights underscores based on context', function() + insert_lines( + "_\b_b\bbe\beg\bgi\bin\bns\bs", + "m\bmi\bid\bd_\b_d\bdl\ble\be", + "_\bm_\bi_\bd_\b__\bd_\bl_\be" + ) + eval('man#highlight_formatted_text()') + + expect([[ + {b:^_begins} | + {b:mid_dle} | + {u:mid_dle} | + ~ | + | + ]]) + end) + + it('highlights various bullet formats', function() + insert_lines( + "· ·\b·", + "+\bo", + "+\b+\bo\bo double" + ) + eval('man#highlight_formatted_text()') + + expect([[ + ^· {b:·} | + {b:·} | + {b:·} double | + ~ | + | + ]]) + end) + end) +end) -- cgit From eb44519b5debf740f692bb4ea19ad83b29749484 Mon Sep 17 00:00:00 2001 From: Gabriel Holodak Date: Sun, 24 Dec 2017 12:16:58 -0500 Subject: Address PR comments --- runtime/autoload/man.vim | 14 +--- runtime/lua/man.lua | 87 +++++++++++++-------- runtime/syntax/man.vim | 15 +--- test/functional/plugin/man_spec.lua | 148 +++++++++++++----------------------- 4 files changed, 113 insertions(+), 151 deletions(-) diff --git a/runtime/autoload/man.vim b/runtime/autoload/man.vim index 0a502be4c7..d20b8c05b0 100644 --- a/runtime/autoload/man.vim +++ b/runtime/autoload/man.vim @@ -161,7 +161,7 @@ function! s:put_page(page) abort while getline(1) =~# '^\s*$' silent keepjumps 1delete _ endwhile - call man#highlight_formatted_text() + lua require("man").highlight_man_page() setlocal filetype=man endfunction @@ -375,7 +375,7 @@ function! man#init_pager() abort else keepjumps 1 endif - call man#highlight_formatted_text() + lua require("man").highlight_man_page() " This is not perfect. See `man glDrawArraysInstanced`. Since the title is " all caps it is impossible to tell what the original capitilization was. let ref = substitute(matchstr(getline(1), '^[^)]\+)'), ' ', '_', 'g') @@ -387,14 +387,4 @@ function! man#init_pager() abort execute 'silent file man://'.fnameescape(ref) endfunction -function! man#highlight_formatted_text() abort - let l:modifiable = &modifiable - set modifiable - - lua man = require("man") - luado return man.highlight_formatted(line, linenr) - - let &modifiable = l:modifiable -endfunction - call s:init() diff --git a/runtime/lua/man.lua b/runtime/lua/man.lua index c3d7df2a2a..baa522f343 100644 --- a/runtime/lua/man.lua +++ b/runtime/lua/man.lua @@ -1,8 +1,10 @@ -local function highlight_formatted(line, linenr) +local buf_hls = {} + +local function highlight_line(line, linenr) local chars = {} local prev_char = '' local overstrike, escape = false, false - local hls = {} -- Store highlight groups as { attr, start, end } + local hls = {} -- Store highlight groups as { attr, start, final } local NONE, BOLD, UNDERLINE, ITALIC = 0, 1, 2, 3 local hl_groups = {[BOLD]="manBold", [UNDERLINE]="manUnderline", [ITALIC]="manItalic"} local attr = NONE @@ -10,40 +12,40 @@ local function highlight_formatted(line, linenr) local function end_attr_hl(attr) for i, hl in ipairs(hls) do - if hl[1] == attr and hl[3] == -1 then - hl[3] = byte + if hl.attr == attr and hl.final == -1 then + hl.final = byte hls[i] = hl end end end local function add_attr_hl(code) - local on = true + local continue_hl = true if code == 0 then attr = NONE - on = false + continue_hl = false elseif code == 1 then attr = BOLD - elseif code == 21 or code == 22 then + elseif code == 22 then attr = BOLD - on = false + continue_hl = false elseif code == 3 then attr = ITALIC elseif code == 23 then attr = ITALIC - on = false + continue_hl = false elseif code == 4 then attr = UNDERLINE elseif code == 24 then attr = UNDERLINE - on = false + continue_hl = false else attr = NONE return end - if on then - hls[#hls + 1] = {attr, byte, -1} + if continue_hl then + hls[#hls + 1] = {attr=attr, start=byte, final=-1} else if attr == NONE then for a, _ in pairs(hl_groups) do @@ -55,12 +57,15 @@ local function highlight_formatted(line, linenr) end end - -- Break input into UTF8 characters + -- Break input into UTF8 code points. ASCII code points (from 0x00 to 0x7f) + -- can be represented in one byte. Any code point above that is represented by + -- a leading byte (0xc0 and above) and continuation bytes (0x80 to 0xbf, or + -- decimal 128 to 191). for char in line:gmatch("[^\128-\191][\128-\191]*") do if overstrike then local last_hl = hls[#hls] if char == prev_char then - if char == '_' and attr == UNDERLINE and last_hl and last_hl[3] == byte then + if char == '_' and attr == UNDERLINE and last_hl and last_hl.final == byte then -- This underscore is in the middle of an underlined word attr = UNDERLINE else @@ -72,21 +77,21 @@ local function highlight_formatted(line, linenr) elseif prev_char == '+' and char == 'o' then -- bullet (overstrike text '+^Ho') attr = BOLD - char = [[·]] - elseif prev_char == [[·]] and char == 'o' then + char = '·' + elseif prev_char == '·' and char == 'o' then -- bullet (additional handling for '+^H+^Ho^Ho') attr = BOLD - char = [[·]] + char = '·' else -- use plain char attr = NONE end -- Grow the previous highlight group if possible - if last_hl and last_hl[1] == attr and last_hl[3] == byte then - last_hl[3] = byte + #char + if last_hl and last_hl.attr == attr and last_hl.final == byte then + last_hl.final = byte + #char else - hls[#hls + 1] = {attr, byte, byte + #char} + hls[#hls + 1] = {attr=attr, start=byte, final=byte + #char} end overstrike = false @@ -96,15 +101,19 @@ local function highlight_formatted(line, linenr) elseif escape then -- Use prev_char to store the escape sequence prev_char = prev_char .. char - local sgr = prev_char:match("^%[([\020-\063]*)m$") + -- We only want to match against SGR sequences, which consist of ESC + -- followed by '[', then a series of parameter and intermediate bytes in + -- the range 0x20 - 0x3f, then 'm'. (See ECMA-48, sections 5.4 & 8.3.117) + local sgr = prev_char:match("^%[([\032-\063]*)m$") if sgr then local match = '' while sgr and #sgr > 0 do + -- Match against SGR parameters, which may be separated by ';' match, sgr = sgr:match("^(%d*);?(.*)") add_attr_hl(match + 0) -- coerce to number end escape = false - elseif not prev_char:match("^%[[\020-\063]*$") then + elseif not prev_char:match("^%[[\032-\063]*$") then -- Stop looking if this isn't a partial CSI sequence escape = false end @@ -122,20 +131,38 @@ local function highlight_formatted(line, linenr) end end - for i, hl in ipairs(hls) do - if hl[1] ~= NONE then - vim.api.nvim_buf_add_highlight( + for _, hl in ipairs(hls) do + if hl.attr ~= NONE then + buf_hls[#buf_hls + 1] = { 0, -1, - hl_groups[hl[1]], + hl_groups[hl.attr], linenr - 1, - hl[2], - hl[3] - ) + hl.start, + hl.final + } end end return table.concat(chars, '') end -return { highlight_formatted = highlight_formatted } +local function highlight_man_page() + local mod = vim.api.nvim_eval("&modifiable") + vim.api.nvim_command("set modifiable") + + local lines = vim.api.nvim_buf_get_lines(0, 0, -1, false) + for i, line in ipairs(lines) do + lines[i] = highlight_line(line, i) + end + vim.api.nvim_buf_set_lines(0, 0, -1, false, lines) + + for _, args in ipairs(buf_hls) do + vim.api.nvim_buf_add_highlight(unpack(args)) + end + buf_hls = {} + + vim.api.nvim_command("let &modifiable = "..mod) +end + +return { highlight_man_page = highlight_man_page } diff --git a/runtime/syntax/man.vim b/runtime/syntax/man.vim index 0544fb29e2..b8e605cb9a 100644 --- a/runtime/syntax/man.vim +++ b/runtime/syntax/man.vim @@ -18,18 +18,9 @@ highlight default link manOptionDesc Constant highlight default link manReference PreProc highlight default link manSubHeading Function -function! s:init_highlight_groups() - highlight default manUnderline cterm=underline gui=underline - highlight default manBold cterm=bold gui=bold - highlight default manItalic cterm=italic gui=italic -endfunction - -augroup man_init_highlight_groups - autocmd! - autocmd ColorScheme * call s:init_highlight_groups() -augroup END - -call s:init_highlight_groups() +highlight default manUnderline cterm=underline gui=underline +highlight default manBold cterm=bold gui=bold +highlight default manItalic cterm=italic gui=italic if &filetype != 'man' " May have been included by some other filetype. diff --git a/test/functional/plugin/man_spec.lua b/test/functional/plugin/man_spec.lua index 53384a9d47..479fd6e7a5 100644 --- a/test/functional/plugin/man_spec.lua +++ b/test/functional/plugin/man_spec.lua @@ -3,7 +3,7 @@ local plugin_helpers = require('test.functional.plugin.helpers') local Screen = require('test.functional.ui.screen') -local buffer, command, eval = helpers.buffer, helpers.command, helpers.eval +local command, eval, rawfeed = helpers.command, helpers.eval, helpers.rawfeed before_each(function() plugin_helpers.reset() @@ -19,107 +19,64 @@ describe('In autoload/man.vim', function() before_each(function() command('syntax off') -- Ignore syntax groups screen = Screen.new(52, 5) - screen:attach() - end) - - after_each(function() - screen:detach() - end) - - local function expect(string) - screen:expect(string, - { + screen:set_default_attr_ids({ b = { bold = true }, i = { italic = true }, u = { underline = true }, bi = { bold = true, italic = true }, biu = { bold = true, italic = true, underline = true }, - }, - {{ bold = true, foreground = Screen.colors.Blue }}) - end - - local function expect_without_highlights(string) - screen:expect(string, nil, true) - end - - local function insert_lines(...) - buffer('set_lines', 0, 0, 1, false, { ... }) - end - - it('clears backspaces from text', function() - insert_lines( - "this i\bis\bs a\ba test", - "with _\bo_\bv_\be_\br_\bs_\bt_\br_\bu_\bc_\bk text" - ) - - expect_without_highlights([[ - ^this i^His^Hs a^Ha test | - with _^Ho_^Hv_^He_^Hr_^Hs_^Ht_^Hr_^Hu_^Hc_^Hk text | - ~ | - ~ | - | - ]]) - - eval('man#highlight_formatted_text()') + }) + screen:set_default_attr_ignore({ + { foreground = Screen.colors.Blue }, -- control chars + { bold = true, foreground = Screen.colors.Blue } -- empty line '~'s + }) + screen:attach() + end) - expect_without_highlights([[ - ^this is a test | - with overstruck text | - ~ | - ~ | - | - ]]) + after_each(function() + screen:detach() end) - it('clears escape sequences from text', function() - insert_lines( - "this \027[1mis \027[3ma \027[4mtest\027[0m", - "\027[4mwith\027[24m \027[4mescaped\027[24m \027[4mtext\027[24m" - ) + it('clears backspaces from text and adds highlights', function() + rawfeed([[ + ithis iiss aa test + with _o_v_e_r_s_t_r_u_c_k text]]) - expect_without_highlights([[ - ^this ^[[1mis ^[[3ma ^[[4mtest^[[0m | - ^[[4mwith^[[24m ^[[4mescaped^[[24m ^[[4mtext^[[24m | + screen:expect([[ + this i^His^Hs a^Ha test | + with _^Ho_^Hv_^He_^Hr_^Hs_^Ht_^Hr_^Hu_^Hc_^Hk tex^t | ~ | ~ | | ]]) - eval('man#highlight_formatted_text()') + eval('man#init_pager()') - expect_without_highlights([[ - ^this is a test | - with escaped text | + screen:expect([[ + ^this {b:is} {b:a} test | + with {u:overstruck} text | ~ | ~ | | ]]) end) - it('highlights overstruck text', function() - insert_lines( - "this i\bis\bs a\ba test", - "with _\bo_\bv_\be_\br_\bs_\bt_\br_\bu_\bc_\bk text" - ) - eval('man#highlight_formatted_text()') + it('clears escape sequences from text and adds highlights', function() + rawfeed([[ + ithis [1mis [3ma [4mtest[0m + [4mwith[24m [4mescaped[24m [4mtext[24m]]) - expect([[ - ^this {b:is} {b:a} test | - with {u:overstruck} text | + screen:expect([[ + this ^[[1mis ^[[3ma ^[[4mtest^[[0m | + ^[[4mwith^[[24m ^[[4mescaped^[[24m ^[[4mtext^[[24^m | ~ | ~ | | ]]) - end) - it('highlights escape sequences in text', function() - insert_lines( - "this \027[1mis \027[3ma \027[4mtest\027[0m", - "\027[4mwith\027[24m \027[4mescaped\027[24m \027[4mtext\027[24m" - ) - eval('man#highlight_formatted_text()') + eval('man#init_pager()') - expect([[ + screen:expect([[ ^this {b:is }{bi:a }{biu:test} | {u:with} {u:escaped} {u:text} | ~ | @@ -129,15 +86,14 @@ describe('In autoload/man.vim', function() end) it('highlights multibyte text', function() - insert_lines( - "this i\bis\bs あ\bあ test", - "with _\bö_\bv_\be_\br_\bs_\bt_\br_\bu_\bc_\bk te\027[3mxt¶\027[0m" - ) - eval('man#highlight_formatted_text()') + rawfeed([[ + ithis iiss ああ test + with _ö_v_e_r_s_t_r_u_̃_c_k te[3mxt¶[0m]]) + eval('man#init_pager()') - expect([[ + screen:expect([[ ^this {b:is} {b:あ} test | - with {u:överstruck} te{i:xt¶} | + with {u:överstrũck} te{i:xt¶} | ~ | ~ | | @@ -145,14 +101,13 @@ describe('In autoload/man.vim', function() end) it('highlights underscores based on context', function() - insert_lines( - "_\b_b\bbe\beg\bgi\bin\bns\bs", - "m\bmi\bid\bd_\b_d\bdl\ble\be", - "_\bm_\bi_\bd_\b__\bd_\bl_\be" - ) - eval('man#highlight_formatted_text()') - - expect([[ + rawfeed([[ + i__bbeeggiinnss + mmiidd__ddllee + _m_i_d___d_l_e]]) + eval('man#init_pager()') + + screen:expect([[ {b:^_begins} | {b:mid_dle} | {u:mid_dle} | @@ -162,14 +117,13 @@ describe('In autoload/man.vim', function() end) it('highlights various bullet formats', function() - insert_lines( - "· ·\b·", - "+\bo", - "+\b+\bo\bo double" - ) - eval('man#highlight_formatted_text()') - - expect([[ + rawfeed([[ + i· ·· + +o + ++oo double]]) + eval('man#init_pager()') + + screen:expect([[ ^· {b:·} | {b:·} | {b:·} double | -- cgit From 2c436b33621a3064aae94e88db90095ae69aabc8 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Fri, 29 Dec 2017 16:56:14 +0100 Subject: Fix TabClose autocommand via close_windows Fixes https://github.com/neovim/neovim/issues/7781 --- src/nvim/window.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/nvim/window.c b/src/nvim/window.c index 5e85a9bede..bbdf40b874 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -1741,6 +1741,11 @@ void close_windows(buf_T *buf, int keep_curwin) } else wp = wp->w_next; } + if (count != tabpage_index(NULL)) { + char_u prev_idx[NUMBUFLEN]; + sprintf((char *)prev_idx, "%i", tabpage_index(curtab)); + apply_autocmds(EVENT_TABCLOSED, prev_idx, prev_idx, false, curbuf); + } /* Also check windows in other tab pages. */ for (tp = first_tabpage; tp != NULL; tp = nexttp) { @@ -1757,15 +1762,16 @@ void close_windows(buf_T *buf, int keep_curwin) break; } } + if (count != tabpage_index(NULL)) { + char_u prev_idx[NUMBUFLEN]; + sprintf((char *)prev_idx, "%i", tabpage_index(tp)); + apply_autocmds(EVENT_TABCLOSED, prev_idx, prev_idx, false, curbuf); + } } } --RedrawingDisabled; - if (count != tabpage_index(NULL)) { - apply_autocmds(EVENT_TABCLOSED, NULL, NULL, false, curbuf); - } - redraw_tabline = true; if (h != tabline_height()) { shell_new_rows(); -- cgit From 49f4358b0aced154565ed4cfe86a136853cc1829 Mon Sep 17 00:00:00 2001 From: Issam Maghni Date: Mon, 18 Dec 2017 19:53:14 -0500 Subject: third-party: update deps #7746 - Latest commit from LuaJIT 2.0.5 --- third-party/CMakeLists.txt | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/third-party/CMakeLists.txt b/third-party/CMakeLists.txt index 66f921ffcc..9bfcee4ed4 100644 --- a/third-party/CMakeLists.txt +++ b/third-party/CMakeLists.txt @@ -90,18 +90,18 @@ include(ExternalProject) set(LIBUV_URL https://github.com/libuv/libuv/archive/v1.12.0.tar.gz) set(LIBUV_SHA256 41ce914a88da21d3b07a76023beca57576ca5b376c6ac440c80bc581cbca1250) -set(MSGPACK_URL https://github.com/msgpack/msgpack-c/archive/cpp-2.1.3.tar.gz) -set(MSGPACK_SHA256 42ff5c213fd24bd4388c45c1f21d84b476678ce6366ea4d4f4086618a1d2cd23) +set(MSGPACK_URL https://github.com/msgpack/msgpack-c/releases/download/cpp-2.1.5/msgpack-2.1.5.tar.gz) +set(MSGPACK_SHA256 6126375af9b204611b9d9f154929f4f747e4599e6ae8443b337915dcf2899d2b) -set(LUAJIT_URL https://github.com/LuaJIT/LuaJIT/archive/82151a4514e6538086f3f5e01cb8d4b22287b14f.tar.gz) -set(LUAJIT_SHA256 8bc4e96ebab74e12ab84e751360e864714289bb089b51b6f396fa9a97df69798) +set(LUAJIT_URL https://github.com/LuaJIT/LuaJIT/archive/7dbf0b05f1228c1c719866db5e5f3d58f87f74c8.tar.gz) +set(LUAJIT_SHA256 cbae019b5e396164eb5f0d07777b55cc03931bb944f83c61a010c053c9f5fd5b) set(LUA_URL https://www.lua.org/ftp/lua-5.1.5.tar.gz) set(LUA_SHA256 2640fc56a795f29d28ef15e13c34a47e223960b0240e8cb0a82d9b0738695333) # NOTE: Version must match LUAROCKS_VERSION in third-party/cmake/BuildLuarocks.cmake -set(LUAROCKS_URL https://github.com/luarocks/luarocks/archive/v2.4.2.tar.gz) -set(LUAROCKS_SHA256 eef88c2429c715a7beb921e4b1ba571dddb7c74a250fbb0d3cc0d4be7a5865d9) +set(LUAROCKS_URL https://github.com/luarocks/luarocks/archive/v2.4.3.tar.gz) +set(LUAROCKS_SHA256 ea1881d6954f2a98c34f93674571c8f0cbdbc28dedb3fa3cb56b6a91886d1a99) set(UNIBILIUM_URL https://github.com/mauke/unibilium/archive/v1.2.1.tar.gz) set(UNIBILIUM_SHA256 6045b4f6adca7b1123284007675ca71f718f70942d3a93d8b9fa5bd442006ec1) @@ -117,8 +117,8 @@ endif() set(LIBVTERM_URL https://github.com/neovim/libvterm/archive/a9c7c6fd20fa35e0ad3e0e98901ca12dfca9c25c.tar.gz) set(LIBVTERM_SHA256 1a4272be91d9614dc183a503786df83b6584e4afaab7feaaa5409f841afbd796) -set(JEMALLOC_URL https://github.com/jemalloc/jemalloc/releases/download/4.5.0/jemalloc-4.5.0.tar.bz2) -set(JEMALLOC_SHA256 9409d85664b4f135b77518b0b118c549009dc10f6cba14557d170476611f6780) +set(JEMALLOC_URL https://github.com/jemalloc/jemalloc/releases/download/5.0.1/jemalloc-5.0.1.tar.bz2) +set(JEMALLOC_SHA256 4814781d395b0ef093b21a08e8e6e0bd3dab8762f9935bbfb71679b0dea7c3e9) set(LUV_URL https://github.com/luvit/luv/archive/1.9.1-1.tar.gz) set(LUV_SHA256 562b9efaad30aa051a40eac9ade0c3df48bb8186763769abe47ec3fb3edb1268) @@ -139,8 +139,8 @@ set(WIN32YANK_X86_SHA256 62f34e5a46c5d4a7b3f3b512e1ff7b77fedd432f42581cbe825233a set(WIN32YANK_X86_64_URL https://github.com/equalsraf/win32yank/releases/download/v0.0.4/win32yank-x64.zip) set(WIN32YANK_X86_64_SHA256 33a747a92da60fb65e668edbf7661d3d902411a2d545fe9dc08623cecd142a20) -set(WINPTY_URL https://github.com/rprichard/winpty/releases/download/0.4.2/winpty-0.4.2-msvc2015.zip) -set(WINPTY_SHA256 b465f2584ff394b3fe27c01aa1dcfc679583c1ee951d0e83de3f859d8b8305b8) +set(WINPTY_URL https://github.com/rprichard/winpty/releases/download/0.4.3/winpty-0.4.3-msvc2015.zip) +set(WINPTY_SHA256 35a48ece2ff4acdcbc8299d4920de53eb86b1fb41e64d2fe5ae7898931bcee89) if(USE_BUNDLED_UNIBILIUM) include(BuildUnibilium) -- cgit From 5563e808da545fb931f5a7fd7c2d391cfc6e21ca Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Fri, 29 Dec 2017 18:35:05 +0100 Subject: health.vim: fix $VIRTUAL_ENV validation Check that the full path to the python interpreter starts with $VIRTUAL_ENV. closes #7770 --- runtime/autoload/health/provider.vim | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/runtime/autoload/health/provider.vim b/runtime/autoload/health/provider.vim index 83dee043a0..d1239db605 100644 --- a/runtime/autoload/health/provider.vim +++ b/runtime/autoload/health/provider.vim @@ -370,21 +370,9 @@ function! s:check_python(version) abort let python_bin = '' endif - - " Check if $VIRTUAL_ENV is active. + " Check if $VIRTUAL_ENV is valid. if exists('$VIRTUAL_ENV') - let virtualenv_inactive = 0 - - if !empty(pyenv) - let pyenv_prefix = resolve(s:trim(s:system([pyenv, 'prefix']))) - if $VIRTUAL_ENV != pyenv_prefix - let virtualenv_inactive = 1 - endif - elseif !empty(pyname) && exepath(pyname) !~# '^'.$VIRTUAL_ENV.'/' - let virtualenv_inactive = 1 - endif - - if virtualenv_inactive + if !empty(pyname) && $VIRTUAL_ENV !=# matchstr(exepath(pyname), '^\V'.$VIRTUAL_ENV) call health#report_warn( \ '$VIRTUAL_ENV exists but appears to be inactive. ' \ . 'This could lead to unexpected results.', -- cgit From 3eaa9a2579f5406f80e0d1d67f2e74c5a33c5e18 Mon Sep 17 00:00:00 2001 From: Anmol Sethi Date: Fri, 29 Dec 2017 13:00:10 -0500 Subject: man.vim: always keep the alternate buffer (#7784) Closes #7772 --- runtime/autoload/man.vim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/runtime/autoload/man.vim b/runtime/autoload/man.vim index dd71ede680..af5c4dbd60 100644 --- a/runtime/autoload/man.vim +++ b/runtime/autoload/man.vim @@ -65,9 +65,9 @@ function! man#open_page(count, count1, mods, ...) abort try set eventignore+=BufReadCmd if a:mods !~# 'tab' && s:find_man() - execute 'silent edit' fnameescape(bufname) + execute 'silent keepalt edit' fnameescape(bufname) else - execute 'silent' a:mods 'split' fnameescape(bufname) + execute 'silent keepalt' a:mods 'split' fnameescape(bufname) endif finally set eventignore-=BufReadCmd -- cgit From e84e1b68c13443efc3e0c39a16c6a6945fbde20b Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Fri, 29 Dec 2017 20:35:52 +0100 Subject: Move applying of TabClosed to win_close_othertab --- src/nvim/ex_docmd.c | 1 - src/nvim/window.c | 18 +++++-------- test/functional/autocmd/tabclose_spec.lua | 44 +++++++++++++++++++++++++++---- 3 files changed, 45 insertions(+), 18 deletions(-) diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index f6a5f59676..a0ede4f3c5 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -6230,7 +6230,6 @@ void tabpage_close_other(tabpage_T *tp, int forceit) if (!valid_tabpage(tp) || tp->tp_firstwin == wp) break; } - apply_autocmds(EVENT_TABCLOSED, prev_idx, prev_idx, FALSE, curbuf); redraw_tabline = TRUE; if (h != tabline_height()) diff --git a/src/nvim/window.c b/src/nvim/window.c index bbdf40b874..81e986c5f2 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -1724,7 +1724,6 @@ void close_windows(buf_T *buf, int keep_curwin) { tabpage_T *tp, *nexttp; int h = tabline_height(); - int count = tabpage_index(NULL); ++RedrawingDisabled; @@ -1741,11 +1740,6 @@ void close_windows(buf_T *buf, int keep_curwin) } else wp = wp->w_next; } - if (count != tabpage_index(NULL)) { - char_u prev_idx[NUMBUFLEN]; - sprintf((char *)prev_idx, "%i", tabpage_index(curtab)); - apply_autocmds(EVENT_TABCLOSED, prev_idx, prev_idx, false, curbuf); - } /* Also check windows in other tab pages. */ for (tp = first_tabpage; tp != NULL; tp = nexttp) { @@ -1762,11 +1756,6 @@ void close_windows(buf_T *buf, int keep_curwin) break; } } - if (count != tabpage_index(NULL)) { - char_u prev_idx[NUMBUFLEN]; - sprintf((char *)prev_idx, "%i", tabpage_index(tp)); - apply_autocmds(EVENT_TABCLOSED, prev_idx, prev_idx, false, curbuf); - } } } @@ -1854,7 +1843,6 @@ static bool close_last_window_tabpage(win_T *win, bool free_buf, // Since goto_tabpage_tp above did not trigger *Enter autocommands, do // that now. - apply_autocmds(EVENT_TABCLOSED, prev_idx, prev_idx, false, curbuf); apply_autocmds(EVENT_WINENTER, NULL, NULL, false, curbuf); apply_autocmds(EVENT_TABENTER, NULL, NULL, false, curbuf); if (old_curbuf != curbuf) { @@ -2114,6 +2102,10 @@ void win_close_othertab(win_T *win, int free_buf, tabpage_T *tp) /* When closing the last window in a tab page remove the tab page. */ if (tp->tp_firstwin == tp->tp_lastwin) { + // save index for tabclosed event + char_u prev_idx[NUMBUFLEN]; + sprintf((char *)prev_idx, "%i", tabpage_index(tp)); + if (tp == first_tabpage) first_tabpage = tp->tp_next; else { @@ -2127,6 +2119,8 @@ void win_close_othertab(win_T *win, int free_buf, tabpage_T *tp) ptp->tp_next = tp->tp_next; } free_tp = TRUE; + + apply_autocmds(EVENT_TABCLOSED, prev_idx, prev_idx, false, win->w_buffer); } /* Free the memory used for the window. */ diff --git a/test/functional/autocmd/tabclose_spec.lua b/test/functional/autocmd/tabclose_spec.lua index 1431c69589..c412687b8c 100644 --- a/test/functional/autocmd/tabclose_spec.lua +++ b/test/functional/autocmd/tabclose_spec.lua @@ -2,10 +2,10 @@ local helpers = require('test.functional.helpers')(after_each) local clear, nvim, eq = helpers.clear, helpers.nvim, helpers.eq describe('TabClosed', function() + before_each(clear) describe('au TabClosed', function() describe('with * as ', function() it('matches when closing any tab', function() - clear() nvim('command', 'au! TabClosed * echom "tabclosed:".expand("").":".expand("").":".tabpagenr()') repeat nvim('command', 'tabnew') @@ -15,17 +15,51 @@ describe('TabClosed', function() eq("\ntabclosed:2:2:3", nvim('command_output', '2tabclose')) -- close tab 2, current tab is now 3 eq("\ntabclosed:1:1:2\ntabclosed:1:1:1", nvim('command_output', 'tabonly')) -- close tabs 1 and 2 end) + + it('is triggered when closing a window via bdelete from another tab', function() + nvim('command', 'au! TabClosed * echom "tabclosed:".expand("").":".expand("").":".tabpagenr()') + nvim('command', '1tabedit Xtestfile') + nvim('command', '1tabedit Xtestfile') + nvim('command', 'normal! 1gt') + eq({1, 3}, nvim('eval', '[tabpagenr(), tabpagenr("$")]')) + eq("\ntabclosed:2:2:1\ntabclosed:2:2:1", nvim('command_output', 'bdelete Xtestfile')) + eq({1, 1}, nvim('eval', '[tabpagenr(), tabpagenr("$")]')) + end) + + it('is triggered when closing a window via bdelete from current tab', function() + nvim('command', 'au! TabClosed * echom "tabclosed:".expand("").":".expand("").":".tabpagenr()') + nvim('command', 'file Xtestfile1') + nvim('command', '1tabedit Xtestfile2') + nvim('command', '1tabedit Xtestfile2') + + -- Only one tab is closed, and the alternate file is used for the other. + eq({2, 3}, nvim('eval', '[tabpagenr(), tabpagenr("$")]')) + eq("\ntabclosed:2:2:2", nvim('command_output', 'bdelete Xtestfile2')) + eq('Xtestfile1', nvim('eval', 'bufname("")')) + end) end) + describe('with NR as ', function() it('matches when closing a tab whose index is NR', function() + nvim('command', 'au! TabClosed * echom "tabclosed:".expand("").":".expand("").":".tabpagenr()') nvim('command', 'au! TabClosed 2 echom "tabclosed:match"') repeat nvim('command', 'tabnew') - until nvim('eval', 'tabpagenr()') == 5 -- current tab is now 5 + until nvim('eval', 'tabpagenr()') == 7 -- current tab is now 7 -- sanity check, we shouldn't match on tabs with numbers other than 2 - eq("\ntabclosed:5:5:4", nvim('command_output', 'tabclose')) - -- close tab page 2, current tab is now 3 - eq("\ntabclosed:2:2:3\ntabclosed:match", nvim('command_output', '2tabclose')) + eq("\ntabclosed:7:7:6", nvim('command_output', 'tabclose')) + -- close tab page 2, current tab is now 5 + eq("\ntabclosed:2:2:5\ntabclosed:match", nvim('command_output', '2tabclose')) + end) + end) + + describe('with close', function() + it('is triggered', function() + nvim('command', 'au! TabClosed * echom "tabclosed:".expand("").":".expand("").":".tabpagenr()') + nvim('command', 'tabedit Xtestfile') + eq({2, 2}, nvim('eval', '[tabpagenr(), tabpagenr("$")]')) + eq("\ntabclosed:2:2:1", nvim('command_output', 'close')) + eq({1, 1}, nvim('eval', '[tabpagenr(), tabpagenr("$")]')) end) end) end) -- cgit From 5dd2ca767f3163cb9d051a9a2676ac88f600ebad Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Fri, 29 Dec 2017 20:52:56 +0100 Subject: use snprintf and has_event --- src/nvim/window.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/nvim/window.c b/src/nvim/window.c index 81e986c5f2..2e1507c0ee 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -2102,9 +2102,10 @@ void win_close_othertab(win_T *win, int free_buf, tabpage_T *tp) /* When closing the last window in a tab page remove the tab page. */ if (tp->tp_firstwin == tp->tp_lastwin) { - // save index for tabclosed event char_u prev_idx[NUMBUFLEN]; - sprintf((char *)prev_idx, "%i", tabpage_index(tp)); + if (has_event(EVENT_TABCLOSED)) { + vim_snprintf((char *)prev_idx, NUMBUFLEN, "%i", tabpage_index(tp)); + } if (tp == first_tabpage) first_tabpage = tp->tp_next; @@ -2120,7 +2121,9 @@ void win_close_othertab(win_T *win, int free_buf, tabpage_T *tp) } free_tp = TRUE; - apply_autocmds(EVENT_TABCLOSED, prev_idx, prev_idx, false, win->w_buffer); + if (has_event(EVENT_TABCLOSED)) { + apply_autocmds(EVENT_TABCLOSED, prev_idx, prev_idx, false, win->w_buffer); + } } /* Free the memory used for the window. */ -- cgit From 697fb05c580ab01cfd97f84632e608e951b80cbf Mon Sep 17 00:00:00 2001 From: James McCoy Date: Sat, 30 Dec 2017 01:17:31 -0500 Subject: vim-patch:8.0.0608: cannot manipulate other than the current quickfix list Problem: Cannot manipulate other than the current quickfix list. Solution: Pass the list index to quickfix functions. (Yegappan Lakshmanan) https://github.com/vim/vim/commit/a3921f48c6b31a035c80fda49925dd3b42df0dec --- src/nvim/quickfix.c | 67 +++++++++++++++++++++++++++++------------------------ 1 file changed, 37 insertions(+), 30 deletions(-) diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index f85009dca8..bf2993b320 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -1121,6 +1121,7 @@ qf_init_ext( } if (qf_add_entry(qi, + qi->qf_curlist, qi->qf_directory, (*fields.namebuf || qi->qf_directory) ? fields.namebuf : ((qi->qf_currfile && fields.valid) @@ -1182,12 +1183,12 @@ qf_init_end: return retval; } -static void qf_store_title(qf_info_T *qi, char_u *title) +static void qf_store_title(qf_info_T *qi, int qf_idx, char_u *title) { if (title != NULL) { char_u *p = xmalloc(STRLEN(title) + 2); - qi->qf_lists[qi->qf_curlist].qf_title = p; + qi->qf_lists[qf_idx].qf_title = p; sprintf((char *)p, ":%s", (char *)title); } } @@ -1217,7 +1218,7 @@ static void qf_new_list(qf_info_T *qi, char_u *qf_title) } else qi->qf_curlist = qi->qf_listcount++; memset(&qi->qf_lists[qi->qf_curlist], 0, (size_t)(sizeof(qf_list_T))); - qf_store_title(qi, qf_title); + qf_store_title(qi, qi->qf_curlist, qf_title); } /* @@ -1260,6 +1261,7 @@ void qf_free_all(win_T *wp) /// Add an entry to the end of the list of errors. /// /// @param qi quickfix list +/// @param qf_idx list index /// @param dir optional directory name /// @param fname file name or NULL /// @param bufnum buffer number or zero @@ -1273,9 +1275,10 @@ void qf_free_all(win_T *wp) /// @param valid valid entry /// /// @returns OK or FAIL. -static int qf_add_entry(qf_info_T *qi, char_u *dir, char_u *fname, int bufnum, - char_u *mesg, long lnum, int col, char_u vis_col, - char_u *pattern, int nr, char_u type, char_u valid) +static int qf_add_entry(qf_info_T *qi, int qf_idx, char_u *dir, char_u *fname, + int bufnum, char_u *mesg, long lnum, int col, + char_u vis_col, char_u *pattern, int nr, char_u type, + char_u valid) { qfline_T *qfp = xmalloc(sizeof(qfline_T)); qfline_T **lastp; // pointer to qf_last or NULL @@ -1306,12 +1309,12 @@ static int qf_add_entry(qf_info_T *qi, char_u *dir, char_u *fname, int bufnum, qfp->qf_type = (char_u)type; qfp->qf_valid = valid; - lastp = &qi->qf_lists[qi->qf_curlist].qf_last; - if (qi->qf_lists[qi->qf_curlist].qf_count == 0) { + lastp = &qi->qf_lists[qf_idx].qf_last; + if (qi->qf_lists[qf_idx].qf_count == 0) { /* first element in the list */ - qi->qf_lists[qi->qf_curlist].qf_start = qfp; - qi->qf_lists[qi->qf_curlist].qf_ptr = qfp; - qi->qf_lists[qi->qf_curlist].qf_index = 0; + qi->qf_lists[qf_idx].qf_start = qfp; + qi->qf_lists[qf_idx].qf_ptr = qfp; + qi->qf_lists[qf_idx].qf_index = 0; qfp->qf_prev = NULL; } else { assert(*lastp); @@ -1321,12 +1324,11 @@ static int qf_add_entry(qf_info_T *qi, char_u *dir, char_u *fname, int bufnum, qfp->qf_next = NULL; qfp->qf_cleared = false; *lastp = qfp; - qi->qf_lists[qi->qf_curlist].qf_count++; - if (qi->qf_lists[qi->qf_curlist].qf_index == 0 && qfp->qf_valid) { + qi->qf_lists[qf_idx].qf_count++; + if (qi->qf_lists[qf_idx].qf_index == 0 && qfp->qf_valid) { /* first valid entry */ - qi->qf_lists[qi->qf_curlist].qf_index = - qi->qf_lists[qi->qf_curlist].qf_count; - qi->qf_lists[qi->qf_curlist].qf_ptr = qfp; + qi->qf_lists[qf_idx].qf_index = qi->qf_lists[qf_idx].qf_count; + qi->qf_lists[qf_idx].qf_ptr = qfp; } return OK; @@ -1429,6 +1431,7 @@ void copy_loclist(win_T *from, win_T *to) i < from_qfl->qf_count && from_qfp != NULL; i++, from_qfp = from_qfp->qf_next) { if (qf_add_entry(to->w_llist, + to->w_llist->qf_curlist, NULL, NULL, 0, @@ -3704,6 +3707,7 @@ void ex_vimgrep(exarg_T *eap) // dummy buffer, unless duplicate_name is set, then the // buffer will be wiped out below. if (qf_add_entry(qi, + qi->qf_curlist, NULL, // dir fname, duplicate_name ? 0 : buf->b_fnum, @@ -4164,23 +4168,24 @@ int get_errorlist_properties(win_T *wp, dict_T *what, dict_T *retdict) /// Add list of entries to quickfix/location list. Each list entry is /// a dictionary with item information. -static int qf_add_entries(qf_info_T *qi, list_T *list, char_u *title, - int action) +static int qf_add_entries(qf_info_T *qi, int qf_idx, list_T *list, + char_u *title, int action) { dict_T *d; qfline_T *old_last = NULL; int retval = OK; bool did_bufnr_emsg = false; - if (action == ' ' || qi->qf_curlist == qi->qf_listcount) { + if (action == ' ' || qf_idx == qi->qf_listcount) { // make place for a new list qf_new_list(qi, title); - } else if (action == 'a' && qi->qf_lists[qi->qf_curlist].qf_count > 0) { + qf_idx = qi->qf_curlist; + } else if (action == 'a' && qi->qf_lists[qf_idx].qf_count > 0) { // Adding to existing list, use last entry. - old_last = qi->qf_lists[qi->qf_curlist].qf_last; + old_last = qi->qf_lists[qf_idx].qf_last; } else if (action == 'r') { - qf_free(qi, qi->qf_curlist); - qf_store_title(qi, title); + qf_free(qi, qf_idx); + qf_store_title(qi, qf_idx, title); } TV_LIST_ITER_CONST(list, li, { @@ -4228,6 +4233,7 @@ static int qf_add_entries(qf_info_T *qi, list_T *list, char_u *title, } int status = qf_add_entry(qi, + qf_idx, NULL, // dir (char_u *)filename, bufnum, @@ -4250,16 +4256,16 @@ static int qf_add_entries(qf_info_T *qi, list_T *list, char_u *title, } }); - if (qi->qf_lists[qi->qf_curlist].qf_index == 0) { + if (qi->qf_lists[qf_idx].qf_index == 0) { // no valid entry - qi->qf_lists[qi->qf_curlist].qf_nonevalid = true; + qi->qf_lists[qf_idx].qf_nonevalid = true; } else { - qi->qf_lists[qi->qf_curlist].qf_nonevalid = false; + qi->qf_lists[qf_idx].qf_nonevalid = false; } if (action != 'a') { - qi->qf_lists[qi->qf_curlist].qf_ptr = qi->qf_lists[qi->qf_curlist].qf_start; - if (qi->qf_lists[qi->qf_curlist].qf_count > 0) { - qi->qf_lists[qi->qf_curlist].qf_index = 1; + qi->qf_lists[qf_idx].qf_ptr = qi->qf_lists[qf_idx].qf_start; + if (qi->qf_lists[qf_idx].qf_count > 0) { + qi->qf_lists[qf_idx].qf_index = 1; } } @@ -4400,7 +4406,7 @@ int set_errorlist(win_T *wp, list_T *list, int action, char_u *title, } else if (what != NULL) { retval = qf_set_properties(qi, what, action); } else { - retval = qf_add_entries(qi, list, title, action); + retval = qf_add_entries(qi, qi->qf_curlist, list, title, action); } return retval; @@ -4718,6 +4724,7 @@ void ex_helpgrep(exarg_T *eap) line[--l] = NUL; if (qf_add_entry(qi, + qi->qf_curlist, NULL, // dir fnames[fi], 0, -- cgit From caf94c72c51b3595676907c66e6259785a87420b Mon Sep 17 00:00:00 2001 From: James McCoy Date: Sat, 30 Dec 2017 07:56:12 -0500 Subject: lint --- src/nvim/quickfix.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index bf2993b320..120a449690 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -1186,10 +1186,11 @@ qf_init_end: static void qf_store_title(qf_info_T *qi, int qf_idx, char_u *title) { if (title != NULL) { - char_u *p = xmalloc(STRLEN(title) + 2); + size_t len = STRLEN(title) + 1; + char_u *p = xmallocz(len); qi->qf_lists[qf_idx].qf_title = p; - sprintf((char *)p, ":%s", (char *)title); + snprintf((char *)p, len + 1, ":%s", (char *)title); } } @@ -1311,7 +1312,7 @@ static int qf_add_entry(qf_info_T *qi, int qf_idx, char_u *dir, char_u *fname, lastp = &qi->qf_lists[qf_idx].qf_last; if (qi->qf_lists[qf_idx].qf_count == 0) { - /* first element in the list */ + // first element in the list qi->qf_lists[qf_idx].qf_start = qfp; qi->qf_lists[qf_idx].qf_ptr = qfp; qi->qf_lists[qf_idx].qf_index = 0; @@ -1326,7 +1327,7 @@ static int qf_add_entry(qf_info_T *qi, int qf_idx, char_u *dir, char_u *fname, *lastp = qfp; qi->qf_lists[qf_idx].qf_count++; if (qi->qf_lists[qf_idx].qf_index == 0 && qfp->qf_valid) { - /* first valid entry */ + // first valid entry qi->qf_lists[qf_idx].qf_index = qi->qf_lists[qf_idx].qf_count; qi->qf_lists[qf_idx].qf_ptr = qfp; } -- cgit From 0d548b73efb31452465dab5d15238e336125042c Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Sat, 30 Dec 2017 14:15:51 +0100 Subject: scripts/vim-patch.sh: continue when patching with -P fails (#7790) The `set -e` caused the script to stop in case `patch` fails, but it is better to continue giving instructions. --- scripts/vim-patch.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/vim-patch.sh b/scripts/vim-patch.sh index c6ff280bb5..ac5e326e9d 100755 --- a/scripts/vim-patch.sh +++ b/scripts/vim-patch.sh @@ -234,7 +234,7 @@ stage_patch() { printf "\n✘ 'patch' command not found\n" else printf "\nApplying patch...\n" - patch -p1 --posix < "${patch_file}" + patch -p1 --posix < "${patch_file}" || true fi printf "\nInstructions:\n Proceed to port the patch.\n" else -- cgit From 46f432074e739a0eca9bb204e9c7769935669dbd Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Fri, 29 Dec 2017 21:13:21 +0100 Subject: tests: termclose_spec: fix flaky SIGTERM test #7787 Followup to https://github.com/neovim/neovim/pull/7217. Build failure: https://travis-ci.org/neovim/neovim/jobs/322930672#L2958. --- src/nvim/window.c | 15 ++++++++------- test/functional/autocmd/termclose_spec.lua | 9 ++++++--- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/nvim/window.c b/src/nvim/window.c index 2e1507c0ee..b687781dfb 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -2104,25 +2104,26 @@ void win_close_othertab(win_T *win, int free_buf, tabpage_T *tp) if (tp->tp_firstwin == tp->tp_lastwin) { char_u prev_idx[NUMBUFLEN]; if (has_event(EVENT_TABCLOSED)) { - vim_snprintf((char *)prev_idx, NUMBUFLEN, "%i", tabpage_index(tp)); + vim_snprintf((char *)prev_idx, NUMBUFLEN, "%i", tabpage_index(tp)); } - if (tp == first_tabpage) + if (tp == first_tabpage) { first_tabpage = tp->tp_next; - else { + } else { for (ptp = first_tabpage; ptp != NULL && ptp->tp_next != tp; - ptp = ptp->tp_next) - ; + ptp = ptp->tp_next) { + // loop + } if (ptp == NULL) { internal_error("win_close_othertab()"); return; } ptp->tp_next = tp->tp_next; } - free_tp = TRUE; + free_tp = true; if (has_event(EVENT_TABCLOSED)) { - apply_autocmds(EVENT_TABCLOSED, prev_idx, prev_idx, false, win->w_buffer); + apply_autocmds(EVENT_TABCLOSED, prev_idx, prev_idx, false, win->w_buffer); } } diff --git a/test/functional/autocmd/termclose_spec.lua b/test/functional/autocmd/termclose_spec.lua index c6c30494dd..e64df502a6 100644 --- a/test/functional/autocmd/termclose_spec.lua +++ b/test/functional/autocmd/termclose_spec.lua @@ -4,6 +4,7 @@ local clear, command, nvim, nvim_dir = helpers.clear, helpers.command, helpers.nvim, helpers.nvim_dir local eval, eq, retry = helpers.eval, helpers.eq, helpers.retry +local ok = helpers.ok if helpers.pending_win32(pending) then return end @@ -41,7 +42,9 @@ describe('TermClose event', function() command('call jobstop(g:test_job)') retry(nil, nil, function() eq(1, eval('get(g:, "test_job_exited", 0)')) end) local duration = os.time() - start - eq(2, duration) + -- nvim starts sending SIGTERM after KILL_TIMEOUT_MS + ok(duration >= 2) + ok(duration <= 4) -- <= 2 + delta because of slow CI end) it('kills pty job trapping SIGHUP and SIGTERM', function() @@ -58,8 +61,8 @@ describe('TermClose event', function() retry(nil, nil, function() eq(1, eval('get(g:, "test_job_exited", 0)')) end) local duration = os.time() - start -- nvim starts sending kill after 2*KILL_TIMEOUT_MS - helpers.ok(4 <= duration) - helpers.ok(duration <= 7) -- <= 4 + delta because of slow CI + ok(duration >= 4) + ok(duration <= 7) -- <= 4 + delta because of slow CI end) it('reports the correct ', function() -- cgit From c55cf5f4c181856d7ebb6697e8558d71279e7adb Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 31 Dec 2017 00:50:31 +0300 Subject: eval,lua/converter: Fix problems spotted in review --- src/nvim/eval.c | 60 ++++++++++++++++++++++++++---------------------- src/nvim/lua/converter.c | 2 +- 2 files changed, 33 insertions(+), 29 deletions(-) diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 370d4f0c0b..d2432e864f 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -1515,7 +1515,6 @@ ex_let_vars ( ) { char_u *arg = arg_start; - int i; typval_T ltv; if (*arg != '[') { @@ -1534,17 +1533,17 @@ ex_let_vars ( } list_T *const l = tv->vval.v_list; - i = tv_list_len(l); - if (semicolon == 0 && var_count < i) { + const int len = tv_list_len(l); + if (semicolon == 0 && var_count < len) { EMSG(_("E687: Less targets than List items")); return FAIL; } - if (var_count - semicolon > i) { + if (var_count - semicolon > len) { EMSG(_("E688: More targets than List items")); return FAIL; } - // lt may actually be NULL, but it should fail with E688 or even earlier if - // you try to do ":let [] = v:_null_list". + // List l may actually be NULL, but it should fail with E688 or even earlier + // if you try to do ":let [] = v:_null_list". assert(l != NULL); listitem_T *item = tv_list_first(l); @@ -12220,10 +12219,11 @@ static void find_some_match(typval_T *const argvars, typval_T *const rettv, // matchlist(): return empty list when there are no matches. case kSomeMatchList: { tv_list_alloc_ret(rettv); - FALLTHROUGH; + break; } // matchstrpos(): return ["", -1, -1, -1] case kSomeMatchStrPos: { + tv_list_alloc_ret(rettv); tv_list_append_string(rettv->vval.v_list, "", 0); tv_list_append_number(rettv->vval.v_list, -1); tv_list_append_number(rettv->vval.v_list, -1); @@ -12385,7 +12385,7 @@ static void find_some_match(typval_T *const argvars, typval_T *const rettv, if (l != NULL) { rettv->vval.v_number = idx; } else { - if (type == kSomeMatchEnd) { + if (type == kSomeMatch) { rettv->vval.v_number = (varnumber_T)(regmatch.startp[0] - str); } else { @@ -12394,6 +12394,7 @@ static void find_some_match(typval_T *const argvars, typval_T *const rettv, } rettv->vval.v_number += (varnumber_T)(str - expr); } + break; } } } @@ -13020,7 +13021,6 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) long prevlen = 0; /* length of data in prev */ long prevsize = 0; /* size of prev buffer */ long maxline = MAXLNUM; - long cnt = 0; char_u *p; /* position in buf */ char_u *start; /* start of current line */ @@ -13034,6 +13034,7 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) } tv_list_alloc_ret(rettv); + list_T *const l = rettv->vval.v_list; // Always open the file in binary mode, library functions have a mind of // their own about CR-LF conversion. @@ -13043,7 +13044,7 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } - while (cnt < maxline || maxline < 0) { + while (maxline < 0 || tv_list_len(l) < maxline) { readlen = (int)fread(buf, 1, io_size, fd); /* This for loop processes what was read, but is also entered at end @@ -13081,22 +13082,32 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) prevlen = prevsize = 0; } - tv_list_append_owned_tv(rettv->vval.v_list, (typval_T) { + tv_list_append_owned_tv(l, (typval_T) { .v_type = VAR_STRING, .v_lock = VAR_UNLOCKED, .vval.v_string = s, }); - start = p + 1; /* step over newline */ - if ((++cnt >= maxline && maxline >= 0) || readlen <= 0) + start = p + 1; // Step over newline. + if (maxline < 0) { + if (tv_list_len(l) > -maxline) { + assert(tv_list_len(l) == 1 + (-maxline)); + tv_list_item_remove(l, tv_list_first(l)); + } + } else if (tv_list_len(l) >= maxline) { + assert(tv_list_len(l) == maxline); break; - } else if (*p == NUL) + } + if (readlen <= 0) { + break; + } + } else if (*p == NUL) { *p = '\n'; - /* Check for utf8 "bom"; U+FEFF is encoded as EF BB BF. Do this - * when finding the BF and check the previous two bytes. */ - else if (*p == 0xbf && enc_utf8 && !binary) { - /* Find the two bytes before the 0xbf. If p is at buf, or buf - * + 1, these may be in the "prev" string. */ + // Check for utf8 "bom"; U+FEFF is encoded as EF BB BF. Do this + // when finding the BF and check the previous two bytes. + } else if (*p == 0xbf && !binary) { + // Find the two bytes before the 0xbf. If p is at buf, or buf + 1, + // these may be in the "prev" string. char_u back1 = p >= buf + 1 ? p[-1] : prevlen >= 1 ? prev[prevlen - 1] : NUL; char_u back2 = p >= buf + 2 ? p[-2] @@ -13130,8 +13141,9 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } /* for */ - if ((cnt >= maxline && maxline >= 0) || readlen <= 0) + if ((maxline >= 0 && tv_list_len(l) >= maxline) || readlen <= 0) { break; + } if (start < p) { /* There's part of a line in buf, store it in "prev". */ if (p - start + prevlen >= prevsize) { @@ -13155,14 +13167,6 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } /* while */ - // For a negative line count use only the lines at the end of the file, - // free the rest. - if (maxline < -tv_list_len(rettv->vval.v_list)) { - listitem_T *const first_li = tv_list_find(rettv->vval.v_list, maxline); - listitem_T *const last_li = tv_list_last(rettv->vval.v_list); - tv_list_remove_items(rettv->vval.v_list, first_li, last_li); - } - xfree(prev); fclose(fd); } diff --git a/src/nvim/lua/converter.c b/src/nvim/lua/converter.c index 89fedae73a..61cb428923 100644 --- a/src/nvim/lua/converter.c +++ b/src/nvim/lua/converter.c @@ -213,7 +213,6 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) if (cur.special) { list_T *const kv_pair = tv_list_alloc(); - tv_list_append_list(cur.tv->vval.v_list, kv_pair); typval_T s_tv = decode_string(s, len, kTrue, false, false); if (s_tv.v_type == VAR_UNKNOWN) { ret = false; @@ -227,6 +226,7 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) .v_type = VAR_UNKNOWN, }); kv_push(stack, cur); + tv_list_append_list(cur.tv->vval.v_list, kv_pair); cur = (TVPopStackItem) { .tv = TV_LIST_ITEM_TV(tv_list_last(kv_pair)), .container = false, -- cgit From 8ac7c23b7dd7c435fb80315921e3704c8e0a7448 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 31 Dec 2017 00:57:17 +0300 Subject: eval: Fix linter errors --- src/nvim/eval.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/nvim/eval.c b/src/nvim/eval.c index d2432e864f..33f8ffb738 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -12358,7 +12358,7 @@ static void find_some_match(typval_T *const argvars, typval_T *const rettv, } case kSomeMatchList: { // Return list with matched string and submatches. - for (int i = 0; i < NSUBEXP; ++i) { + for (int i = 0; i < NSUBEXP; i++) { if (regmatch.endp[i] == NULL) { tv_list_append_string(rettv->vval.v_list, NULL, 0); } else { @@ -13021,8 +13021,6 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) long prevlen = 0; /* length of data in prev */ long prevsize = 0; /* size of prev buffer */ long maxline = MAXLNUM; - char_u *p; /* position in buf */ - char_u *start; /* start of current line */ if (argvars[1].v_type != VAR_UNKNOWN) { if (strcmp(tv_get_string(&argvars[1]), "b") == 0) { @@ -13047,14 +13045,16 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) while (maxline < 0 || tv_list_len(l) < maxline) { readlen = (int)fread(buf, 1, io_size, fd); - /* This for loop processes what was read, but is also entered at end - * of file so that either: - * - an incomplete line gets written - * - a "binary" file gets an empty line at the end if it ends in a - * newline. */ + // This for loop processes what was read, but is also entered at end + // of file so that either: + // - an incomplete line gets written + // - a "binary" file gets an empty line at the end if it ends in a + // newline. + char_u *p; // Position in buf. + char_u *start; // Start of current line. for (p = buf, start = buf; p < buf + readlen || (readlen <= 0 && (prevlen > 0 || binary)); - ++p) { + p++) { if (*p == '\n' || readlen <= 0) { char_u *s = NULL; size_t len = p - start; @@ -15416,7 +15416,7 @@ static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort) } } - /* Make an array with each entry pointing to an item in the List. */ + // Make an array with each entry pointing to an item in the List. ptrs = xmalloc((size_t)(len * sizeof(ListSortItem))); if (sort) { -- cgit From 89d1b36084f50acffdc7680212c37383e08787b5 Mon Sep 17 00:00:00 2001 From: James McCoy Date: Sat, 30 Dec 2017 23:30:40 -0500 Subject: vim-patch:8.0.0591: changes to eval functionality not documented Problem: Changes to eval functionality not documented. Solution: Include all the changes. https://github.com/vim/vim/commit/45d2cca1ea3f90fc70ad99d0c6812a9d8536303c --- runtime/doc/eval.txt | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index a9474b58a3..2c3d38ced7 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -4327,6 +4327,7 @@ getqflist([{what}]) *getqflist()* If the optional {what} dictionary argument is supplied, then returns only the items listed in {what} as a dictionary. The following string items are supported in {what}: + context get the context stored with |setqflist()| nr get information for this quickfix list; zero means the current quickfix list title get the list title @@ -4338,6 +4339,7 @@ getqflist([{what}]) *getqflist()* returned. The returned dictionary contains the following entries: + context context information stored with |setqflist()| nr quickfix list number title quickfix list title text winid quickfix |window-ID| (if opened) @@ -6885,6 +6887,7 @@ setqflist({list} [, {action}[, {what}]]) *setqflist()* only the items listed in {what} are set. The first {list} argument is ignored. The following items can be specified in {what}: + context any Vim type can be stored as a context nr list number in the quickfix stack title quickfix list title text Unsupported keys in {what} are ignored. @@ -10508,18 +10511,19 @@ missing: > To execute a command only when the |+eval| feature is disabled requires a trick, as this example shows: > - if 1 - nnoremap : :" - endif - normal :set history=111 - if 1 - nunmap : - endif + + silent! while 0 + set history=111 + silent! endwhile + +When the |+eval| feature is available the command is skipped because of the +"while 0". Without the |+eval| feature the "while 0" is an error, which is +silently ignored, and the command is executed. The "" here is a real CR character, type CTRL-V Enter to get it. When the |+eval| feature is available the ":" is remapped to add a double -quote, which has the effect of commenting-out the command. without the +quote, which has the effect of commenting-out the command. Without the |+eval| feature the nnoremap command is skipped and the command is executed. ============================================================================== -- cgit From 6742fd8aead06e45f19b59222f96ccdcb1748e4c Mon Sep 17 00:00:00 2001 From: James McCoy Date: Sat, 30 Dec 2017 23:39:51 -0500 Subject: vim-patch:8.0.0634: cannot easily get to the last quickfix list Problem: Cannot easily get to the last quickfix list. Solution: Add "$" as a value for the "nr" argument of getqflist() and setqflist(). (Yegappan Lakshmanan) https://github.com/vim/vim/commit/875feea6ce223462d55543735143d747dcaf4287 --- runtime/doc/eval.txt | 18 +++++++++++---- src/nvim/quickfix.c | 47 ++++++++++++++++++++++++++++---------- src/nvim/testdir/test_quickfix.vim | 44 +++++++++++++++++++++++++++++++++-- 3 files changed, 91 insertions(+), 18 deletions(-) diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index 2c3d38ced7..7c1e524b66 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -4329,12 +4329,16 @@ getqflist([{what}]) *getqflist()* following string items are supported in {what}: context get the context stored with |setqflist()| nr get information for this quickfix list; zero - means the current quickfix list + means the current quickfix list and '$' means + the last quickfix list title get the list title winid get the |window-ID| (if opened) all all of the above quickfix properties Non-string items in {what} are ignored. If "nr" is not present then the current quickfix list is used. + To get the number of lists in the quickfix stack, set 'nr' to + '$' in {what}. The 'nr' value in the returned dictionary + contains the quickfix stack size. In case of error processing {what}, an empty dictionary is returned. @@ -6888,7 +6892,9 @@ setqflist({list} [, {action}[, {what}]]) *setqflist()* argument is ignored. The following items can be specified in {what}: context any Vim type can be stored as a context - nr list number in the quickfix stack + nr list number in the quickfix stack; zero + means the current quickfix list and '$' means + the last quickfix list title quickfix list title text Unsupported keys in {what} are ignored. If the "nr" item is not present, then the current quickfix list @@ -6993,18 +6999,22 @@ shellescape({string} [, {special}]) *shellescape()* quotes within {string}. Otherwise, it will enclose {string} in single quotes and replace all "'" with "'\''". + When the {special} argument is present and it's a non-zero Number or a non-empty String (|non-zero-arg|), then special items such as "!", "%", "#" and "" will be preceded by a backslash. This backslash will be removed again by the |:!| command. + The "!" character will be escaped (again with a |non-zero-arg| {special}) when 'shell' contains "csh" in the tail. That is because for csh and tcsh "!" is used for history replacement even when inside single quotes. - The character is also escaped. With a |non-zero-arg| - {special} and 'shell' containing "csh" in the tail it's + + With a |non-zero-arg| {special} the character is also + escaped. When 'shell' containing "csh" in the tail it's escaped a second time. + Example of use with a |:!| command: > :exe '!dir ' . shellescape(expand(''), 1) < This results in a directory listing for the file under the diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 120a449690..1d368bf87a 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -4088,16 +4088,22 @@ enum { int get_errorlist_properties(win_T *wp, dict_T *what, dict_T *retdict) { qf_info_T *qi = &ql_info; + dictitem_T *di; if (wp != NULL) { qi = GET_LOC_LIST(wp); if (qi == NULL) { + // If querying for the size of the location list, return 0 + if (((di = tv_dict_find(what, S_LEN("nr"))) != NULL) + && (di->di_tv.v_type == VAR_STRING) + && strequal((const char *)di->di_tv.vval.v_string, "$")) { + return tv_dict_add_nr(retdict, S_LEN("nr"), 0); + } return FAIL; } } int status = OK; - dictitem_T *di; int flags = QF_GETLIST_NONE; int qf_idx = qi->qf_curlist; // default is the current list @@ -4110,6 +4116,17 @@ int get_errorlist_properties(win_T *wp, dict_T *what, dict_T *retdict) if (qf_idx < 0 || qf_idx >= qi->qf_listcount) { return FAIL; } + } else if (qi->qf_listcount == 0) { // stack is empty + return FAIL; + } + flags |= QF_GETLIST_NR; + } else if (di->di_tv.v_type == VAR_STRING + && strequal((const char *)di->di_tv.vval.v_string, "$")) { + // Get the last quickfix list number + if (qi->qf_listcount > 0) { + qf_idx = qi->qf_listcount - 1; + } else { + qf_idx = -1; // Quickfix stack is empty } flags |= QF_GETLIST_NR; } else { @@ -4117,20 +4134,22 @@ int get_errorlist_properties(win_T *wp, dict_T *what, dict_T *retdict) } } - if (tv_dict_find(what, S_LEN("all")) != NULL) { - flags |= QF_GETLIST_ALL; - } + if (qf_idx != -1) { + if (tv_dict_find(what, S_LEN("all")) != NULL) { + flags |= QF_GETLIST_ALL; + } - if (tv_dict_find(what, S_LEN("title")) != NULL) { - flags |= QF_GETLIST_TITLE; - } + if (tv_dict_find(what, S_LEN("title")) != NULL) { + flags |= QF_GETLIST_TITLE; + } - if (tv_dict_find(what, S_LEN("winid")) != NULL) { - flags |= QF_GETLIST_WINID; - } + if (tv_dict_find(what, S_LEN("winid")) != NULL) { + flags |= QF_GETLIST_WINID; + } - if (tv_dict_find(what, S_LEN("context")) != NULL) { - flags |= QF_GETLIST_CONTEXT; + if (tv_dict_find(what, S_LEN("context")) != NULL) { + flags |= QF_GETLIST_CONTEXT; + } } if (flags & QF_GETLIST_TITLE) { @@ -4296,6 +4315,10 @@ static int qf_set_properties(qf_info_T *qi, dict_T *what, int action) if (qf_idx < 0 || qf_idx >= qi->qf_listcount) { return FAIL; } + } else if (di->di_tv.v_type == VAR_STRING + && strequal((const char *)di->di_tv.vval.v_string, "$") + && qi->qf_listcount > 0) { + qf_idx = qi->qf_listcount - 1; } else { return FAIL; } diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim index 30023dddc9..828176e6be 100644 --- a/src/nvim/testdir/test_quickfix.vim +++ b/src/nvim/testdir/test_quickfix.vim @@ -1632,12 +1632,12 @@ func XbottomTests(cchar) call assert_fails('lbottom', 'E776:') endif - call g:Xsetlist([{'filename': 'foo', 'lnum': 42}]) + call g:Xsetlist([{'filename': 'foo', 'lnum': 42}]) Xopen let wid = win_getid() call assert_equal(1, line('.')) wincmd w - call g:Xsetlist([{'filename': 'var', 'lnum': 24}], 'a') + call g:Xsetlist([{'filename': 'var', 'lnum': 24}], 'a') Xbottom call win_gotoid(wid) call assert_equal(2, line('.')) @@ -2102,3 +2102,43 @@ func Test_bufoverflow() set efm&vim endfunc +func Test_cclose_from_copen() + augroup QF_Test + au! + au FileType qf :cclose + augroup END + copen + augroup QF_Test + au! + augroup END + augroup! QF_Test +endfunc + +" Tests for getting the quickfix stack size +func XsizeTests(cchar) + call s:setup_commands(a:cchar) + + call g:Xsetlist([], 'f') + call assert_equal(0, g:Xgetlist({'nr':'$'}).nr) + call assert_equal(1, len(g:Xgetlist({'nr':'$', 'all':1}))) + call assert_equal(0, len(g:Xgetlist({'nr':0}))) + + Xexpr "File1:10:Line1" + Xexpr "File2:20:Line2" + Xexpr "File3:30:Line3" + Xolder | Xolder + call assert_equal(3, g:Xgetlist({'nr':'$'}).nr) + call g:Xsetlist([], 'f') + + Xexpr "File1:10:Line1" + Xexpr "File2:20:Line2" + Xexpr "File3:30:Line3" + Xolder | Xolder + call g:Xsetlist([], 'a', {'nr':'$', 'title':'Compiler'}) + call assert_equal('Compiler', g:Xgetlist({'nr':3, 'all':1}).title) +endfunc + +func Test_Qf_Size() + call XsizeTests('c') + call XsizeTests('l') +endfunc -- cgit From 3efc50d1d4e97c3d7bc3734ac77e6ed7b43a391d Mon Sep 17 00:00:00 2001 From: James McCoy Date: Sun, 31 Dec 2017 00:27:08 -0500 Subject: vim-patch:8.0.0641: cannot set a separate highlighting for the quickfix line Problem: Cannot set a separate highlighting for the current line in the quickfix window. Solution: Add QuickFixLine. (anishsane, closes vim/vim#1755) https://github.com/vim/vim/commit/2102035488e80ef6fd5038ed15d21672712ba0f6 --- runtime/doc/options.txt | 4 ++++ runtime/doc/quickfix.txt | 6 +++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index f70ec32bd8..067aad1f93 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -2712,6 +2712,10 @@ A jump table for the options with a short description can be found at |Q_op|. :s///g subst. one subst. all :s///gg subst. all subst. one + DEPRECATED: Setting this option may break plugins that are not aware + of this option. Also, many users get confused that adding the /g flag + has the opposite effect of that it normally does. + *'grepformat'* *'gfm'* 'grepformat' 'gfm' string (default "%f:%l:%m,%f:%l%m,%f %l%m") global diff --git a/runtime/doc/quickfix.txt b/runtime/doc/quickfix.txt index 74f82b2c65..f280286290 100644 --- a/runtime/doc/quickfix.txt +++ b/runtime/doc/quickfix.txt @@ -470,7 +470,11 @@ keep its height, ignoring 'winheight' and 'equalalways'. You can change the height manually (e.g., by dragging the status line above it with the mouse). In the quickfix window, each line is one error. The line number is equal to -the error number. You can use ":.cc" to jump to the error under the cursor. +the error number. The current entry is highlighted with the QuickFixLine +highlighting. You can change it to your liking, e.g.: > + :hi QuickFixLine ctermbg=Yellow guibg=Yellow + +You can use ":.cc" to jump to the error under the cursor. Hitting the key or double-clicking the mouse on a line has the same effect. The file containing the error is opened in the window above the quickfix window. If there already is a window for that file, it is used -- cgit From d0c4bd23f78ee00943725ea77a63f2a223dba66b Mon Sep 17 00:00:00 2001 From: James McCoy Date: Sun, 31 Dec 2017 00:36:35 -0500 Subject: vim-patch:8.0.0657: cannot get and set quickfix list items Problem: Cannot get and set quickfix list items. Solution: Add the "items" argument to getqflist() and setqflist(). (Yegappan Lakshmanan) https://github.com/vim/vim/commit/6a8958db259d4444da6e6956e54a6513c1af8860 --- runtime/doc/eval.txt | 4 ++ src/nvim/quickfix.c | 81 +++++++++++++++++++++++++++++--------- src/nvim/testdir/test_quickfix.vim | 67 +++++++++++++++++++++++++++++++ 3 files changed, 134 insertions(+), 18 deletions(-) diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index 7c1e524b66..ae62498d35 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -4328,6 +4328,7 @@ getqflist([{what}]) *getqflist()* returns only the items listed in {what} as a dictionary. The following string items are supported in {what}: context get the context stored with |setqflist()| + items quickfix list entries nr get information for this quickfix list; zero means the current quickfix list and '$' means the last quickfix list @@ -4344,6 +4345,7 @@ getqflist([{what}]) *getqflist()* The returned dictionary contains the following entries: context context information stored with |setqflist()| + items quickfix list entries nr quickfix list number title quickfix list title text winid quickfix |window-ID| (if opened) @@ -6892,6 +6894,8 @@ setqflist({list} [, {action}[, {what}]]) *setqflist()* argument is ignored. The following items can be specified in {what}: context any Vim type can be stored as a context + items list of quickfix entries. Same as the {list} + argument. nr list number in the quickfix stack; zero means the current quickfix list and '$' means the last quickfix list diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 1d368bf87a..224e43008d 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -76,18 +76,25 @@ struct qfline_S { */ #define LISTCOUNT 10 +/// Quickfix/Location list definition +/// +/// Usually the list contains one or more entries. But an empty list can be +/// created using setqflist()/setloclist() with a title and/or user context +/// information and entries can be added later using setqflist()/setloclist(). typedef struct qf_list_S { - qfline_T *qf_start; // pointer to the first error - qfline_T *qf_last; // pointer to the last error - qfline_T *qf_ptr; // pointer to the current error - int qf_count; // number of errors (0 means no error list) - int qf_index; // current index in the error list - int qf_nonevalid; // TRUE if not a single valid entry found - char_u *qf_title; // title derived from the command that created - // the error list - typval_T *qf_ctx; // context set by setqflist/setloclist + qfline_T *qf_start; ///< pointer to the first error + qfline_T *qf_last; ///< pointer to the last error + qfline_T *qf_ptr; ///< pointer to the current error + int qf_count; ///< number of errors (0 means empty list) + int qf_index; ///< current index in the error list + int qf_nonevalid; ///< TRUE if not a single valid entry found + char_u *qf_title; ///< title derived from the command that created + ///< the error list or set by setqflist + typval_T *qf_ctx; ///< context set by setqflist/setloclist } qf_list_T; +/// Quickfix/Location list stack definition +/// Contains a list of quickfix/location lists (qf_list_T) struct qf_info_S { /* * Count of references to this list. Used only for location lists. @@ -1185,6 +1192,9 @@ qf_init_end: static void qf_store_title(qf_info_T *qi, int qf_idx, char_u *title) { + xfree(qi->qf_lists[qf_idx].qf_title); + qi->qf_lists[qf_idx].qf_title = NULL; + if (title != NULL) { size_t len = STRLEN(title) + 1; char_u *p = xmallocz(len); @@ -2396,8 +2406,9 @@ void qf_history(exarg_T *eap) } } -/// Free all the entries in the error list "idx". -static void qf_free(qf_info_T *qi, int idx) +/// Free all the entries in the error list "idx". Note that other information +/// associated with the list like context and title are not freed. +static void qf_free_items(qf_info_T *qi, int idx) { qfline_T *qfp; qfline_T *qfpnext; @@ -2421,12 +2432,9 @@ static void qf_free(qf_info_T *qi, int idx) qi->qf_lists[idx].qf_start = qfpnext; qi->qf_lists[idx].qf_count--; } - xfree(qi->qf_lists[idx].qf_title); + qi->qf_lists[idx].qf_start = NULL; qi->qf_lists[idx].qf_ptr = NULL; - qi->qf_lists[idx].qf_title = NULL; - tv_free(qi->qf_lists[idx].qf_ctx); - qi->qf_lists[idx].qf_ctx = NULL; qi->qf_lists[idx].qf_index = 0; qi->qf_lists[idx].qf_start = NULL; qi->qf_lists[idx].qf_last = NULL; @@ -2442,6 +2450,18 @@ static void qf_free(qf_info_T *qi, int idx) qi->qf_multiscan = false; } +/// Free error list "idx". Frees all the entries in the quickfix list, +/// associated context information and the title. +static void qf_free(qf_info_T *qi, int idx) +{ + qf_free_items(qi, idx); + + xfree(qi->qf_lists[idx].qf_title); + qi->qf_lists[idx].qf_title = NULL; + tv_free(qi->qf_lists[idx].qf_ctx); + qi->qf_lists[idx].qf_ctx = NULL; +} + /* * qf_mark_adjust: adjust marks */ @@ -4150,6 +4170,10 @@ int get_errorlist_properties(win_T *wp, dict_T *what, dict_T *retdict) if (tv_dict_find(what, S_LEN("context")) != NULL) { flags |= QF_GETLIST_CONTEXT; } + + if (tv_dict_find(what, S_LEN("items")) != NULL) { + flags |= QF_GETLIST_ITEMS; + } } if (flags & QF_GETLIST_TITLE) { @@ -4168,6 +4192,11 @@ int get_errorlist_properties(win_T *wp, dict_T *what, dict_T *retdict) status = tv_dict_add_nr(retdict, S_LEN("winid"), win->handle); } } + if ((status == OK) && (flags & QF_GETLIST_ITEMS)) { + list_T *l = tv_list_alloc(); + (void)get_errorlist(wp, qf_idx, l); + tv_dict_add_list(retdict, S_LEN("items"), l); + } if ((status == OK) && (flags & QF_GETLIST_CONTEXT)) { if (qi->qf_lists[qf_idx].qf_ctx != NULL) { @@ -4204,7 +4233,7 @@ static int qf_add_entries(qf_info_T *qi, int qf_idx, list_T *list, // Adding to existing list, use last entry. old_last = qi->qf_lists[qf_idx].qf_last; } else if (action == 'r') { - qf_free(qi, qf_idx); + qf_free_items(qi, qf_idx); qf_store_title(qi, qf_idx, title); } @@ -4312,17 +4341,24 @@ static int qf_set_properties(qf_info_T *qi, dict_T *what, int action) if (di->di_tv.vval.v_number != 0) { qf_idx = (int)di->di_tv.vval.v_number - 1; } - if (qf_idx < 0 || qf_idx >= qi->qf_listcount) { + + if ((action == ' ' || action == 'a') && qf_idx == qi->qf_listcount) { + // When creating a new list, accept qf_idx pointing to the next + // non-available list + newlist = true; + } else if (qf_idx < 0 || qf_idx >= qi->qf_listcount) { return FAIL; + } else { + newlist = false; // use the specified list } } else if (di->di_tv.v_type == VAR_STRING && strequal((const char *)di->di_tv.vval.v_string, "$") && qi->qf_listcount > 0) { qf_idx = qi->qf_listcount - 1; + newlist = false; } else { return FAIL; } - newlist = false; // use the specified list } if (newlist) { @@ -4341,6 +4377,15 @@ static int qf_set_properties(qf_info_T *qi, dict_T *what, int action) retval = OK; } } + if ((di = tv_dict_find(what, S_LEN("items"))) != NULL) { + if (di->di_tv.v_type == VAR_LIST) { + char_u *title_save = vim_strsave(qi->qf_lists[qf_idx].qf_title); + + retval = qf_add_entries(qi, qf_idx, di->di_tv.vval.v_list, + title_save, action == ' ' ? 'a' : action); + xfree(title_save); + } + } if ((di = tv_dict_find(what, S_LEN("context"))) != NULL) { tv_free(qi->qf_lists[qf_idx].qf_ctx); diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim index 828176e6be..95a7d29089 100644 --- a/src/nvim/testdir/test_quickfix.vim +++ b/src/nvim/testdir/test_quickfix.vim @@ -1817,6 +1817,73 @@ func Xproperty_tests(cchar) call test_garbagecollect_now() let m = g:Xgetlist({'context' : 1}) call assert_equal(["red", "blue", "green"], m.context) + + " Test for setting/getting items + Xexpr "" + let qfprev = g:Xgetlist({'nr':0}) + call g:Xsetlist([], ' ', {'title':'Green', + \ 'items' : [{'filename':'F1', 'lnum':10}]}) + let qfcur = g:Xgetlist({'nr':0}) + call assert_true(qfcur.nr == qfprev.nr + 1) + let l = g:Xgetlist({'items':1}) + call assert_equal('F1', bufname(l.items[0].bufnr)) + call assert_equal(10, l.items[0].lnum) + call g:Xsetlist([], 'a', {'items' : [{'filename':'F2', 'lnum':20}, + \ {'filename':'F2', 'lnum':30}]}) + let l = g:Xgetlist({'items':1}) + call assert_equal('F2', bufname(l.items[2].bufnr)) + call assert_equal(30, l.items[2].lnum) + call g:Xsetlist([], 'r', {'items' : [{'filename':'F3', 'lnum':40}]}) + let l = g:Xgetlist({'items':1}) + call assert_equal('F3', bufname(l.items[0].bufnr)) + call assert_equal(40, l.items[0].lnum) + call g:Xsetlist([], 'r', {'items' : []}) + let l = g:Xgetlist({'items':1}) + call assert_equal(0, len(l.items)) + + " Save and restore the quickfix stack + call g:Xsetlist([], 'f') + call assert_equal(0, g:Xgetlist({'nr':'$'}).nr) + Xexpr "File1:10:Line1" + Xexpr "File2:20:Line2" + Xexpr "File3:30:Line3" + let last_qf = g:Xgetlist({'nr':'$'}).nr + call assert_equal(3, last_qf) + let qstack = [] + for i in range(1, last_qf) + let qstack = add(qstack, g:Xgetlist({'nr':i, 'all':1})) + endfor + call g:Xsetlist([], 'f') + for i in range(len(qstack)) + call g:Xsetlist([], ' ', qstack[i]) + endfor + call assert_equal(3, g:Xgetlist({'nr':'$'}).nr) + call assert_equal(10, g:Xgetlist({'nr':1, 'items':1}).items[0].lnum) + call assert_equal(20, g:Xgetlist({'nr':2, 'items':1}).items[0].lnum) + call assert_equal(30, g:Xgetlist({'nr':3, 'items':1}).items[0].lnum) + call g:Xsetlist([], 'f') + + " Swap two quickfix lists + Xexpr "File1:10:Line10" + Xexpr "File2:20:Line20" + Xexpr "File3:30:Line30" + call g:Xsetlist([], 'r', {'nr':1,'title':'Colors','context':['Colors']}) + call g:Xsetlist([], 'r', {'nr':2,'title':'Fruits','context':['Fruits']}) + let l1=g:Xgetlist({'nr':1,'all':1}) + let l2=g:Xgetlist({'nr':2,'all':1}) + let l1.nr=2 + let l2.nr=1 + call g:Xsetlist([], 'r', l1) + call g:Xsetlist([], 'r', l2) + let newl1=g:Xgetlist({'nr':1,'all':1}) + let newl2=g:Xgetlist({'nr':2,'all':1}) + call assert_equal(':Fruits', newl1.title) + call assert_equal(['Fruits'], newl1.context) + call assert_equal('Line20', newl1.items[0].text) + call assert_equal(':Colors', newl2.title) + call assert_equal(['Colors'], newl2.context) + call assert_equal('Line10', newl2.items[0].text) + call g:Xsetlist([], 'f') endfunc func Test_qf_property() -- cgit From 9fe6c12e8159b656e53b6d79226aa5a1c39fdc27 Mon Sep 17 00:00:00 2001 From: James McCoy Date: Sun, 31 Dec 2017 09:50:05 -0500 Subject: doc: deprecate 'gdefault' [ci skip] --- runtime/doc/deprecated.txt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/runtime/doc/deprecated.txt b/runtime/doc/deprecated.txt index 72dfe1230e..b3073ffe58 100644 --- a/runtime/doc/deprecated.txt +++ b/runtime/doc/deprecated.txt @@ -37,8 +37,8 @@ Functions ~ *file_readable()* Obsolete name for |filereadable()|. *highlight_exists()* Obsolete name for |hlexists()|. *highlightID()* Obsolete name for |hlID()|. -*jobclose()* Obsolete name for |chanclose()| -*jobsend()* Obsolete name for |chansend()| +*jobclose()* Obsolete name for |chanclose()| +*jobsend()* Obsolete name for |chansend()| *last_buffer_nr()* Obsolete name for bufnr("$"). Modifiers ~ @@ -48,8 +48,10 @@ Modifiers ~ *:map-special* <> notation is always enabled. |cpo-<| Options ~ +'gd' +'gdefault' Enables the |:substitute| flag 'g' by default. *'fe'* 'fenc'+'enc' before Vim 6.0; no longer used. -*'highlight'* *'hl'* Names of builtin |highlight-groups| cannot be changed. +*'highlight'* *'hl'* Names of builtin |highlight-groups| cannot be changed. *'langnoremap'* Deprecated alias to 'nolangremap'. *'vi'* *'viminfo'* Deprecated alias to 'shada' option. -- cgit From f4e372c8abed86650b22fdb5b50c9ac82dae6613 Mon Sep 17 00:00:00 2001 From: Felipe Morales Date: Mon, 1 Jan 2018 14:26:45 +0100 Subject: tutor: readjust tutor for 80 char wide terminals --- runtime/tutor/en/vim-01-beginner.tutor | 625 ++++++++++++++-------------- runtime/tutor/en/vim-01-beginner.tutor.json | 74 ++-- 2 files changed, 359 insertions(+), 340 deletions(-) diff --git a/runtime/tutor/en/vim-01-beginner.tutor b/runtime/tutor/en/vim-01-beginner.tutor index 3f243a18fa..dce98d53a4 100644 --- a/runtime/tutor/en/vim-01-beginner.tutor +++ b/runtime/tutor/en/vim-01-beginner.tutor @@ -1,26 +1,26 @@ # Welcome to the VIM Tutor -Vim is a very powerful editor that has many commands, too many to explain in a -tutor such as this. This tutor is designed to describe enough of the commands -that you will be able to easily use Vim as an all-purpose editor. It is -IMPORTANT to remember that this tutor is set up to teach by use. That means -that you need to do the exercises to learn them properly. If you only read -the text, you will soon forget what is most important! +Vim is a very powerful editor that has many commands, too many to explain in +a tutor such as this. This tutor is designed to describe enough of the +commands that you will be able to easily use Vim as an all-purpose editor. +It is IMPORTANT to remember that this tutor is set up to teach by use. That +means that you need to do the exercises to learn them properly. If you only +read the text, you will soon forget what is most important! -For now, make sure that your Shift-Lock key is NOT depressed and press the `j`{normal} -key enough times to move the cursor so that Lesson 0 completely fills the -screen. +For now, make sure that your Shift-Lock key is NOT depressed and press the +`j`{normal} key enough times to move the cursor so that Lesson 0 completely +fills the screen. # Lesson 0 -NOTE: The commands in the lessons will modify the text, but those changes won't -be saved. Don't worry about messing things up; just remember that pressing -[]() and then [u](u) will undo the latest change. +NOTE: The commands in the lessons will modify the text, but those changes +won't be saved. Don't worry about messing things up; just remember that +pressing []() and then [u](u) will undo the latest change. This tutorial is interactive, and there are a few things you should know. -Pressing []() over text highlighted [like this](holy-grail) will take you to some -relevant help (hopefully), and pressing K over any word will try to do so too. -Sometimes you will be required to modify text like +Pressing []() over text highlighted [like this](holy-grail ) +will take you to some relevant help (hopefully), and pressing K over any +word will try to do so too. Sometimes you will be required to modify text like this here Once you have done the changes correctly, the ✗ sign at the left will change to ✓. I imagine you can already see how neat Vim can be. ;) @@ -33,10 +33,10 @@ or press a sequence of keys 0fd3wP$P ~~~ -Text within <'s and >'s (like ``{normal}) describes a key to press instead of text -to type. +Text within <'s and >'s (like ``{normal}) describes a key to press +instead of text to type. -Now, move to the next lesson (remember, use the `j`{normal} key to scroll down). +Now, move to the next lesson (use the `j`{normal} key to scroll down). ## Lesson 1.1: MOVING THE CURSOR @@ -63,87 +63,90 @@ NOTE: The cursor keys should also work. But using hjkl you will be able to # Lesson 1.2: EXITING VIM -!! NOTE: Before executing any of the steps below, read this entire lesson !! +!! NOTE: Before executing any of the steps below, +read this entire lesson !! - 1. Press the key (to make sure you are in [Normal mode](Normal-mode). + 1. Press the key (to make sure you are in Normal mode). - 2. Type: + 2. Type: `:q!`{vim} ``{normal}. This exits the editor, DISCARDING any changes you have made. - 3. Open vim and get back here by executing the command that got you into this - tutor. That might be: + 3. Open vim and get back here by executing the command that got you into + this tutor. That might be: :Tutor - 4. If you have these steps memorized and are confident, execute steps + 4. If you have these steps memorized and are confident, execute steps 1 through 3 to exit and re-enter the editor. NOTE: [:q!](:q) discards any changes you made. In a few lessons you will learn how to save the changes to a file. - 5. Move the cursor down to Lesson 1.3. + 5. Move the cursor down to Lesson 1.3. ## Lesson 1.3: TEXT EDITING - DELETION ** Press `x`{normal} to delete the character under the cursor. ** - 1. Move the cursor to the line below marked --->. + 1. Move the cursor to the line below marked --->. - 2. To fix the errors, move the cursor until it is on top of the + 2. To fix the errors, move the cursor until it is on top of the character to be deleted. - 3. Press [the x key](x) to delete the unwanted character. + 3. Press [the x key](x) to delete the unwanted character. - 4. Repeat steps 2 through 4 until the sentence is correct. + 4. Repeat steps 2 through 4 until the sentence is correct. The ccow jumpedd ovverr thhe mooon. - 5. Now that the line is correct, go on to Lesson 1.4. + 5. Now that the line is correct, go on to Lesson 1.4. -NOTE: As you go through this tutor, do not try to memorize, learn by usage. +NOTE: As you go through this tutor, do not try to memorize, learn by + usage. # Lesson 1.4: TEXT EDITING: INSERTION ** Press `i`{normal} to insert text. ** - 1. Move the cursor to the first line below marked --->. + 1. Move the cursor to the first line below marked --->. - 2. To make the first line the same as the second, move the cursor on top + 2. To make the first line the same as the second, move the cursor on top of the first character AFTER where the text is to be inserted. - 3. Press `i`{normal} and type in the necessary additions. + 3. Press `i`{normal} and type in the necessary additions. - 4. As each error is fixed press ``{normal} to return to Normal mode. + 4. As each error is fixed press ``{normal} to return to Normal mode. Repeat steps 2 through 4 to correct the sentence. There is text misng this . There is some text missing from this line. - 5. When you are comfortable inserting text move to Lesson 1.5. + 5. When you are comfortable inserting text move to Lesson 1.5. # Lesson 1.5: TEXT EDITING: APPENDING ** Press `A`{normal} to append text. ** - 1. Move the cursor to the first line below marked --->. - It does not matter on what character the cursor is in that line. + 1. Move the cursor to the first line below marked --->. + It does not matter on what character the cursor is in that line. - 2. Press [A](A) and type in the necessary additions. + 2. Press [A](A) and type in the necessary additions. - 3. As the text has been appended press ``{normal} to return to Normal mode. + 3. As the text has been appended press ``{normal} to return to Normal + mode. - 4. Move the cursor to the second line marked ---> and repeat - steps 2 and 3 to correct this sentence. + 4. Move the cursor to the second line marked ---> and repeat + steps 2 and 3 to correct this sentence. There is some text missing from th There is some text missing from this line. There is also some text miss There is also some text missing here. - 5. When you are comfortable appending text move to Lesson 1.6. + 5. When you are comfortable appending text move to Lesson 1.6. # Lesson 1.6: EDITING A FILE @@ -151,49 +154,51 @@ There is also some text missing here. !! NOTE: Before executing any of the steps below, read this entire lesson !! - 1. Exit this tutor as you did in Lesson 1.2: `:q!`{vim} - Or, if you have access to another terminal, do the following there. + 1. Exit this tutor as you did in Lesson 1.2: `:q!`{vim} + Or, if you have access to another terminal, do the following there. - 2. At the shell prompt type this command: + 2. At the shell prompt type this command: ~~~ sh $ vim tutor ~~~ - 'vim' is the command to start the Vim editor, 'tutor' is the name of the - file you wish to edit. Use a file that may be changed. + 'vim' is the command to start the Vim editor, 'tutor' is the name of + the file you wish to edit. Use a file that may be changed. - 3. Insert and delete text as you learned in the previous lessons. + 3. Insert and delete text as you learned in the previous lessons. - 4. Save the file with changes and exit Vim with: + 4. Save the file with changes and exit Vim with: ~~~ cmd :wq ~~~ - Note you'll need to press `` to execute the command. + Note you'll need to press `` to execute the command. - 5. If you have quit vimtutor in step 1 restart the vimtutor and move down to - the following summary. + 5. If you have quit vimtutor in step 1 restart the vimtutor and move down + to the following summary. - 6. After reading the above steps and understanding them: do it. + 6. After reading the above steps and understanding them: do it. # Lesson 1 SUMMARY - 1. The cursor is moved using either the arrow keys or the hjkl keys. + 1. The cursor is moved using either the arrow keys or the hjkl keys. h (left) j (down) k (up) l (right) - 2. To start Vim from the shell prompt type: + 2. To start Vim from the shell prompt type: ~~~ sh $ vim FILENAME ~~~ - 3. To exit Vim type: ``{normal} `:q!`{vim} ``{normal} to trash all changes. - OR type: ``{normal} `:wq`{vim} ``{normal} to save the changes. + 3. To exit Vim type: ``{normal} `:q!`{vim} ``{normal} to trash + all changes. + OR type: ``{normal} `:wq`{vim} ``{normal} to save + the changes. - 4. To delete the character at the cursor type: `x`{normal} + 4. To delete the character at the cursor type: `x`{normal} - 5. To insert or append text type: - `i`{normal} insert text ``{normal} insert before the cursor. - `A`{normal} append text ``{normal} append after the line. + 5. To insert or append text type: + `i`{normal} insert text ``{normal} insert before the cursor. + `A`{normal} append text ``{normal} append after the line. NOTE: Pressing ``{normal} will place you in Normal mode or will cancel an unwanted and partially completed command. @@ -204,38 +209,38 @@ Now continue with Lesson 2. ** Type `dw`{normal} to delete a word. ** - 1. Press ``{normal} to make sure you are in Normal mode. + 1. Press ``{normal} to make sure you are in Normal mode. - 2. Move the cursor to the line below marked --->. + 2. Move the cursor to the line below marked --->. - 3. Move the cursor to the beginning of a word that needs to be deleted. + 3. Move the cursor to the beginning of a word that needs to be deleted. - 4. Type [d](d)[w](w) to make the word disappear. + 4. Type [d](d)[w](w) to make the word disappear. There are a some words fun that don't belong paper in this sentence. - 5. Repeat steps 3 and 4 until the sentence is correct and go to Lesson 2.2. + 5. Repeat steps 3 and 4 until the sentence is correct and go to Lesson 2.2. # Lesson 2.2: MORE DELETION COMMANDS ** Type `d$`{normal} to delete to the end of the line. ** - 1. Press ``{normal} to make sure you are in Normal mode. + 1. Press ``{normal} to make sure you are in Normal mode. - 2. Move the cursor to the line below marked --->. + 2. Move the cursor to the line below marked --->. - 3. Move the cursor to the end of the correct line (AFTER the first . ). + 3. Move the cursor to the end of the correct line (AFTER the first . ). - 4. Type `d$`{normal} to delete to the end of the line. + 4. Type `d$`{normal} to delete to the end of the line. Somebody typed the end of this line twice. end of this line twice. - 5. Move on to Lesson 2.3 to understand what is happening. + 5. Move on to Lesson 2.3 to understand what is happening. # Lesson 2.3: ON OPERATORS AND MOTIONS - -Many commands that change text are made from an [operator](operator) and a [motion](navigation). +Many commands that change text are made from an [operator](operator) and +a [motion](navigation). The format for a delete command with the [d](d) delete operator is as follows: d motion @@ -251,26 +256,26 @@ The format for a delete command with the [d](d) delete operator is as follows: Thus typing `de`{normal} will delete from the cursor to the end of the word. -NOTE: Pressing just the motion while in Normal mode without an operator will - move the cursor as specified. +NOTE: Pressing just the motion while in Normal mode without an operator + will move the cursor as specified. # Lesson 2.4: USING A COUNT FOR A MOTION ** Typing a number before a motion repeats it that many times. ** - 1. Move the cursor to the start of the line marked ---> below. + 1. Move the cursor to the start of the line marked ---> below. - 2. Type `2w`{normal} to move the cursor two words forward. + 2. Type `2w`{normal} to move the cursor two words forward. - 3. Type `3e`{normal} to move the cursor to the end of the third word forward. + 3. Type `3e`{normal} to move the cursor to the end of the third word forward. - 4. Type `0`{normal} ([zero](0)) to move to the start of the line. + 4. Type `0`{normal} ([zero](0)) to move to the start of the line. - 5. Repeat steps 2 and 3 with different numbers. + 5. Repeat steps 2 and 3 with different numbers. This is just a line with words you can move around in. - 6. Move on to Lesson 2.5. + 6. Move on to Lesson 2.5. # Lesson 2.5: USING A COUNT TO DELETE MORE @@ -280,12 +285,12 @@ In the combination of the delete operator and a motion mentioned above you insert a count before the motion to delete more: d number motion - 1. Move the cursor to the first UPPER CASE word in the line marked --->. + 1. Move the cursor to the first UPPER CASE word in the line marked --->. - 2. Type `d2w`{normal} to delete the two UPPER CASE words + 2. Type `d2w`{normal} to delete the two UPPER CASE words - 3. Repeat steps 1 and 2 with a different count to delete the consecutive - UPPER CASE words with one command + 3. Repeat steps 1 and 2 with a different count to delete the consecutive + UPPER CASE words with one command This ABC DE line FGHI JK LMN OP of words is Q RS TUV cleaned up. @@ -293,13 +298,13 @@ This ABC DE line FGHI JK LMN OP of words is Q RS TUV cleaned up. ** Type `dd`{normal} to delete a whole line. ** - Due to the frequency of whole line deletion, the designers of Vi decided - it would be easier to simply type two d's to delete a line. +Due to the frequency of whole line deletion, the designers of Vi decided +it would be easier to simply type two d's to delete a line. - 1. Move the cursor to the second line in the phrase below. - 2. Type [dd](dd) to delete the line. - 3. Now move to the fourth line. - 4. Type `2dd`{normal} to delete two lines. + 1. Move the cursor to the second line in the phrase below. + 2. Type [dd](dd) to delete the line. + 3. Now move to the fourth line. + 4. Type `2dd`{normal} to delete two lines. 1) Roses are red, 2) Mud is fun, @@ -313,54 +318,56 @@ This ABC DE line FGHI JK LMN OP of words is Q RS TUV cleaned up. ** Press `u`{normal} to undo the last commands, `U`{normal} to fix a whole line. ** - 1. Move the cursor to the line below marked ---> and place it on the - first error. - 2. Type `x`{normal} to delete the first unwanted character. - 3. Now type `u`{normal} to undo the last command executed. - 4. This time fix all the errors on the line using the `x`{normal} command. - 5. Now type a capital `U`{normal} to return the line to its original state. - 6. Now type `u`{normal} a few times to undo the `U`{normal} and preceding commands. - 7. Now type ``{normal} a few times to redo the commands (undo the undo's). + 1. Move the cursor to the line below marked ---> and place it on the + first error. + 2. Type `x`{normal} to delete the first unwanted character. + 3. Now type `u`{normal} to undo the last command executed. + 4. This time fix all the errors on the line using the `x`{normal} command. + 5. Now type a capital `U`{normal} to return the line to its original state. + 6. Now type `u`{normal} a few times to undo the `U`{normal} and preceding + commands. + 7. Now type ``{normal} (Control + R) a few times to redo the commands + (undo the undos). Fiix the errors oon thhis line and reeplace them witth undo. - 8. These are very useful commands. Now move on to the Lesson 2 Summary. + 8. These are very useful commands. Now move on to the Lesson 2 Summary. # Lesson 2 SUMMARY - 1. To delete from the cursor up to the next word type: `dw`{normal} - 2. To delete from the cursor to the end of a line type: `d$`{normal} - 3. To delete a whole line type: `dd`{normal} - 4. To repeat a motion prepend it with a number: `2w`{normal} + 1. To delete from the cursor up to the next word type: `dw`{normal} + 2. To delete from the cursor to the end of a line type: `d$`{normal} + 3. To delete a whole line type: `dd`{normal} + 4. To repeat a motion prepend it with a number: `2w`{normal} - 5. The format for a change command is: + 5. The format for a change command is: operator [number] motion - where: + where: operator - is what to do, such as [d](d) for delete [number] - is an optional count to repeat the motion motion - moves over the text to operate on, such as: [w](w) (word), [$]($) (to the end of line), etc. - 6. To move to the start of the line use a zero: [0](0) + 6. To move to the start of the line use a zero: [0](0) - 7. To undo previous actions, type: `u`{normal} (lowercase u) - To undo all the changes on a line, type: `U`{normal} (capital U) - To undo the undo's, type: ``{normal} + 7. To undo previous actions, type: `u`{normal} (lowercase u) + To undo all the changes on a line, type: `U`{normal} (capital U) + To undo the undo's, type: ``{normal} # Lesson 3.1: THE PUT COMMAND ** Type `p`{normal} to put previously deleted text after the cursor. ** - 1. Move the cursor to the first ---> line below. + 1. Move the cursor to the first ---> line below. - 2. Type `dd`{normal} to delete the line and store it in a Vim register. + 2. Type `dd`{normal} to delete the line and store it in a Vim register. - 3. Move the cursor to the c) line, ABOVE where the deleted line should go. + 3. Move the cursor to the c) line, ABOVE where the deleted line should go. - 4. Type `p`{normal} to put the line below the cursor. + 4. Type `p`{normal} to put the line below the cursor. - 5. Repeat steps 2 through 4 to put all the lines in correct order. + 5. Repeat steps 2 through 4 to put all the lines in correct order. d) Can you learn too? b) Violets are blue, @@ -371,18 +378,18 @@ a) Roses are red, ** Type `rx`{normal} to replace the character at the cursor with x. ** - 1. Move the cursor to the first line below marked --->. + 1. Move the cursor to the first line below marked --->. - 2. Move the cursor so that it is on top of the first error. + 2. Move the cursor so that it is on top of the first error. - 3. Type `r`{normal} and then the character which should be there. + 3. Type `r`{normal} and then the character which should be there. - 4. Repeat steps 2 and 3 until the first line is equal to the second one. + 4. Repeat steps 2 and 3 until the first line is equal to the second one. Whan this lime was tuoed in, someone presswd some wrojg keys! When this line was typed in, someone pressed some wrong keys! - 5. Now move on to Lesson 3.3. + 5. Now move on to Lesson 3.3. NOTE: Remember that you should be learning by doing, not memorization. @@ -390,15 +397,16 @@ NOTE: Remember that you should be learning by doing, not memorization. ** To change until the end of a word, type `ce`{normal}. ** - 1. Move the cursor to the first line below marked --->. + 1. Move the cursor to the first line below marked --->. - 2. Place the cursor on the "u" in "lubw". + 2. Place the cursor on the "u" in "lubw". - 3. Type `ce`{normal} and the correct word (in this case, type "ine" ). + 3. Type `ce`{normal} and the correct word (in this case, type "ine" ). - 4. Press ``{normal} and move to the next character that needs to be changed. + 4. Press ``{normal} and move to the next character that needs to be + changed. - 5. Repeat steps 3 and 4 until the first sentence is the same as the second. + 5. Repeat steps 3 and 4 until the first sentence is the same as the second. This lubw has a few wptfd that mrrf changing usf the change operator. This line has a few words that need changing using the change operator. @@ -409,17 +417,17 @@ Notice that [c](c)e deletes the word and places you in Insert mode. ** The change operator is used with the same motions as delete. ** - 1. The change operator works in the same way as delete. The format is: + 1. The change operator works in the same way as delete. The format is: c [number] motion - 2. The motions are the same, such as `w`{normal} (word) and `$`{normal} (end of line). + 2. The motions are the same, such as `w`{normal} (word) and `$`{normal} (end of line). - 3. Move to the first line below marked --->. + 3. Move to the first line below marked --->. - 4. Move the cursor to the first error. + 4. Move the cursor to the first error. - 5. Type `c$`{normal} and type the rest of the line like the second and press ``{normal}. + 5. Type `c$`{normal} and type the rest of the line like the second and press ``{normal}. The end of this line needs some help to make it like the second. The end of this line needs to be corrected using the `c$`{normal} command. @@ -428,18 +436,18 @@ NOTE: You can use the Backspace key to correct mistakes while typing. # Lesson 3 SUMMARY - 1. To put back text that has just been deleted, type [p](p). This puts the - deleted text AFTER the cursor (if a line was deleted it will go on the - line below the cursor). + 1. To put back text that has just been deleted, type [p](p). This puts the + deleted text AFTER the cursor (if a line was deleted it will go on the + line below the cursor). - 2. To replace the character under the cursor, type [r](r) and then the - character you want to have there. + 2. To replace the character under the cursor, type [r](r) and then the + character you want to have there. - 3. The [change operator](c) allows you to change from the cursor to where the - motion takes you. Type `ce`{normal} to change from the cursor to the end of - the word, `c$`{normal} to change to the end of a line. + 3. The [change operator](c) allows you to change from the cursor to where + the motion takes you. Type `ce`{normal} to change from the cursor to the + end of the word, `c$`{normal} to change to the end of a line. - 4. The format for change is: + 4. The format for change is: c [number] motion @@ -447,42 +455,44 @@ Now go on to the next lesson. # Lesson 4.1: CURSOR LOCATION AND FILE STATUS -** Type ``{normal} to show your location in the file and the file status. +** Type ``{normal} to show your location in a file and the file status. Type `G`{normal} to move to a line in the file. ** NOTE: Read this entire lesson before executing any of the steps!! - 1. Hold down the ``{normal} key and press `g`{normal}. We call this ``{normal}. - A message will appear at the bottom of the page with the filename and the - position in the file. Remember the line number for Step 3. + 1. Hold down the ``{normal} key and press `g`{normal}. We call this + ``{normal}. A message will appear at the bottom of the page with the + filename and the position in the file. Remember the line number for + Step 3. -NOTE: You may see the cursor position in the lower right corner of the screen - This happens when the ['ruler']('ruler') option is set (see `:help 'ruler'`{vim} ). +NOTE: You may see the cursor position in the lower right corner of the + screen. This happens when the ['ruler']('ruler') option is set. + 2. Press [G](G) to move you to the bottom of the file. + Type [gg](gg) to move you to the start of the file. - 2. Press [G](G) to move you to the bottom of the file. - Type [gg](gg) to move you to the start of the file. + 3. Type the number of the line you were on and then `G`{normal}. This will + return you to the line you were on when you first pressed ``{normal}. - 3. Type the number of the line you were on and then `G`{normal}. This will - return you to the line you were on when you first pressed ``{normal}. - - 4. If you feel confident to do this, execute steps 1 through 3. + 4. If you feel confident to do this, execute steps 1 through 3. # Lesson 4.2: THE SEARCH COMMAND ** Type `/`{normal} followed by a phrase to search for the phrase. ** - 1. In Normal mode type the `/`{normal} character. Notice that it and the cursor - appear at the bottom of the screen as with the `:`{normal} command. + 1. In Normal mode type the `/`{normal} character. Notice that it and the + cursor appear at the bottom of the screen as with the `:`{normal} command. - 2. Now type 'errroor' ``{normal}. This is the word you want to search for. + 2. Now type 'errroor' ``{normal}. This is the word you want to search + for. - 3. To search for the same phrase again, simply type [n](n). - To search for the same phrase in the opposite direction, type [N](N). + 3. To search for the same phrase again, simply type [n](n). + To search for the same phrase in the opposite direction, type [N](N). - 4. To search for a phrase in the backward direction, use [?](?) instead of `/`{normal}. + 4. To search for a phrase in the backward direction, use [?](?) instead + of `/`{normal}. - 5. To go back to where you came from press ``{normal} (keep ``{normal} pressed down while - pressing the letter `o`{normal}). Repeat to go back further. ``{normal} goes forward. + 5. To go back to where you came from press ``{normal} (keep ``{normal} pressed down while pressing the letter `o`{normal}). Repeat to go back + further. ``{normal} goes forward. "errroor" is not the way to spell error; errroor is an error. @@ -493,15 +503,15 @@ NOTE: When the search reaches the end of the file it will continue at the ** Type `%`{normal} to find a matching ),], or }. ** - 1. Place the cursor on any (, [, or { in the line below marked --->. + 1. Place the cursor on any (, [, or { in the line below marked --->. - 2. Now type the [%](%) character. + 2. Now type the [%](%) character. - 3. The cursor will move to the matching parenthesis or bracket. + 3. The cursor will move to the matching parenthesis or bracket. - 4. Type `%`{normal} to move the cursor to the other matching bracket. + 4. Type `%`{normal} to move the cursor to the other matching bracket. - 5. Move the cursor to another (,),[,],{ or } and see what `%`{normal} does. + 5. Move the cursor to another (,),[,],{ or } and see what `%`{normal} does. This ( is a test line with ('s, ['s ] and {'s } in it. )) @@ -511,75 +521,79 @@ NOTE: This is very useful in debugging a program with unmatched parentheses! ** Type `:s/old/new/g` to substitute "new" for "old". ** - 1. Move the cursor to the line below marked --->. + 1. Move the cursor to the line below marked --->. - 2. Type + 2. Type ~~~ cmd :s/thee/the/ ~~~ NOTE that the [:s](:s) command only changed the first occurrence of "thee" in the line. - 3. Now type + 3. Now type ~~~ cmd :s/thee/the/g ~~~ - Adding the g [flag](:s_flags) means to substitute globally in the line, change - all occurrences of "thee" in the line. + Adding the g [flag](:s_flags) means to substitute globally in the line, + change all occurrences of "thee" in the line. Usually thee best time to see thee flowers is in thee spring. - 4. To change every occurrence of a character string between two lines, type + 4. To change every occurrence of a character string between two lines, type ~~~ cmd :#,#s/old/new/g ~~~ - where #,# are the line numbers of the range of lines where the substitution is to be done. + where #,# are the line numbers of the range of lines where the + substitution is to be done. - Type + Type ~~~ cmd - :%s/old/new/g + :%s/old/new/g ~~~ - to change every occurrence in the whole file. + to change every occurrence in the whole file. - Type + Type ~~~ cmd - :%s/old/new/gc + :%s/old/new/gc ~~~ - to find every occurrence in the whole file, with a prompt whether to substitute or not. + to find every occurrence in the whole file, with a prompt whether to + substitute or not. # Lesson 4 SUMMARY - 1. ``{normal} displays your location in the file and the file status. - `G`{normal} moves to the end of the file. - number `G`{normal} moves to that line number. - `gg`{normal} moves to the first line. + 1. ``{normal} displays your location and the file status. + `G`{normal} moves to the end of the file. + number `G`{normal} moves to that line number. + `gg`{normal} moves to the first line. - 2. Typing `/`{normal} followed by a phrase searches FORWARD for the phrase. - Typing `?`{normal} followed by a phrase searches BACKWARD for the phrase. - After a search type `n`{normal} to find the next occurrence in the same direction - or `N`{normal} to search in the opposite direction. - ``{normal} takes you back to older positions, ``{normal} to newer positions. + 2. Typing `/`{normal} followed by a phrase searches FORWARD for the phrase. + Typing `?`{normal} followed by a phrase searches BACKWARD for the phrase. + After a search type `n`{normal} to find the next occurrence in the same + direction or `N`{normal} to search in the opposite direction. + ``{normal} takes you back to older positions, ``{normal} to + newer positions. - 3. Typing `%`{normal} while the cursor is on a (,),[,],{, or } goes to its match. + 3. Typing `%`{normal} while the cursor is on a (,),[,],{, or } goes to its + match. - 4. To substitute new for the first old in a line type + 4. To substitute new for the first old in a line type ~~~ cmd :s/old/new ~~~ - To substitute new for all 'old's on a line type + To substitute new for all 'old's on a line type ~~~ cmd :s/old/new/g ~~~ - To substitute phrases between two line #'s type + To substitute phrases between two line #'s type ~~~ cmd :#,#s/old/new/g ~~~ - To substitute all occurrences in the file type + To substitute all occurrences in the file type ~~~ cmd :%s/old/new/g ~~~ - To ask for confirmation each time add 'c' + To ask for confirmation each time add 'c' ~~~ cmd :%s/old/new/gc ~~~ @@ -588,15 +602,15 @@ Usually thee best time to see thee flowers is in thee spring. ** Type `:!`{vim} followed by an external command to execute that command. ** - 1. Type the familiar command `:`{normal} to set the cursor at the bottom of the - screen. This allows you to enter a command-line command. + 1. Type the familiar command `:`{normal} to set the cursor at the bottom of + the screen. This allows you to enter a command-line command. - 2. Now type the [!](!cmd) (exclamation point) character. This allows you to - execute any external shell command. + 2. Now type the [!](!cmd) (exclamation point) character. This allows you to + execute any external shell command. - 3. As an example type "ls" following the "!" and then hit ``{normal}. This - will show you a listing of your directory, just as if you were at the - shell prompt. + 3. As an example type "ls" following the "!" and then hit ``{normal}. + This will show you a listing of your directory, just as if you were + at the shell prompt. NOTE: It is possible to execute any external command this way, also with arguments. @@ -608,24 +622,24 @@ NOTE: All `:`{vim} commands must be finished by hitting ``{normal}. ** To save the changes made to the text, type `:w`{vim} FILENAME. ** - 1. Type `:!ls`{vim} to get a listing of your directory. - You already know you must hit ``{normal} after this. + 1. Type `:!ls`{vim} to get a listing of your directory. + You already know you must hit ``{normal} after this. - 2. Choose a filename that does not exist yet, such as TEST. + 2. Choose a filename that does not exist yet, such as TEST. - 3. Now type: + 3. Now type: ~~~ cmd :w TEST ~~~ - (where TEST is the filename you chose.) + (where TEST is the filename you chose.) - 4. This saves the whole file (the Vim Tutor) under the name TEST. - To verify this, type `:!ls`{vim} again to see your directory. + 4. This saves the whole file (the Vim Tutor) under the name TEST. + To verify this, type `:!ls`{vim} again to see your directory. NOTE: If you were to exit Vim and start it again with `nvim TEST`, the file would be an exact copy of the tutor when you saved it. - 5. Now remove the file by typing: + 5. Now remove the file by typing: ~~~ cmd :!rm TEST ~~~ @@ -634,53 +648,52 @@ NOTE: If you were to exit Vim and start it again with `nvim TEST`, the file ** To save part of the file, type `v`{normal} motion `:w FILENAME`{vim}. ** - 1. Move the cursor to this line. + 1. Move the cursor to this line. - 2. Press [v](v) and move the cursor to the fifth item below. Notice that the - text is highlighted. + 2. Press [v](v) and move the cursor to the fifth item below. Notice that the + text is highlighted. - 3. Press the `:`{normal} character. At the bottom of the screen + 3. Press the `:`{normal} character. At the bottom of the screen :'<,'> will appear. - 4. Type + 4. Type `:w TEST`{vim} - where TEST is a filename that does not exist yet. Verify that you see + where TEST is a filename that does not exist yet. Verify that you see `:'<,'>w TEST`{vim} - before you press ``{normal}. + before you press ``{normal}. - 5. Vim will write the selected lines to the file TEST. Use `:!ls`{vim} to see it. - Do not remove it yet! We will use it in the next lesson. + 5. Vim will write the selected lines to the file TEST. Use `:!ls`{vim} to see it. Do not remove it yet! We will use it in the next lesson. NOTE: Pressing [v](v) starts [Visual selection](visual-mode). You can move the cursor around to make the selection bigger or smaller. Then you can - use an operator to do something with the text. For example, `d`{normal} deletes - the text. + use an operator to do something with the text. For example, `d`{normal} + deletes the text. # Lesson 5.4: RETRIEVING AND MERGING FILES ** To insert the contents of a file, type `:r FILENAME`{vim}. ** - 1. Place the cursor just above this line. + 1. Place the cursor just above this line. NOTE: After executing Step 2 you will see text from Lesson 5.3. Then move DOWN to see this lesson again. - 2. Now retrieve your TEST file using the command + 2. Now retrieve your TEST file using the command `:r TEST`{vim} where TEST is the name of the file you used. The file you retrieve is placed below the cursor line. - 3. To verify that a file was retrieved, cursor back and notice that there - are now two copies of Lesson 5.3, the original and the file version. + 3. To verify that a file was retrieved, cursor back and notice that there + are now two copies of Lesson 5.3, the original and the file version. NOTE: You can also read the output of an external command. For example, @@ -690,39 +703,39 @@ NOTE: You can also read the output of an external command. For example, # Lesson 5 SUMMARY - 1. [:!command](:!cmd) executes an external command. + 1. [:!command](:!cmd) executes an external command. Some useful examples are: `:!ls`{vim} - shows a directory listing `:!rm FILENAME`{vim} - removes file FILENAME - 2. [:w](:w) FILENAME writes the current Vim file to disk with - name FILENAME. + 2. [:w](:w) FILENAME writes the current Vim file to disk with + name FILENAME. - 3. [v](v) motion :w FILENAME saves the Visually selected lines in file - FILENAME. + 3. [v](v) motion :w FILENAME saves the Visually selected lines in file + FILENAME. - 4. [:r](:r) FILENAME retrieves disk file FILENAME and puts it - below the cursor position. + 4. [:r](:r) FILENAME retrieves disk file FILENAME and puts it + below the cursor position. - 5. [:r !dir](:r!) reads the output of the dir command and - puts it below the cursor position. + 5. [:r !dir](:r!) reads the output of the dir command and + puts it below the cursor position. # Lesson 6.1: THE OPEN COMMAND ** Type `o`{normal} to open a line below the cursor and place you in Insert mode. ** - 1. Move the cursor to the line below marked --->. + 1. Move the cursor to the line below marked --->. - 2. Type the lowercase letter `o`{normal} to [open](o) up a line BELOW the cursor and place - you in Insert mode. + 2. Type the lowercase letter `o`{normal} to [open](o) up a line BELOW the + cursor and place you in Insert mode. - 3. Now type some text and press ``{normal} to exit Insert mode. + 3. Now type some text and press ``{normal} to exit Insert mode. After typing `o`{normal} the cursor is placed on the open line in Insert mode. - 4. To open up a line ABOVE the cursor, simply type a [capital O](O), rather - than a lowercase `o`{normal}. Try this on the line below. + 4. To open up a line ABOVE the cursor, simply type a [capital O](O), rather + than a lowercase `o`{normal}. Try this on the line below. Open up a line above this by typing O while the cursor is on this line. @@ -730,37 +743,39 @@ Open up a line above this by typing O while the cursor is on this line. ** Type `a`{normal} to insert text AFTER the cursor. ** - 1. Move the cursor to the start of the line below marked --->. + 1. Move the cursor to the start of the line below marked --->. - 2. Press `e`{normal} until the cursor is on the end of "li". + 2. Press `e`{normal} until the cursor is on the end of "li". - 3. Type the lowercase letter `a`{normal} to [append](a) text AFTER the cursor. + 3. Type the lowercase letter `a`{normal} to [append](a) text AFTER the + cursor. - 4. Complete the word like the line below it. Press ``{normal} to exit Insert - mode. + 4. Complete the word like the line below it. Press ``{normal} to exit + Insert mode. - 5. Use `e`{normal} to move to the next incomplete word and repeat steps 3 and 4. + 5. Use `e`{normal} to move to the next incomplete word and repeat steps 3 + and 4. This li will allow you to pract appendi text to a line. This line will allow you to practice appending text to a line. -NOTE: [a](a), [i](i) and [A](A) all go to the same Insert mode, the only difference is where - the characters are inserted. +NOTE: [a](a), [i](i) and [A](A) all go to the same Insert mode, the only + difference is where the characters are inserted. # Lesson 6.3: ANOTHER WAY TO REPLACE ** Type a capital `R`{normal} to replace more than one character. ** - 1. Move the cursor to the first line below marked --->. Move the cursor to + 1. Move the cursor to the first line below marked --->. Move the cursor to the beginning of the first "xxx". - 2. Now press `R`{normal} ([capital R](R)) and type the number below it in the second line, so that it - replaces the "xxx". + 2. Now press `R`{normal} ([capital R](R)) and type the number below it in the + second line, so that it replaces the "xxx". - 3. Press ``{normal} to leave [Replace mode](mode-replace). Notice that the rest of the line - remains unmodified. + 3. Press ``{normal} to leave [Replace mode](mode-replace). Notice that + the rest of the line remains unmodified. - 4. Repeat the steps to replace the remaining "xxx". + 4. Repeat the steps to replace the remaining "xxx". Adding 123 to xxx gives you xxx. Adding 123 to 456 gives you 579. @@ -772,51 +787,53 @@ NOTE: Replace mode is like Insert mode, but every typed character deletes an ** Use the `y`{normal} operator to copy text and `p`{normal} to paste it. ** - 1. Go to the line marked with ---> below and place the cursor after "a)". + 1. Go to the line marked with ---> below and place the cursor after "a)". - 2. Start Visual mode with `v`{normal} and move the cursor to just before "first". + 2. Start Visual mode with `v`{normal} and move the cursor to just before + "first". - 3. Type `y`{normal} to [yank](yank) (copy) the highlighted text. + 3. Type `y`{normal} to [yank](yank) (copy) the highlighted text. - 4. Move the cursor to the end of the next line: `j$`{normal} + 4. Move the cursor to the end of the next line: `j$`{normal} - 5. Type `p`{normal} to [put](put) (paste) the text. + 5. Type `p`{normal} to [put](put) (paste) the text. - 6. Press `a`{normal} and then type "second". Press ``{normal} to leave Insert mode. + 6. Press `a`{normal} and then type "second". Press ``{normal} to leave + Insert mode. - 7. Use Visual mode to select " item.", yank it with `y`{normal}, move to the end of - the next line with `j$`{normal} and put the text there with `p`{normal}. + 7. Use Visual mode to select "item.", yank it with `y`{normal}, move to the + end of the next line with `j$`{normal} and put the text there with `p`{normal} a) This is the first item. b) -NOTE: you can also use `y`{normal} as an operator; `yw`{normal} yanks one word. +NOTE: you can use `y`{normal} as an operator: `yw`{normal} yanks one word. # Lesson 6.5: SET OPTION ** Set an option so a search or substitute ignores case. ** - 1. Search for 'ignore' by entering: `/ignore` - Repeat several times by pressing `n`{normal}. + 1. Search for 'ignore' by entering: `/ignore` + Repeat several times by pressing `n`{normal}. - 2. Set the 'ic' (Ignore case) option by entering: + 2. Set the 'ic' (Ignore case) option by entering: ~~~ cmd :set ic ~~~ - 3. Now search for 'ignore' again by pressing `n`{normal}. - Notice that Ignore and IGNORE are now also found. + 3. Now search for 'ignore' again by pressing `n`{normal}. + Notice that Ignore and IGNORE are now also found. - 4. Set the 'hlsearch' and 'incsearch' options: + 4. Set the 'hlsearch' and 'incsearch' options: ~~~ cmd :set hls is ~~~ - 5. Now type the search command again and see what happens: /ignore + 5. Now type the search command again and see what happens: /ignore - 6. To disable ignoring case enter: + 6. To disable ignoring case enter: ~~~ cmd :set noic ~~~ - 7. To toggle the value of a setting, prepend it with "inv": + 7. To toggle the value of a setting, prepend it with "inv": ~~~ cmd :set invic ~~~ @@ -829,19 +846,20 @@ NOTE: If you want to ignore case for just one search command, use [\c](/\c) # Lesson 6 SUMMARY - 1. Type `o`{normal} to open a line BELOW the cursor and start Insert mode. - Type `O`{normal} to open a line ABOVE the cursor. + 1. Type `o`{normal} to open a line BELOW the cursor and start Insert mode. + Type `O`{normal} to open a line ABOVE the cursor. - 2. Type `a`{normal} to insert text AFTER the cursor. - Type `A`{normal} to insert text after the end of the line. + 2. Type `a`{normal} to insert text AFTER the cursor. + Type `A`{normal} to insert text after the end of the line. - 3. The `e`{normal} command moves to the end of a word. + 3. The `e`{normal} command moves to the end of a word. - 4. The `y`{normal} operator yanks (copies) text, `p`{normal} puts (pastes) it. + 4. The `y`{normal} operator copies text, `p`{normal} pastes it. - 5. Typing a capital `R`{normal} enters Replace mode until ``{normal} is pressed. + 5. Typing a capital `R`{normal} enters Replace mode until ``{normal} is + pressed. - 6. Typing "[:set](:set) xxx" sets the option "xxx". Some options are: + 6. Typing "[:set](:set) xxx" sets the option "xxx". Some options are: 'ic' 'ignorecase' ignore upper/lower case when searching 'is' 'incsearch' show partial matches for a search phrase @@ -849,11 +867,11 @@ NOTE: If you want to ignore case for just one search command, use [\c](/\c) You can either use the long or the short option name. - 7. Prepend "no" to switch an option off: + 7. Prepend "no" to switch an option off: ~~~ cmd :set noic ~~~ - 8. Prepend "inv" to toggle an option: + 8. Prepend "inv" to toggle an option: ~~~ cmd :set invic ~~~ @@ -870,7 +888,7 @@ these three: `:help`{vim} Read the text in the help window to find out how the help works. -Type ``{normal} to jump from one window to another. +Type ``{normal} to jump from one window to another. Type `:q`{vim} to close the help window. You can find help on just about any subject, by giving an argument to the @@ -888,13 +906,13 @@ You can find help on just about any subject, by giving an argument to the Vim has many more features than Vi, but most of them are disabled by default. To start using more features you have to create a "vimrc" file. - 1. Start editing the "vimrc" file. This depends on your system: + 1. Start editing the "vimrc" file. This depends on your system: `:e ~/.config/nvim/init.vim`{vim} for Unix-like systems - 2. Now read the example "vimrc" file contents: + 2. Now read the example "vimrc" file contents: `:r $VIMRUNTIME/vimrc_example.vim`{vim} - 3. Write the file with: + 3. Write the file with: `:w`{vim} The next time you start Vim it will use syntax highlighting. @@ -903,36 +921,38 @@ default. To start using more features you have to create a "vimrc" file. # Lesson 7.3: COMPLETION -** Command line completion with ``{normal} and ``{normal}. ** +** Command line completion with ``{normal} and ``{normal}. ** - 1. Look what files exist in the directory: `:!ls`{vim} + 1. Look what files exist in the directory: `:!ls`{vim} - 2. Type the start of a command: `:e`{vim} + 2. Type the start of a command: `:e`{vim} - 3. Press ``{normal} and Vim will show a list of commands that start with "e". + 3. Press ``{normal} and Vim will show a list of commands that start + with "e". - 4. Press ``{normal} and Vim will complete the command name to ":edit". + 4. Press ``{normal} and Vim will complete the command name to ":edit". - 5. Now add a space and the start of an existing file name: `:edit FIL`{vim} + 5. Now add a space and the start of an existing file name: `:edit FIL`{vim} - 6. Press ``{normal}. Vim will complete the name (if it is unique). + 6. Press ``{normal}. Vim will complete the name (if it is unique). -NOTE: Completion works for many commands. It is especially useful for `:help`{vim}. +NOTE: Completion works for many commands. It is especially useful for + `:help`{vim}. # Lesson 7 SUMMARY - 1. Type `:help`{vim} - or press ``{normal} or ``{normal} to open a help window. + 1. Type `:help`{vim} + or press ``{normal} or ``{normal} to open a help window. - 2. Type `:help TOPIC`{vim} to find help on TOPIC. + 2. Type `:help TOPIC`{vim} to find help on TOPIC. - 3. Type ``{normal} to jump to another window + 3. Type ``{normal} to jump to another window - 4. Type `:q`{vim} to close the help window + 4. Type `:q`{vim} to close the help window - 5. Create a vimrc startup script to keep your preferred settings. + 5. Create a vimrc startup script to keep your preferred settings. - 6. While in command mode, press ``{normal} to see possible completions. + 6. While in command mode, press ``{normal} to see possible completions. Press ``{normal} to use one completion. # CONCLUSION @@ -941,7 +961,8 @@ This was intended to give a brief overview of the Vim editor, just enough to allow you to use the editor fairly easily. It is far from complete as Vim has many many more commands. Consult the help often. -There are many resources online to learn more about vim. Here's a bunch of them: +There are many resources online to learn more about vim. Here's a bunch of +them: - *Learn Vim Progressively*: http://yannesposito.com/Scratch/en/blog/Learn-Vim-Progressively/ - *Learning Vim in 2014*: http://benmccormick.org/learning-vim-in-2014/ @@ -951,8 +972,8 @@ There are many resources online to learn more about vim. Here's a bunch of them: - *7 Habits of Effective Text Editing*: http://www.moolenaar.net/habits.html - *vim-galore*: https://github.com/mhinz/vim-galore -If you prefer a book, *Practical Vim* by Drew Neil is recommended often (the sequel, *Modern -Vim*, includes material specific to nvim). +If you prefer a book, *Practical Vim* by Drew Neil is recommended often +(the sequel, *Modern Vim*, includes material specific to nvim). This tutorial was written by Michael C. Pierce and Robert K. Ware, Colorado School of Mines using ideas supplied by Charles Smith, Colorado State diff --git a/runtime/tutor/en/vim-01-beginner.tutor.json b/runtime/tutor/en/vim-01-beginner.tutor.json index 444bd7c4b7..2f87d7543f 100644 --- a/runtime/tutor/en/vim-01-beginner.tutor.json +++ b/runtime/tutor/en/vim-01-beginner.tutor.json @@ -1,45 +1,43 @@ { "expect": { "24": -1, - "102": "The cow jumped over the moon.", - "122": "There is some text missing from this line.", - "123": "There is some text missing from this line.", - "141": "There is some text missing from this line.", - "142": "There is some text missing from this line.", - "143": "There is also some text missing here.", - "144": "There is also some text missing here.", - "215": "There are some words that don't belong in this sentence.", - "231": "Somebody typed the end of this line twice.", - "271": -1, - "290": "This line of words is cleaned up.", - "304": -1, - "305": -1, - "306": -1, - "307": -1, - "308": -1, + "103": "The cow jumped over the moon.", + "124": "There is some text missing from this line.", + "125": "There is some text missing from this line.", + "144": "There is some text missing from this line.", + "145": "There is some text missing from this line.", + "146": "There is also some text missing here.", + "147": "There is also some text missing here.", + "220": "There are some words that don't belong in this sentence.", + "236": "Somebody typed the end of this line twice.", + "276": -1, + "295": "This line of words is cleaned up.", "309": -1, "310": -1, - "325": "Fix the errors on this line and replace them with undo.", - "365": -1, - "366": -1, - "367": -1, - "368": -1, - "382": "When this line was typed in, someone pressed some wrong keys!", - "383": "When this line was typed in, someone pressed some wrong keys!", - "403": "This line has a few words that need changing using the change operator.", - "404": "This line has a few words that need changing using the change operator.", - "424": "The end of this line needs to be corrected using the c$ command.", - "425": "The end of this line needs to be corrected using the c$ command.", - "487": -1, - "506": -1, - "531": "Usually the best time to see the flowers is in the spring.", - "722": -1, - "727": -1, - "744": "This line will allow you to practice appending text to a line.", - "745": "This line will allow you to practice appending text to a line.", - "765": "Adding 123 to 456 gives you 579.", - "766": "Adding 123 to 456 gives you 579.", - "790": "a) This is the first item.", - "791": " b) This is the second item." + "311": -1, + "312": -1, + "313": -1, + "314": -1, + "315": -1, + "332": "Fix the errors on this line and replace them with undo.", + "372": -1, + "373": -1, + "374": -1, + "375": -1, + "389": "When this line was typed in, someone pressed some wrong keys!", + "390": "When this line was typed in, someone pressed some wrong keys!", + "411": "This line has a few words that need changing using the change operator.", + "412": "This line has a few words that need changing using the change operator.", + "432": "The end of this line needs to be corrected using the c$ command.", + "433": "The end of this line needs to be corrected using the c$ command.", + "497": -1, + "516": -1, + "541": "Usually the best time to see the flowers is in the spring.", + "759": "This line will allow you to practice appending text to a line.", + "760": "This line will allow you to practice appending text to a line.", + "780": "Adding 123 to 456 gives you 579.", + "781": "Adding 123 to 456 gives you 579.", + "807": "a) This is the first item.", + "808": " b) This is the second item." } } -- cgit From 1d8c612f788b1a5c8d74814db4654462e0224831 Mon Sep 17 00:00:00 2001 From: KunMing Xie Date: Mon, 1 Jan 2018 23:08:26 +0800 Subject: vim-patch:8.0.0339: illegal memory access with vi' (#7794) Problem: Illegal memory access with vi' Solution: For quoted text objects bail out if the Visual area spans more than one line. https://github.com/vim/vim/commit/46522af72424c7fadfa7a4cbba3dd21b82d19131 --- src/nvim/search.c | 5 +++++ src/nvim/testdir/test_visual.vim | 7 +++++++ 2 files changed, 12 insertions(+) diff --git a/src/nvim/search.c b/src/nvim/search.c index 387614fd09..1eb1a25a19 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -3669,6 +3669,11 @@ current_quote ( /* Correct cursor when 'selection' is exclusive */ if (VIsual_active) { + // this only works within one line + if (VIsual.lnum != curwin->w_cursor.lnum) { + return false; + } + vis_bef_curs = lt(VIsual, curwin->w_cursor); if (*p_sel == 'e' && vis_bef_curs) dec_cursor(); diff --git a/src/nvim/testdir/test_visual.vim b/src/nvim/testdir/test_visual.vim index 1694adbd32..69607e642c 100644 --- a/src/nvim/testdir/test_visual.vim +++ b/src/nvim/testdir/test_visual.vim @@ -43,3 +43,10 @@ func Test_dotregister_paste() call assert_equal('hello world world', getline(1)) q! endfunc + +func Test_Visual_inner_quote() + new + normal oxX + normal vki' + bwipe! +endfunc -- cgit From 321a46b724ccb2e574875cf18c8d280f8516527c Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Mon, 1 Jan 2018 16:21:55 +0100 Subject: vim-patch: b:changedtick-related patches vim-patch:8.0.0334 vim-patch:8.0.0335 vim-patch:8.0.0343 vim-patch:8.0.0345 Problem: Can't access b:changedtick from a dict reference. Solution: Make changedtick a member of the b: dict. (inspired by neovim vim/vim#6112) https://github.com/vim/vim/commit/79518e2ace5fce7b9c49060e462a6e935dba0a84 vim-patch:8.0.0343: b:changedtick can be unlocked Problem: b:changedtick can be unlocked, even though it has no effect. (Nikolai Pavlov) Solution: Add a check and error E940. (closes #1496) vim-patch:8.0.0345: islocked('d.changedtick') does not work Problem: islocked('d.changedtick') does not work. Solution: Make it work. vim-patch:8.0.0335: functions test fails Problem: Functions test fails. Solution: Use the right buffer number. https://github.com/vim/vim/commit/507647da3151f7ffccac1b217936240daa79849c --- src/nvim/testdir/Makefile | 1 + src/nvim/testdir/test_alot.vim | 3 +- src/nvim/testdir/test_changedtick.vim | 57 +++++++++++++++++++++++++++++++++++ src/nvim/testdir/test_functions.vim | 42 ++++++++++++++++++++++++++ 4 files changed, 102 insertions(+), 1 deletion(-) create mode 100644 src/nvim/testdir/test_changedtick.vim diff --git a/src/nvim/testdir/Makefile b/src/nvim/testdir/Makefile index 1f8cf8a0e7..5af8dd20cd 100644 --- a/src/nvim/testdir/Makefile +++ b/src/nvim/testdir/Makefile @@ -42,6 +42,7 @@ SCRIPTS ?= $(SCRIPTS_DEFAULT) NEW_TESTS ?= \ test_autocmd.res \ test_bufwintabinfo.res \ + test_changedtick.res \ test_charsearch.res \ test_cmdline.res \ test_command_count.res \ diff --git a/src/nvim/testdir/test_alot.vim b/src/nvim/testdir/test_alot.vim index c1f6405579..5ce6799b99 100644 --- a/src/nvim/testdir/test_alot.vim +++ b/src/nvim/testdir/test_alot.vim @@ -2,9 +2,10 @@ " This makes testing go faster, since Vim doesn't need to restart. source test_assign.vim +source test_changedtick.vim source test_cursor_func.vim -source test_execute_func.vim source test_ex_undo.vim +source test_execute_func.vim source test_expr.vim source test_expr_utf8.vim source test_feedkeys.vim diff --git a/src/nvim/testdir/test_changedtick.vim b/src/nvim/testdir/test_changedtick.vim new file mode 100644 index 0000000000..3a91bb54aa --- /dev/null +++ b/src/nvim/testdir/test_changedtick.vim @@ -0,0 +1,57 @@ +" Tests for b:changedtick + +func Test_changedtick_increments() + new + " New buffer has an empty line, tick starts at 2. + let expected = 2 + call assert_equal(expected, b:changedtick) + call assert_equal(expected, b:['changedtick']) + call setline(1, 'hello') + let expected += 1 + call assert_equal(expected, b:changedtick) + call assert_equal(expected, b:['changedtick']) + undo + " Somehow undo counts as two changes. + let expected += 2 + call assert_equal(expected, b:changedtick) + call assert_equal(expected, b:['changedtick']) + bwipe! +endfunc + +func Test_changedtick_dict_entry() + let d = b: + call assert_equal(b:changedtick, d['changedtick']) +endfunc + +func Test_changedtick_bdel() + new + let bnr = bufnr('%') + let v = b:changedtick + bdel + " Delete counts as a change too. + call assert_equal(v + 1, getbufvar(bnr, 'changedtick')) +endfunc + +func Test_changedtick_islocked() + call assert_equal(0, islocked('b:changedtick')) + let d = b: + call assert_equal(0, islocked('d.changedtick')) +endfunc + +func Test_changedtick_fixed() + call assert_fails('let b:changedtick = 4', 'E46:') + call assert_fails('let b:["changedtick"] = 4', 'E46:') + + call assert_fails('lockvar b:changedtick', 'E940:') + call assert_fails('lockvar b:["changedtick"]', 'E46:') + call assert_fails('unlockvar b:changedtick', 'E940:') + call assert_fails('unlockvar b:["changedtick"]', 'E46:') + call assert_fails('unlet b:changedtick', 'E795:') + call assert_fails('unlet b:["changedtick"]', 'E46:') + + let d = b: + call assert_fails('lockvar d["changedtick"]', 'E46:') + call assert_fails('unlockvar d["changedtick"]', 'E46:') + call assert_fails('unlet d["changedtick"]', 'E46:') + +endfunc diff --git a/src/nvim/testdir/test_functions.vim b/src/nvim/testdir/test_functions.vim index 0ce034b63e..398e9ab331 100644 --- a/src/nvim/testdir/test_functions.vim +++ b/src/nvim/testdir/test_functions.vim @@ -311,3 +311,45 @@ func! Test_mode() bwipe! iunmap endfunc + +func Test_getbufvar() + let bnr = bufnr('%') + let b:var_num = '1234' + let def_num = '5678' + call assert_equal('1234', getbufvar(bnr, 'var_num')) + call assert_equal('1234', getbufvar(bnr, 'var_num', def_num)) + + let bd = getbufvar(bnr, '') + call assert_equal('1234', bd['var_num']) + call assert_true(exists("bd['changedtick']")) + call assert_equal(2, len(bd)) + + let bd2 = getbufvar(bnr, '', def_num) + call assert_equal(bd, bd2) + + unlet b:var_num + call assert_equal(def_num, getbufvar(bnr, 'var_num', def_num)) + call assert_equal('', getbufvar(bnr, 'var_num')) + + let bd = getbufvar(bnr, '') + call assert_equal(1, len(bd)) + let bd = getbufvar(bnr, '',def_num) + call assert_equal(1, len(bd)) + + call assert_equal('', getbufvar(9999, '')) + call assert_equal(def_num, getbufvar(9999, '', def_num)) + unlet def_num + + call assert_equal(0, getbufvar(bnr, '&autoindent')) + call assert_equal(0, getbufvar(bnr, '&autoindent', 1)) + + " Open new window with forced option values + set fileformats=unix,dos + new ++ff=dos ++bin ++enc=iso-8859-2 + call assert_equal('dos', getbufvar(bufnr('%'), '&fileformat')) + call assert_equal(1, getbufvar(bufnr('%'), '&bin')) + call assert_equal('iso-8859-2', getbufvar(bufnr('%'), '&fenc')) + close + + set fileformats& +endfunc -- cgit From c5f9762b7e40d23e6a0cfc8f808761a084223859 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Mon, 1 Jan 2018 16:46:03 +0100 Subject: vim-patch:8.0.0336: flags of :substitute not sufficiently tested Problem: Flags of :substitute not sufficiently tested. Solution: Test up to two letter flag combinations. (James McCoy, closes vim/vim#1479) https://github.com/vim/vim/commit/8c50d50b6e19b755d7bad7b2724d14ead29364a7 --- src/nvim/testdir/test_substitute.vim | 67 ++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/src/nvim/testdir/test_substitute.vim b/src/nvim/testdir/test_substitute.vim index e2b6de03c3..a3bc04dcd0 100644 --- a/src/nvim/testdir/test_substitute.vim +++ b/src/nvim/testdir/test_substitute.vim @@ -39,3 +39,70 @@ function! Test_multiline_subst() call assert_equal('xxxxx', getline(13)) enew! endfunction + +function! Test_substitute_variants() + " Validate that all the 2-/3-letter variants which embed the flags into the + " command name actually work. + enew! + let ln = 'Testing string' + let variants = [ + \ { 'cmd': ':s/Test/test/c', 'exp': 'testing string', 'prompt': 'y' }, + \ { 'cmd': ':s/foo/bar/ce', 'exp': ln }, + \ { 'cmd': ':s/t/r/cg', 'exp': 'Tesring srring', 'prompt': 'a' }, + \ { 'cmd': ':s/t/r/ci', 'exp': 'resting string', 'prompt': 'y' }, + \ { 'cmd': ':s/t/r/cI', 'exp': 'Tesring string', 'prompt': 'y' }, + \ { 'cmd': ':s/t/r/cn', 'exp': ln }, + \ { 'cmd': ':s/t/r/cp', 'exp': 'Tesring string', 'prompt': 'y' }, + \ { 'cmd': ':s/t/r/cl', 'exp': 'Tesring string', 'prompt': 'y' }, + \ { 'cmd': ':s/t/r/gc', 'exp': 'Tesring srring', 'prompt': 'a' }, + \ { 'cmd': ':s/foo/bar/ge', 'exp': ln }, + \ { 'cmd': ':s/t/r/g', 'exp': 'Tesring srring' }, + \ { 'cmd': ':s/t/r/gi', 'exp': 'resring srring' }, + \ { 'cmd': ':s/t/r/gI', 'exp': 'Tesring srring' }, + \ { 'cmd': ':s/t/r/gn', 'exp': ln }, + \ { 'cmd': ':s/t/r/gp', 'exp': 'Tesring srring' }, + \ { 'cmd': ':s/t/r/gl', 'exp': 'Tesring srring' }, + \ { 'cmd': ':s//r/gr', 'exp': 'Testr strr' }, + \ { 'cmd': ':s/t/r/ic', 'exp': 'resting string', 'prompt': 'y' }, + \ { 'cmd': ':s/foo/bar/ie', 'exp': ln }, + \ { 'cmd': ':s/t/r/i', 'exp': 'resting string' }, + \ { 'cmd': ':s/t/r/iI', 'exp': 'Tesring string' }, + \ { 'cmd': ':s/t/r/in', 'exp': ln }, + \ { 'cmd': ':s/t/r/ip', 'exp': 'resting string' }, + \ { 'cmd': ':s//r/ir', 'exp': 'Testr string' }, + \ { 'cmd': ':s/t/r/Ic', 'exp': 'Tesring string', 'prompt': 'y' }, + \ { 'cmd': ':s/foo/bar/Ie', 'exp': ln }, + \ { 'cmd': ':s/t/r/Ig', 'exp': 'Tesring srring' }, + \ { 'cmd': ':s/t/r/Ii', 'exp': 'resting string' }, + \ { 'cmd': ':s/t/r/I', 'exp': 'Tesring string' }, + \ { 'cmd': ':s/t/r/Ip', 'exp': 'Tesring string' }, + \ { 'cmd': ':s/t/r/Il', 'exp': 'Tesring string' }, + \ { 'cmd': ':s//r/Ir', 'exp': 'Testr string' }, + \ { 'cmd': ':s//r/rc', 'exp': 'Testr string', 'prompt': 'y' }, + \ { 'cmd': ':s//r/rg', 'exp': 'Testr strr' }, + \ { 'cmd': ':s//r/ri', 'exp': 'Testr string' }, + \ { 'cmd': ':s//r/rI', 'exp': 'Testr string' }, + \ { 'cmd': ':s//r/rn', 'exp': 'Testing string' }, + \ { 'cmd': ':s//r/rp', 'exp': 'Testr string' }, + \ { 'cmd': ':s//r/rl', 'exp': 'Testr string' }, + \ { 'cmd': ':s//r/r', 'exp': 'Testr string' }, + \] + + for var in variants + for run in [1, 2] + let cmd = var.cmd + if run == 2 && cmd =~ "/.*/.*/." + " Change :s/from/to/{flags} to :s{flags} + let cmd = substitute(cmd, '/.*/', '', '') + endif + call setline(1, [ln]) + let msg = printf('using "%s"', cmd) + let @/='ing' + let v:errmsg = '' + call feedkeys(cmd . "\" . get(var, 'prompt', ''), 'ntx') + " No error should exist (matters for testing e flag) + call assert_equal('', v:errmsg, msg) + call assert_equal(var.exp, getline('.'), msg) + endfor + endfor +endfunction -- cgit From 6a9f2cdc6837f7e6989ad1f5454a555682a27baa Mon Sep 17 00:00:00 2001 From: Felipe Morales Date: Mon, 1 Jan 2018 19:08:01 +0100 Subject: tutor: install metadata files for tutor documents --- runtime/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/CMakeLists.txt b/runtime/CMakeLists.txt index 6dbe049232..52a8609fec 100644 --- a/runtime/CMakeLists.txt +++ b/runtime/CMakeLists.txt @@ -138,7 +138,7 @@ endforeach() file(GLOB_RECURSE RUNTIME_FILES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} rgb.txt - *.vim *.dict *.py *.rb *.ps *.tutor) + *.vim *.dict *.py *.rb *.ps *.tutor *.tutor.json) foreach(F ${RUNTIME_FILES}) get_filename_component(BASEDIR ${F} PATH) -- cgit From 60716371e97dae916c6525e9ba840aae562069bb Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Mon, 1 Jan 2018 19:38:18 +0100 Subject: tui: support TERM=konsole-256color TERM=konsole-256color is recognized by ncurses. TERM=konsole-xterm might be more clever, but should not be necessary (for Nvim at least), we already special-case Konsole in various places. We may need to clean up some areas that currently assume Konsole always "pretends xterm" (`TERM=xterm-256color`), though I didn't find any such cases. ref #6403 ref https://github.com/neovim/neovim/issues/6403#issuecomment-348713346 --- runtime/doc/term.txt | 4 ++-- src/nvim/tui/terminfo.c | 4 ++-- src/nvim/tui/tui.c | 3 ++- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/runtime/doc/term.txt b/runtime/doc/term.txt index a694185fc9..6cf62f9bb5 100644 --- a/runtime/doc/term.txt +++ b/runtime/doc/term.txt @@ -38,13 +38,13 @@ Otherwise Nvim cannot know what sequences your terminal expects, and weird or sub-optimal behavior will result (scrolling quirks, wrong colors, etc.). $TERM is also important because it is mirrored by SSH to the remote session, -unlike other common client-end environment variables ($COLORTERM, -$XTERM_VERSION, $VTE_VERSION, $KONSOLE_PROFILE_NAME, $TERM_PROGRAM, ...). +unlike most other environment variables. For this terminal Set $TERM to |builtin-terms| ------------------------------------------------------------------------- iTerm (original) iterm, iTerm.app N iTerm2 (new capabilities) iterm2, iTerm2.app Y + Konsole konsole-256color N anything libvte-based vte, vte-256color Y (e.g. GNOME Terminal) (aliases: gnome, gnome-256color) tmux tmux, tmux-256color Y diff --git a/src/nvim/tui/terminfo.c b/src/nvim/tui/terminfo.c index 492c1c5e9c..2f07e83158 100644 --- a/src/nvim/tui/terminfo.c +++ b/src/nvim/tui/terminfo.c @@ -90,8 +90,8 @@ bool terminfo_is_term_family(const char *term, const char *family) size_t tlen = strlen(term); size_t flen = strlen(family); return tlen >= flen - && 0 == memcmp(term, family, flen) \ - // Per the commentary in terminfo, minus sign is the suffix separator. + && 0 == memcmp(term, family, flen) + // Per commentary in terminfo, minus is the only valid suffix separator. && ('\0' == term[flen] || '-' == term[flen]); } diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index dbe1222dc0..df5b41a64b 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -222,7 +222,8 @@ static void terminfo_start(UI *ui) const char *vte_version_env = os_getenv("VTE_VERSION"); long vte_version = vte_version_env ? strtol(vte_version_env, NULL, 10) : 0; bool iterm_env = termprg && strstr(termprg, "iTerm.app"); - bool konsole = os_getenv("KONSOLE_PROFILE_NAME") + bool konsole = terminfo_is_term_family(term, "konsole") + || os_getenv("KONSOLE_PROFILE_NAME") || os_getenv("KONSOLE_DBUS_SESSION"); patch_terminfo_bugs(data, term, colorterm, vte_version, konsole, iterm_env); -- cgit From 90aae43984ce3b6a5193ce3bc18293c28db6c606 Mon Sep 17 00:00:00 2001 From: James McCoy Date: Mon, 1 Jan 2018 21:00:07 -0500 Subject: travis: Use Ubuntu's clang instead of llvm's repo The llvm repos commonly have access issues, so removing them will improve stability of the Travis builds. Filtering check_log's output through asan_symbolize also avoids the version dance every time a new clang version makes its way into Travis. --- .travis.yml | 10 ++++------ ci/common/test.sh | 2 +- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2bab1635ad..35a3c76fce 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,7 +10,7 @@ env: # http://docs.travis-ci.com/user/speeding-up-the-build/#Paralellizing-your-build-on-one-VM - MAKE_CMD="make -j2" # Update PATH for pip. - - PATH="$(python2.7 -c 'import site; print(site.getuserbase())')/bin:/usr/lib/llvm-symbolizer-4.0/bin:$PATH" + - PATH="$(python2.7 -c 'import site; print(site.getuserbase())')/bin:$PATH" # Build directory for Neovim. - BUILD_DIR="$TRAVIS_BUILD_DIR/build" # Build directory for third-party dependencies. @@ -53,7 +53,7 @@ jobs: include: - stage: sanitizers os: linux - compiler: clang-4.0 + compiler: clang env: > CLANG_SANITIZER=ASAN_UBSAN CMAKE_FLAGS="$CMAKE_FLAGS -DPREFER_LUA=ON" @@ -81,7 +81,7 @@ jobs: compiler: gcc-5 env: GCOV=gcov-5 CMAKE_FLAGS="$CMAKE_FLAGS -DUSE_GCOV=ON" - os: linux - compiler: clang-4.0 + compiler: clang env: CLANG_SANITIZER=TSAN allow_failures: - env: GCOV=gcov-5 CMAKE_FLAGS="$CMAKE_FLAGS -DUSE_GCOV=ON" @@ -99,13 +99,12 @@ addons: apt: sources: - ubuntu-toolchain-r-test - - llvm-toolchain-trusty-4.0 packages: - autoconf - automake - apport - build-essential - - clang-4.0 + - clang - cmake - cscope - g++-5-multilib @@ -116,7 +115,6 @@ addons: - language-pack-tr - libc6-dev-i386 - libtool - - llvm-4.0-dev - locales - pkg-config - unzip diff --git a/ci/common/test.sh b/ci/common/test.sh index 55f76ca798..2de89dee79 100644 --- a/ci/common/test.sh +++ b/ci/common/test.sh @@ -71,7 +71,7 @@ valgrind_check() { } asan_check() { - check_logs "${1}" "*san.*" + check_logs "${1}" "*san.*" | asan_symbolize } run_unittests() {( -- cgit From dc1444e112eaaa13fb3b4ed4c58b01781219aca6 Mon Sep 17 00:00:00 2001 From: James McCoy Date: Mon, 1 Jan 2018 21:19:58 -0500 Subject: travis: Remove ubuntu-r-toolchain/test ppa Use unversioned gcc/gcov commands rather than pulling in a separate repo. --- .travis.yml | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/.travis.yml b/.travis.yml index 35a3c76fce..5b37ad656d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -59,32 +59,32 @@ jobs: CMAKE_FLAGS="$CMAKE_FLAGS -DPREFER_LUA=ON" - stage: normal builds os: linux - compiler: gcc-5 + compiler: gcc env: FUNCTIONALTEST=functionaltest-lua - os: linux # Travis creates a cache per compiler. # Set a different value here to store 32-bit # dependencies in a separate cache. - compiler: gcc-5 -m32 + compiler: gcc -m32 env: BUILD_32BIT=ON - os: osx compiler: clang osx_image: xcode7.3 # macOS 10.11 - os: osx - compiler: gcc-4.9 + compiler: gcc osx_image: xcode7.3 # macOS 10.11 - stage: lint os: linux env: CI_TARGET=lint - stage: Flaky builds os: linux - compiler: gcc-5 - env: GCOV=gcov-5 CMAKE_FLAGS="$CMAKE_FLAGS -DUSE_GCOV=ON" + compiler: gcc + env: GCOV=gcov CMAKE_FLAGS="$CMAKE_FLAGS -DUSE_GCOV=ON" - os: linux compiler: clang env: CLANG_SANITIZER=TSAN allow_failures: - - env: GCOV=gcov-5 CMAKE_FLAGS="$CMAKE_FLAGS -DUSE_GCOV=ON" + - env: GCOV=gcov CMAKE_FLAGS="$CMAKE_FLAGS -DUSE_GCOV=ON" - env: CLANG_SANITIZER=TSAN fast_finish: true @@ -97,8 +97,6 @@ after_success: ci/after_success.sh addons: apt: - sources: - - ubuntu-toolchain-r-test packages: - autoconf - automake @@ -107,9 +105,7 @@ addons: - clang - cmake - cscope - - g++-5-multilib - g++-multilib - - gcc-5-multilib - gcc-multilib - gdb - language-pack-tr -- cgit From d162815ca9237edf13b227cc51b4d2931f1f26a1 Mon Sep 17 00:00:00 2001 From: James McCoy Date: Mon, 1 Jan 2018 21:30:54 -0500 Subject: travis: Reduce stages to flaky builds (gcov, tsan) and everything else Separating the non-flaky builds (asan, normal builds, lint) into separate stages simply slowed down overall CI turnaround. Since none of the builds rely on the output of others, reducing the stages increases the opportunities for parallel builds. --- .travis.yml | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5b37ad656d..09ef8bba46 100644 --- a/.travis.yml +++ b/.travis.yml @@ -51,14 +51,13 @@ env: jobs: include: - - stage: sanitizers + - stage: normal builds os: linux compiler: clang env: > CLANG_SANITIZER=ASAN_UBSAN CMAKE_FLAGS="$CMAKE_FLAGS -DPREFER_LUA=ON" - - stage: normal builds - os: linux + - os: linux compiler: gcc env: FUNCTIONALTEST=functionaltest-lua - os: linux @@ -73,8 +72,7 @@ jobs: - os: osx compiler: gcc osx_image: xcode7.3 # macOS 10.11 - - stage: lint - os: linux + - os: linux env: CI_TARGET=lint - stage: Flaky builds os: linux -- cgit From dd0fa4fd0e6dd7c59aba17dfeadc66b271501ce1 Mon Sep 17 00:00:00 2001 From: James McCoy Date: Mon, 1 Jan 2018 21:46:44 -0500 Subject: ci: asan_check: No-op unless performing ASAN build --- ci/common/test.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ci/common/test.sh b/ci/common/test.sh index 2de89dee79..1cb3a6224b 100644 --- a/ci/common/test.sh +++ b/ci/common/test.sh @@ -71,7 +71,9 @@ valgrind_check() { } asan_check() { - check_logs "${1}" "*san.*" | asan_symbolize + if test "${CLANG_SANITIZER}" = "ASAN_UBSAN" ; then + check_logs "${1}" "*san.*" | asan_symbolize + fi } run_unittests() {( -- cgit From b86e44aa358c2164bebddbb4ed9640b000323c6b Mon Sep 17 00:00:00 2001 From: James McCoy Date: Mon, 1 Jan 2018 23:11:31 -0500 Subject: vim-patch:8.0.0160: EMSG() is sometimes used where it should be IEMSG() Problem: EMSG() is sometimes used for internal errors. Solution: Change them to IEMSG(). (Dominique Pelle) And a few more. https://github.com/vim/vim/commit/de33011ec623fd562419dede6bf465b5b9881a20 --- src/nvim/if_cscope.c | 2 +- src/nvim/regexp_nfa.c | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/nvim/if_cscope.c b/src/nvim/if_cscope.c index 654b4630c5..303a9bd92b 100644 --- a/src/nvim/if_cscope.c +++ b/src/nvim/if_cscope.c @@ -1370,7 +1370,7 @@ static char *cs_manage_matches(char **matches, char **contexts, cs_print_tags_priv(mp, cp, cnt); break; default: /* should not reach here */ - (void)EMSG(_("E570: fatal error in cs_manage_matches")); + IEMSG(_("E570: fatal error in cs_manage_matches")); return NULL; } diff --git a/src/nvim/regexp_nfa.c b/src/nvim/regexp_nfa.c index 93ba9ce097..bc3ad57dde 100644 --- a/src/nvim/regexp_nfa.c +++ b/src/nvim/regexp_nfa.c @@ -1263,7 +1263,7 @@ static int nfa_regatom(void) rc_did_emsg = TRUE; return FAIL; } - EMSGN("INTERNAL: Unknown character class char: %" PRId64, c); + IEMSGN("INTERNAL: Unknown character class char: %" PRId64, c); return FAIL; } /* When '.' is followed by a composing char ignore the dot, so that @@ -4414,7 +4414,7 @@ static int check_char_class(int class, int c) default: /* should not be here :P */ - EMSGN(_(e_ill_char_class), class); + IEMSGN(_(e_ill_char_class), class); return FAIL; } return FAIL; @@ -5993,7 +5993,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, #ifdef REGEXP_DEBUG if (c < 0) - EMSGN("INTERNAL: Negative state char: %" PRId64, c); + IEMSGN("INTERNAL: Negative state char: %" PRId64, c); #endif result = (c == curc); @@ -6464,9 +6464,9 @@ static regprog_T *nfa_regcomp(char_u *expr, int re_flags) if (postfix == NULL) { /* TODO: only give this error for debugging? */ if (post_ptr >= post_end) - EMSGN("Internal error: estimated max number " - "of states insufficient: %" PRId64, - post_end - post_start); + IEMSGN("Internal error: estimated max number " + "of states insufficient: %" PRId64, + post_end - post_start); goto fail; /* Cascaded (syntax?) error */ } -- cgit From 5f5011e8f67e6c26ec0c04d2f390bca392dba648 Mon Sep 17 00:00:00 2001 From: James McCoy Date: Mon, 1 Jan 2018 23:17:31 -0500 Subject: lint --- src/nvim/if_cscope.c | 2 +- src/nvim/regexp_nfa.c | 12 +++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/nvim/if_cscope.c b/src/nvim/if_cscope.c index 303a9bd92b..773e29693c 100644 --- a/src/nvim/if_cscope.c +++ b/src/nvim/if_cscope.c @@ -1369,7 +1369,7 @@ static char *cs_manage_matches(char **matches, char **contexts, case Print: cs_print_tags_priv(mp, cp, cnt); break; - default: /* should not reach here */ + default: // should not reach here IEMSG(_("E570: fatal error in cs_manage_matches")); return NULL; } diff --git a/src/nvim/regexp_nfa.c b/src/nvim/regexp_nfa.c index bc3ad57dde..c520ef5fb9 100644 --- a/src/nvim/regexp_nfa.c +++ b/src/nvim/regexp_nfa.c @@ -4413,7 +4413,7 @@ static int check_char_class(int class, int c) break; default: - /* should not be here :P */ + // should not be here :P IEMSGN(_(e_ill_char_class), class); return FAIL; } @@ -5992,8 +5992,9 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, int c = t->state->c; #ifdef REGEXP_DEBUG - if (c < 0) + if (c < 0) { IEMSGN("INTERNAL: Negative state char: %" PRId64, c); + } #endif result = (c == curc); @@ -6462,12 +6463,13 @@ static regprog_T *nfa_regcomp(char_u *expr, int re_flags) * (and count its size). */ postfix = re2post(); if (postfix == NULL) { - /* TODO: only give this error for debugging? */ - if (post_ptr >= post_end) + // TODO(vim): only give this error for debugging? + if (post_ptr >= post_end) { IEMSGN("Internal error: estimated max number " "of states insufficient: %" PRId64, post_end - post_start); - goto fail; /* Cascaded (syntax?) error */ + } + goto fail; // Cascaded (syntax?) error } /* -- cgit From d63c3d9d105b7d81ad397e784b33d3dec7073338 Mon Sep 17 00:00:00 2001 From: Paul Rigge Date: Sat, 30 Dec 2017 20:53:01 -0800 Subject: Add assertion in set_var_lval for null pointer. If the lval is a index into a list, li should not be null. --- src/nvim/eval.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 33f8ffb738..f30840041b 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -2417,6 +2417,7 @@ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv, if (ri == NULL || (!lp->ll_empty2 && lp->ll_n2 == lp->ll_n1)) { break; } + assert(lp->ll_li != NULL); if (TV_LIST_ITEM_NEXT(lp->ll_list, lp->ll_li) == NULL) { // Need to add an empty item. tv_list_append_number(lp->ll_list, 0); -- cgit From dea7a41138674b45e8cfd5c1d713d4048987c830 Mon Sep 17 00:00:00 2001 From: Paul Rigge Date: Sat, 30 Dec 2017 22:17:31 -0800 Subject: Add another const to tv_copy Clang static analyzer had trouble with filter_map in eval.c because tv_copy could, in principle, change the v_type of argvars[0]. It saw a potential null pointer going somewhere it shouldn't as a result. The from argument in tv_copy should be const, which also cleans up the static analyzer's complaint. --- src/nvim/eval/typval.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 21bb84a945..ac6c8c8aa6 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -2153,7 +2153,7 @@ void tv_free(typval_T *tv) /// /// @param[in] from Location to copy from. /// @param[out] to Location to copy to. -void tv_copy(typval_T *const from, typval_T *const to) +void tv_copy(const typval_T *const from, typval_T *const to) { to->v_type = from->v_type; to->v_lock = VAR_UNLOCKED; -- cgit From 65ec4ea62972e021065d5a5be83b04bb8da2561a Mon Sep 17 00:00:00 2001 From: Paul Rigge Date: Mon, 1 Jan 2018 20:20:57 -0800 Subject: Add assertions for watchers The clang static analyzer convinced itself lp->ll_newkey could be NULL. This adds an assertion that checks this doesn't actually happen, as well as a parallel assertion for di->di_key. --- src/nvim/eval.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/nvim/eval.c b/src/nvim/eval.c index f30840041b..555a0506d9 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -2480,9 +2480,11 @@ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv, notify: if (watched) { if (oldtv.v_type == VAR_UNKNOWN) { + assert(lp->ll_newkey != NULL); tv_dict_watcher_notify(dict, (char *)lp->ll_newkey, lp->ll_tv, NULL); } else { dictitem_T *di = lp->ll_di; + assert(di->di_key != NULL); tv_dict_watcher_notify(dict, (char *)di->di_key, lp->ll_tv, &oldtv); tv_clear(&oldtv); } -- cgit From 2e630d261157dbb902768ba8ef8346ee1eb41eb7 Mon Sep 17 00:00:00 2001 From: Paul Rigge Date: Mon, 1 Jan 2018 21:15:22 -0800 Subject: Refactor profiling check in call_user_func. do_profiling is a global variable, and as such the clang static analyzer has trouble making arguments about it. This commit does one comparison against do_profiling and puts the result in a local variable. This prevents errors from the value of do_profiling changing between comparisons. --- src/nvim/eval.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 555a0506d9..1c4dda0716 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -21254,15 +21254,17 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, } } + const bool do_profiling_yes = do_profiling == PROF_YES; + bool func_not_yet_profiling_but_should = - do_profiling == PROF_YES + do_profiling_yes && !fp->uf_profiling && has_profiling(FALSE, fp->uf_name, NULL); if (func_not_yet_profiling_but_should) func_do_profile(fp); bool func_or_func_caller_profiling = - do_profiling == PROF_YES + do_profiling_yes && (fp->uf_profiling || (fc->caller != NULL && fc->caller->func->uf_profiling)); @@ -21272,7 +21274,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, fp->uf_tm_children = profile_zero(); } - if (do_profiling == PROF_YES) { + if (do_profiling_yes) { script_prof_save(&wait_start); } @@ -21348,7 +21350,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, sourcing_name = save_sourcing_name; sourcing_lnum = save_sourcing_lnum; current_SID = save_current_SID; - if (do_profiling == PROF_YES) + if (do_profiling_yes) script_prof_restore(&wait_start); if (p_verbose >= 12 && sourcing_name != NULL) { -- cgit From 1bbe6d0a3015007bf67fc81caab86859a0e2d2bd Mon Sep 17 00:00:00 2001 From: Paul Rigge Date: Mon, 1 Jan 2018 22:45:35 -0800 Subject: Add null pointer assertions for do_unlet_var. --- src/nvim/eval.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 1c4dda0716..186fa0da76 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -2900,6 +2900,7 @@ static int do_unlet_var(lval_T *const lp, char_u *const name_end, int forceit) lp->ll_name_len))) { return FAIL; } else if (lp->ll_range) { + assert(lp->ll_list != NULL); // Delete a range of List items. listitem_T *const first_li = lp->ll_li; listitem_T *last_li = first_li; @@ -2926,6 +2927,7 @@ static int do_unlet_var(lval_T *const lp, char_u *const name_end, int forceit) } else { // unlet a Dictionary item. dict_T *d = lp->ll_dict; + assert(d != NULL); dictitem_T *di = lp->ll_di; bool watched = tv_dict_is_watched(d); char *key = NULL; -- cgit From 88d643eb36693bcbbac24ec3d6f63a316a452402 Mon Sep 17 00:00:00 2001 From: Paul Rigge Date: Mon, 1 Jan 2018 23:22:13 -0800 Subject: Add null check when adding variable to dict. --- src/nvim/eval.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 186fa0da76..155b816b33 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -19016,6 +19016,9 @@ static void set_var(const char *name, const size_t name_len, typval_T *const tv, return; } + // Make sure dict is valid + assert(dict != NULL); + v = xmalloc(sizeof(dictitem_T) + strlen(varname)); STRCPY(v->di_key, varname); if (tv_dict_add(dict, v) == FAIL) { -- cgit From d55881d2783da4161a37ac39fcfb2c535a837a5e Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Wed, 4 Oct 2017 20:32:22 -0400 Subject: test: enable K_spec tests in Windows --- test/functional/normal/K_spec.lua | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/test/functional/normal/K_spec.lua b/test/functional/normal/K_spec.lua index 43e598633c..174313d80e 100644 --- a/test/functional/normal/K_spec.lua +++ b/test/functional/normal/K_spec.lua @@ -2,8 +2,6 @@ local helpers = require('test.functional.helpers')(after_each) local eq, clear, eval, feed = helpers.eq, helpers.clear, helpers.eval, helpers.feed -if helpers.pending_win32(pending) then return end - describe('K', function() local test_file = 'K_spec_out' before_each(function() @@ -29,7 +27,7 @@ describe('K', function() it("invokes non-prefixed 'keywordprg' as shell command", function() helpers.source([[ let @a='fnord' - set keywordprg=echo\ fnord\ >>]]) + set keywordprg=echo\ fnord>>]]) -- K on the text "K_spec_out" resolves to `!echo fnord >> K_spec_out`. feed('i'..test_file..'K') -- cgit From 0a881575dab1af7bf70b3c85385f71997efe8258 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Wed, 3 Jan 2018 19:15:40 +0100 Subject: vim-patch:8.0.0955: Test_existent_file() fails on some file systems Problem: Test_existent_file() fails on some file systems. Solution: Run the test again with a sleep when the test fails without a sleep. (James McCoy, closes vim/vim#1984) https://github.com/vim/vim/commit/82de3c2c036bc89c2d9bdea236e0a7f1208a5571 --- src/nvim/testdir/test_stat.vim | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/src/nvim/testdir/test_stat.vim b/src/nvim/testdir/test_stat.vim index dee0d13e84..1239fe9427 100644 --- a/src/nvim/testdir/test_stat.vim +++ b/src/nvim/testdir/test_stat.vim @@ -1,20 +1,41 @@ " Tests for stat functions and checktime -func Test_existent_file() +func CheckFileTime(doSleep) let fname = 'Xtest.tmp' + let result = 0 let ts = localtime() + if a:doSleep + sleep 1 + endif let fl = ['Hello World!'] call writefile(fl, fname) let tf = getftime(fname) + if a:doSleep + sleep 1 + endif let te = localtime() - call assert_true(ts <= tf && tf <= te) - call assert_equal(strlen(fl[0] . "\n"), getfsize(fname)) - call assert_equal('file', getftype(fname)) - call assert_equal('rw-', getfperm(fname)[0:2]) + let time_correct = (ts <= tf && tf <= te) + if a:doSleep || time_correct + call assert_true(time_correct) + call assert_equal(strlen(fl[0] . "\n"), getfsize(fname)) + call assert_equal('file', getftype(fname)) + call assert_equal('rw-', getfperm(fname)[0:2]) + let result = 1 + endif call delete(fname) + return result +endfunc + +func Test_existent_file() + " On some systems the file timestamp is rounded to a multiple of 2 seconds. + " We need to sleep to handle that, but that makes the test slow. First try + " without the sleep, and if it fails try again with the sleep. + if CheckFileTime(0) == 0 + call CheckFileTime(1) + endif endfunc func Test_existent_directory() -- cgit From 7c4bb23ff38a459911a742657572bc273e528ddf Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Wed, 3 Jan 2018 19:57:22 +0100 Subject: defaults: do :filetype stuff unless explicitly "off" Until now, the default `:filetype ...` setup was skipped if the user config touched `:filetype` in any way (including implicitly via `:syntax on`). No one needs that, and it's very confusing. Instead, proceed with `:filetype ... on` unless the user explicitly called `:filetype ... off`. closes #7765 --- runtime/doc/filetype.txt | 6 +-- runtime/doc/starting.txt | 9 ++-- src/nvim/ex_docmd.c | 11 ++-- test/functional/options/defaults_spec.lua | 90 +++++++++++++++++-------------- 4 files changed, 62 insertions(+), 54 deletions(-) diff --git a/runtime/doc/filetype.txt b/runtime/doc/filetype.txt index 78402b47fe..74c453f79a 100644 --- a/runtime/doc/filetype.txt +++ b/runtime/doc/filetype.txt @@ -24,10 +24,8 @@ Each time a new or existing file is edited, Vim will try to recognize the type of the file and set the 'filetype' option. This will trigger the FileType event, which can be used to set the syntax highlighting, set options, etc. -Detail: The ":filetype on" command will load one of these files: - Mac $VIMRUNTIME/filetype.vim - MS-DOS $VIMRUNTIME\filetype.vim - Unix $VIMRUNTIME/filetype.vim +Detail: The ":filetype on" command will load this file: + $VIMRUNTIME/filetype.vim This file is a Vim script that defines autocommands for the BufNewFile and BufRead events. If the file type is not found by the name, the file $VIMRUNTIME/scripts.vim is used to detect it from the diff --git a/runtime/doc/starting.txt b/runtime/doc/starting.txt index 30c0641ef7..e9188ba641 100644 --- a/runtime/doc/starting.txt +++ b/runtime/doc/starting.txt @@ -439,15 +439,14 @@ accordingly. Vim proceeds in this order: :runtime! filetype.vim :runtime! ftplugin.vim :runtime! indent.vim -< This step is skipped if ":filetype ..." was called before now or if - the "-u NONE" command line argument was given. +< Skipped if ":filetype … off" was called or if the "-u NONE" command + line argument was given. 5. Enable syntax highlighting. This does the same as the command: > :runtime! syntax/syntax.vim -< Note: This enables filetype detection even if ":filetype off" was - called before now. - This step is skipped if the "-u NONE" command line argument was given. +< Skipped if ":syntax off" was called or if the "-u NONE" command + line argument was given. 6. Load the plugin scripts. *load-plugins* This does the same as the command: > diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index a0ede4f3c5..2fa8db6b82 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -9718,17 +9718,18 @@ static void ex_filetype(exarg_T *eap) EMSG2(_(e_invarg2), arg); } -/// Do ":filetype plugin indent on" if user did not already do some -/// permutation thereof. +/// Set all :filetype options ON if user did not explicitly set any to OFF. void filetype_maybe_enable(void) { - if (filetype_detect == kNone - && filetype_plugin == kNone - && filetype_indent == kNone) { + if (filetype_detect == kNone) { source_runtime((char_u *)FILETYPE_FILE, true); filetype_detect = kTrue; + } + if (filetype_plugin == kNone) { source_runtime((char_u *)FTPLUGIN_FILE, true); filetype_plugin = kTrue; + } + if (filetype_indent == kNone) { source_runtime((char_u *)INDENT_FILE, true); filetype_indent = kTrue; } diff --git a/test/functional/options/defaults_spec.lua b/test/functional/options/defaults_spec.lua index b83b7b8eee..fd232cb4dd 100644 --- a/test/functional/options/defaults_spec.lua +++ b/test/functional/options/defaults_spec.lua @@ -11,15 +11,6 @@ local neq = helpers.neq local mkdir = helpers.mkdir local rmdir = helpers.rmdir -local function init_session(...) - local args = { helpers.nvim_prog, '-i', 'NONE', '--embed', - '--cmd', helpers.nvim_set } - for _, v in ipairs({...}) do - table.insert(args, v) - end - helpers.set_session(helpers.spawn(args)) -end - describe('startup defaults', function() describe(':filetype', function() if helpers.pending_win32(pending) then return end @@ -36,50 +27,70 @@ describe('startup defaults', function() ) end - it('enabled by `-u NORC`', function() - init_session('-u', 'NORC') + it('all ON after `-u NORC`', function() + clear('-u', 'NORC') expect_filetype( 'filetype detection:ON plugin:ON indent:ON |') end) - it('disabled by `-u NONE`', function() - init_session('-u', 'NONE') + it('all ON after `:syntax …` #7765', function() + clear('-u', 'NORC', '--cmd', 'syntax on') expect_filetype( - 'filetype detection:OFF plugin:OFF indent:OFF |') + 'filetype detection:ON plugin:ON indent:ON |') + clear('-u', 'NORC', '--cmd', 'syntax off') + expect_filetype( + 'filetype detection:ON plugin:ON indent:ON |') end) - it('overridden by early `filetype on`', function() - init_session('-u', 'NORC', '--cmd', 'filetype on') + it('all OFF after `-u NONE`', function() + clear('-u', 'NONE') expect_filetype( - 'filetype detection:ON plugin:OFF indent:OFF |') + 'filetype detection:OFF plugin:OFF indent:OFF |') end) - it('overridden by early `filetype plugin on`', function() - init_session('-u', 'NORC', '--cmd', 'filetype plugin on') + it('explicit OFF stays OFF', function() + clear('-u', 'NORC', '--cmd', + 'syntax off | filetype off | filetype plugin indent off') + expect_filetype( + 'filetype detection:OFF plugin:OFF indent:OFF |') + clear('-u', 'NORC', '--cmd', 'syntax off | filetype plugin indent off') + expect_filetype( + 'filetype detection:ON plugin:OFF indent:OFF |') + clear('-u', 'NORC', '--cmd', 'filetype indent off') expect_filetype( 'filetype detection:ON plugin:ON indent:OFF |') + clear('-u', 'NORC', '--cmd', 'syntax off | filetype off') + expect_filetype( + 'filetype detection:OFF plugin:(on) indent:(on) |') + -- Swap the order. + clear('-u', 'NORC', '--cmd', 'filetype off | syntax off') + expect_filetype( + 'filetype detection:OFF plugin:(on) indent:(on) |') end) - it('overridden by early `filetype indent on`', function() - init_session('-u', 'NORC', '--cmd', 'filetype indent on') + it('all ON after early `:filetype … on`', function() + -- `:filetype … on` should not change the defaults. #7765 + -- Only an explicit `:filetype … off` sets OFF. + + clear('-u', 'NORC', '--cmd', 'filetype on') expect_filetype( - 'filetype detection:ON plugin:OFF indent:ON |') + 'filetype detection:ON plugin:ON indent:ON |') + clear('-u', 'NORC', '--cmd', 'filetype plugin on') + expect_filetype( + 'filetype detection:ON plugin:ON indent:ON |') + clear('-u', 'NORC', '--cmd', 'filetype indent on') + expect_filetype( + 'filetype detection:ON plugin:ON indent:ON |') end) - it('adjusted by late `filetype off`', function() - init_session('-u', 'NORC', '-c', 'filetype off') + it('late `:filetype … off` stays OFF', function() + clear('-u', 'NORC', '-c', 'filetype off') expect_filetype( 'filetype detection:OFF plugin:(on) indent:(on) |') - end) - - it('adjusted by late `filetype plugin off`', function() - init_session('-u', 'NORC', '-c', 'filetype plugin off') + clear('-u', 'NORC', '-c', 'filetype plugin off') expect_filetype( 'filetype detection:ON plugin:OFF indent:ON |') - end) - - it('adjusted by late `filetype indent off`', function() - init_session('-u', 'NORC', '-c', 'filetype indent off') + clear('-u', 'NORC', '-c', 'filetype indent off') expect_filetype( 'filetype detection:ON plugin:ON indent:OFF |') end) @@ -87,22 +98,21 @@ describe('startup defaults', function() describe('syntax', function() it('enabled by `-u NORC`', function() - init_session('-u', 'NORC') + clear('-u', 'NORC') eq(1, eval('g:syntax_on')) end) it('disabled by `-u NONE`', function() - init_session('-u', 'NONE') + clear('-u', 'NONE') eq(0, eval('exists("g:syntax_on")')) end) - it('overridden by early `syntax off`', function() - init_session('-u', 'NORC', '--cmd', 'syntax off') + it('`:syntax off` stays off', function() + -- early + clear('-u', 'NORC', '--cmd', 'syntax off') eq(0, eval('exists("g:syntax_on")')) - end) - - it('adjusted by late `syntax off`', function() - init_session('-u', 'NORC', '-c', 'syntax off') + -- late + clear('-u', 'NORC', '-c', 'syntax off') eq(0, eval('exists("g:syntax_on")')) end) end) -- cgit From b616ef9b220fdf7a7b842a95a8a9d088cfbf472d Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Fri, 5 Jan 2018 10:54:29 +0100 Subject: tests: stderr output contains `cp` noise closes #7811 --- test/functional/shada/helpers.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/test/functional/shada/helpers.lua b/test/functional/shada/helpers.lua index b77e59682f..1312d762d8 100644 --- a/test/functional/shada/helpers.lua +++ b/test/functional/shada/helpers.lua @@ -37,7 +37,6 @@ local function add_argv(...) end local clear = function() - os.execute('cp ' .. tmpname .. ' /tmp/test.shada') os.remove(tmpname) append_argv = nil end -- cgit From a18db72a36cbd691e08283e7fc5557c4943300d4 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sat, 6 Jan 2018 16:22:38 +0100 Subject: third-party: revert to jemalloc 4.5.0 ref #7746 closes #7808 jemalloc-5.0.1 broke the Ubuntu Unstable PPA builds on arm64: https://launchpadlibrarian.net/351647411/buildlog_ubuntu-artful-arm64.neovim_0.2.0ubuntu1+git201712291800+3837+26~ubuntu17.10.1_BUILDING.txt.gz). make[5]: Entering directory '/<>/neovim-0.2.0ubuntu1+git201712291800+3837+26~ubuntu17.10.1/build' Segmentation fault (core dumped) runtime/CMakeFiles/vimball-tags.dir/build.make:57: recipe for target 'runtime/CMakeFiles/vimball-tags' failed make[5]: *** [runtime/CMakeFiles/vimball-tags] Error 139 make[5]: Leaving directory '/<>/neovim-0.2.0ubuntu1+git201712291800+3837+26~ubuntu17.10.1/build' CMakeFiles/Makefile2:7467: recipe for target 'runtime/CMakeFiles/vimball-tags.dir/all' failed jemalloc bug: https://github.com/jemalloc/jemalloc/issues/979 --- third-party/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/third-party/CMakeLists.txt b/third-party/CMakeLists.txt index 9bfcee4ed4..537059afdd 100644 --- a/third-party/CMakeLists.txt +++ b/third-party/CMakeLists.txt @@ -117,8 +117,8 @@ endif() set(LIBVTERM_URL https://github.com/neovim/libvterm/archive/a9c7c6fd20fa35e0ad3e0e98901ca12dfca9c25c.tar.gz) set(LIBVTERM_SHA256 1a4272be91d9614dc183a503786df83b6584e4afaab7feaaa5409f841afbd796) -set(JEMALLOC_URL https://github.com/jemalloc/jemalloc/releases/download/5.0.1/jemalloc-5.0.1.tar.bz2) -set(JEMALLOC_SHA256 4814781d395b0ef093b21a08e8e6e0bd3dab8762f9935bbfb71679b0dea7c3e9) +set(JEMALLOC_URL https://github.com/jemalloc/jemalloc/releases/download/4.5.0/jemalloc-4.5.0.tar.bz2) +set(JEMALLOC_SHA256 9409d85664b4f135b77518b0b118c549009dc10f6cba14557d170476611f6780) set(LUV_URL https://github.com/luvit/luv/archive/1.9.1-1.tar.gz) set(LUV_SHA256 562b9efaad30aa051a40eac9ade0c3df48bb8186763769abe47ec3fb3edb1268) -- cgit From c82e7c75fe4c127a2886e22891794a9f12bff530 Mon Sep 17 00:00:00 2001 From: Marvim the Paranoid Android Date: Sat, 6 Jan 2018 16:35:19 +0100 Subject: version.c: update [ci skip] (#7780) --- src/nvim/version.c | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/nvim/version.c b/src/nvim/version.c index e35b803b4e..996c06c902 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -480,7 +480,7 @@ static const int included_patches[] = { // 958, // 957, // 956, - // 955, + 955, // 954, // 953, // 952, @@ -778,7 +778,7 @@ static const int included_patches[] = { // 660, // 659, // 658, - // 657, + 657, // 656, // 655, // 654, @@ -794,14 +794,14 @@ static const int included_patches[] = { // 644, // 643, // 642, - // 641, + 641, // 640, // 639, // 638, // 637, // 636, // 635, - // 634, + 634, // 633, // 632, // 631, @@ -827,7 +827,7 @@ static const int included_patches[] = { // 611, // 610, // 609, - // 608, + 608, 607, 606, 605, @@ -844,7 +844,7 @@ static const int included_patches[] = { // 594, // 593, // 592, - // 591, + 591, 590, // 589, // 588, @@ -1096,12 +1096,12 @@ static const int included_patches[] = { // 342, 341, // 340, - // 339, + 339, // 338, // 337, - // 336, - // 335, - // 334, + 336, + 335, + 334, 333, // 332, 331, @@ -1120,8 +1120,8 @@ static const int included_patches[] = { // 318, // 317, // 316, - // 315, - // 314, + 315, + 314, // 313, // 312, 311, @@ -1275,7 +1275,7 @@ static const int included_patches[] = { 163, 162, 161, - // 160, + 160, 159, 158, 157, -- cgit From e9b5616eaf49755c972bcc46719f985f9ca1e15f Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Thu, 5 Oct 2017 12:38:10 -0400 Subject: win: enable tests in ex_terminal_spec --- test/functional/terminal/ex_terminal_spec.lua | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/test/functional/terminal/ex_terminal_spec.lua b/test/functional/terminal/ex_terminal_spec.lua index e015df10db..ee92ba6865 100644 --- a/test/functional/terminal/ex_terminal_spec.lua +++ b/test/functional/terminal/ex_terminal_spec.lua @@ -7,7 +7,6 @@ local retry = helpers.retry local iswin = helpers.iswin describe(':terminal', function() - if helpers.pending_win32(pending) then return end local screen before_each(function() @@ -24,7 +23,11 @@ describe(':terminal', function() echomsg "msg3" ]]) -- Invoke a command that emits frequent terminal activity. - feed_command([[terminal while true; do echo X; done]]) + if iswin() then + feed_command([[terminal for /L \\%I in (1,0,2) do echo \\%I]]) + else + feed_command([[terminal while true; do echo X; done]]) + end helpers.feed([[]]) wait() screen:sleep(10) -- Let some terminal activity happen. @@ -38,7 +41,11 @@ describe(':terminal', function() end) it("in normal-mode :split does not move cursor", function() - feed_command([[terminal while true; do echo foo; sleep .1; done]]) + if iswin() then + feed_command([[terminal for /L \\%I in (1,0,2) do ( echo foo & ping -w 100 -n 1 127.0.0.1 > nul )]]) + else + feed_command([[terminal while true; do echo foo; sleep .1; done]]) + end helpers.feed([[M]]) -- move cursor away from last line wait() eq(3, eval("line('$')")) -- window height -- cgit From 7311fb7cadff49422a15d3a40d7014000c8f4385 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Thu, 19 Oct 2017 17:26:03 -0400 Subject: win: enable more functional tests - plugin/shada_spec.lua: Use \r\n as Windows EOL for tests on BufWriteCmd, FileWriteCmd, FileAppendCmd. Alternative is 'set fileformat=unix'. --- test/functional/options/autochdir_spec.lua | 5 ++--- test/functional/options/defaults_spec.lua | 2 -- test/functional/plugin/shada_spec.lua | 10 +++++----- test/functional/ui/bufhl_spec.lua | 2 -- test/functional/ui/inccommand_spec.lua | 12 ------------ test/functional/ui/input_spec.lua | 2 -- test/functional/ui/mouse_spec.lua | 2 -- test/functional/ui/searchhl_spec.lua | 2 -- test/functional/ui/sign_spec.lua | 2 -- test/functional/ui/syntax_conceal_spec.lua | 2 -- test/functional/viml/completion_spec.lua | 2 -- 11 files changed, 7 insertions(+), 36 deletions(-) diff --git a/test/functional/options/autochdir_spec.lua b/test/functional/options/autochdir_spec.lua index 209531515c..2fce0a5ed9 100644 --- a/test/functional/options/autochdir_spec.lua +++ b/test/functional/options/autochdir_spec.lua @@ -3,8 +3,6 @@ local clear = helpers.clear local eq = helpers.eq local getcwd = helpers.funcs.getcwd -if helpers.pending_win32(pending) then return end - describe("'autochdir'", function() it('given on the shell gets processed properly', function() local targetdir = 'test/functional/fixtures' @@ -12,9 +10,10 @@ describe("'autochdir'", function() -- By default 'autochdir' is off, thus getcwd() returns the repo root. clear(targetdir..'/tty-test.c') local rootdir = getcwd() + local expected = rootdir .. '/' .. targetdir -- With 'autochdir' on, we should get the directory of tty-test.c. clear('--cmd', 'set autochdir', targetdir..'/tty-test.c') - eq(rootdir..'/'..targetdir, getcwd()) + eq(helpers.iswin() and expected:gsub('/', '\\') or expected, getcwd()) end) end) diff --git a/test/functional/options/defaults_spec.lua b/test/functional/options/defaults_spec.lua index b83b7b8eee..68e94d1a85 100644 --- a/test/functional/options/defaults_spec.lua +++ b/test/functional/options/defaults_spec.lua @@ -22,8 +22,6 @@ end describe('startup defaults', function() describe(':filetype', function() - if helpers.pending_win32(pending) then return end - local function expect_filetype(expected) local screen = Screen.new(50, 4) screen:attach() diff --git a/test/functional/plugin/shada_spec.lua b/test/functional/plugin/shada_spec.lua index dbc78e63f0..57891a8229 100644 --- a/test/functional/plugin/shada_spec.lua +++ b/test/functional/plugin/shada_spec.lua @@ -43,8 +43,6 @@ local wshada, _, fname = get_shada_rw('Xtest-functional-plugin-shada.shada') local wshada_tmp, _, fname_tmp = get_shada_rw('Xtest-functional-plugin-shada.shada.tmp.f') -if helpers.pending_win32(pending) then return end - describe('In autoload/shada.vim', function() local epoch = os.date('%Y-%m-%dT%H:%M:%S', 0) before_each(function() @@ -2140,6 +2138,7 @@ end) describe('In plugin/shada.vim', function() local epoch = os.date('%Y-%m-%dT%H:%M:%S', 0) + local eol = helpers.iswin() and '\r\n' or '\n' before_each(function() reset() os.remove(fname) @@ -2279,7 +2278,7 @@ describe('In plugin/shada.vim', function() ' + f file name ["foo"]', ' + l line number 2', ' + c column -200', - }, '\n') .. '\n', io.open(fname .. '.tst'):read('*a')) + }, eol) .. eol, io.open(fname .. '.tst'):read('*a')) shada_eq({{ timestamp=0, type=8, @@ -2303,6 +2302,7 @@ describe('In plugin/shada.vim', function() describe('event FileWriteCmd', function() it('works', function() + if helpers.pending_win32(pending) then return end nvim('set_var', 'shada#add_own_header', 0) curbuf('set_lines', 0, 1, true, { 'Jump with timestamp ' .. epoch .. ':', @@ -2326,7 +2326,7 @@ describe('In plugin/shada.vim', function() 'Jump with timestamp ' .. epoch .. ':', ' % Key________ Description Value', ' + n name \'A\'', - }, '\n') .. '\n', io.open(fname .. '.tst'):read('*a')) + }, eol) .. eol, io.open(fname .. '.tst'):read('*a')) shada_eq({{ timestamp=0, type=8, @@ -2383,7 +2383,7 @@ describe('In plugin/shada.vim', function() ' + f file name ["foo"]', ' + l line number 2', ' + c column -200', - }, '\n') .. '\n', io.open(fname .. '.tst'):read('*a')) + }, eol) .. eol, io.open(fname .. '.tst'):read('*a')) shada_eq({{ timestamp=0, type=8, diff --git a/test/functional/ui/bufhl_spec.lua b/test/functional/ui/bufhl_spec.lua index 2143c01139..091c45596d 100644 --- a/test/functional/ui/bufhl_spec.lua +++ b/test/functional/ui/bufhl_spec.lua @@ -4,8 +4,6 @@ local Screen = require('test.functional.ui.screen') local clear, feed, insert = helpers.clear, helpers.feed, helpers.insert local command, request, neq = helpers.command, helpers.request, helpers.neq -if helpers.pending_win32(pending) then return end - describe('Buffer highlighting', function() local screen local curbuf diff --git a/test/functional/ui/inccommand_spec.lua b/test/functional/ui/inccommand_spec.lua index 53fd17c309..4a8ebf6212 100644 --- a/test/functional/ui/inccommand_spec.lua +++ b/test/functional/ui/inccommand_spec.lua @@ -121,8 +121,6 @@ describe(":substitute, inccommand=split does not trigger preview", function() end) describe(":substitute, 'inccommand' preserves", function() - if helpers.pending_win32(pending) then return end - before_each(clear) it('listed buffers (:ls)', function() @@ -285,8 +283,6 @@ describe(":substitute, 'inccommand' preserves", function() end) describe(":substitute, 'inccommand' preserves undo", function() - if helpers.pending_win32(pending) then return end - local cases = { "", "split", "nosplit" } local substrings = { @@ -700,8 +696,6 @@ describe(":substitute, 'inccommand' preserves undo", function() end) describe(":substitute, inccommand=split", function() - if helpers.pending_win32(pending) then return end - local screen = Screen.new(30,15) before_each(function() @@ -1169,8 +1163,6 @@ describe(":substitute, inccommand=split", function() end) describe("inccommand=nosplit", function() - if helpers.pending_win32(pending) then return end - local screen = Screen.new(20,10) before_each(function() @@ -1356,8 +1348,6 @@ describe("inccommand=nosplit", function() end) describe(":substitute, 'inccommand' with a failing expression", function() - if helpers.pending_win32(pending) then return end - local screen = Screen.new(20,10) local cases = { "", "split", "nosplit" } @@ -1621,8 +1611,6 @@ describe("'inccommand' autocommands", function() end) describe("'inccommand' split windows", function() - if helpers.pending_win32(pending) then return end - local screen local function refresh() clear() diff --git a/test/functional/ui/input_spec.lua b/test/functional/ui/input_spec.lua index 29d974b709..f8842901c1 100644 --- a/test/functional/ui/input_spec.lua +++ b/test/functional/ui/input_spec.lua @@ -4,8 +4,6 @@ local feed, next_message, eq = helpers.feed, helpers.next_message, helpers.eq local expect = helpers.expect local Screen = require('test.functional.ui.screen') -if helpers.pending_win32(pending) then return end - describe('mappings', function() local cid diff --git a/test/functional/ui/mouse_spec.lua b/test/functional/ui/mouse_spec.lua index 3daf92eea0..3fdedeb073 100644 --- a/test/functional/ui/mouse_spec.lua +++ b/test/functional/ui/mouse_spec.lua @@ -4,8 +4,6 @@ local clear, feed, meths = helpers.clear, helpers.feed, helpers.meths local insert, feed_command = helpers.insert, helpers.feed_command local eq, funcs = helpers.eq, helpers.funcs -if helpers.pending_win32(pending) then return end - describe('ui/mouse/input', function() local screen diff --git a/test/functional/ui/searchhl_spec.lua b/test/functional/ui/searchhl_spec.lua index 11b18d015f..5af8b83a36 100644 --- a/test/functional/ui/searchhl_spec.lua +++ b/test/functional/ui/searchhl_spec.lua @@ -3,8 +3,6 @@ local Screen = require('test.functional.ui.screen') local clear, feed, insert = helpers.clear, helpers.feed, helpers.insert local feed_command = helpers.feed_command -if helpers.pending_win32(pending) then return end - describe('search highlighting', function() local screen local colors = Screen.colors diff --git a/test/functional/ui/sign_spec.lua b/test/functional/ui/sign_spec.lua index e5c96f2ec0..c00d99cf90 100644 --- a/test/functional/ui/sign_spec.lua +++ b/test/functional/ui/sign_spec.lua @@ -2,8 +2,6 @@ local helpers = require('test.functional.helpers')(after_each) local Screen = require('test.functional.ui.screen') local clear, feed, command = helpers.clear, helpers.feed, helpers.command -if helpers.pending_win32(pending) then return end - describe('Signs', function() local screen diff --git a/test/functional/ui/syntax_conceal_spec.lua b/test/functional/ui/syntax_conceal_spec.lua index 28a104360d..e7a7004c1e 100644 --- a/test/functional/ui/syntax_conceal_spec.lua +++ b/test/functional/ui/syntax_conceal_spec.lua @@ -3,8 +3,6 @@ local Screen = require('test.functional.ui.screen') local clear, feed, command = helpers.clear, helpers.feed, helpers.command local insert = helpers.insert -if helpers.pending_win32(pending) then return end - describe('Screen', function() local screen diff --git a/test/functional/viml/completion_spec.lua b/test/functional/viml/completion_spec.lua index b70ef724b7..fbc7a527f8 100644 --- a/test/functional/viml/completion_spec.lua +++ b/test/functional/viml/completion_spec.lua @@ -5,8 +5,6 @@ local eval, eq, neq = helpers.eval, helpers.eq, helpers.neq local feed_command, source, expect = helpers.feed_command, helpers.source, helpers.expect local meths = helpers.meths -if helpers.pending_win32(pending) then return end - describe('completion', function() local screen -- cgit From 8d58012786b837380e6006d38580852d9506fbc8 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Tue, 24 Oct 2017 17:38:18 -0400 Subject: test: use unix fileformat to test NULs on systemlist --- test/functional/eval/system_spec.lua | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/functional/eval/system_spec.lua b/test/functional/eval/system_spec.lua index 7e213e2156..96212bf538 100644 --- a/test/functional/eval/system_spec.lua +++ b/test/functional/eval/system_spec.lua @@ -442,11 +442,13 @@ describe('systemlist()', function() describe('with output containing NULs', function() local fname = 'Xtest' - before_each(create_file_with_nuls(fname)) + before_each(function() + command('set ff=unix') + create_file_with_nuls(fname)() + end) after_each(delete_file(fname)) it('replaces NULs by newline characters', function() - if helpers.pending_win32(pending) then return end eq({'part1\npart2\npart3'}, eval('systemlist("cat '..fname..'")')) end) end) -- cgit From d4485f7cc0e4bab7d8ca41590956fa96d31f6aab Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Mon, 6 Nov 2017 21:00:19 -0500 Subject: win: test: check non-shell system() --- test/functional/eval/system_spec.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/functional/eval/system_spec.lua b/test/functional/eval/system_spec.lua index 96212bf538..74c3b77d91 100644 --- a/test/functional/eval/system_spec.lua +++ b/test/functional/eval/system_spec.lua @@ -89,7 +89,9 @@ describe('system()', function() end) it('does NOT run in shell', function() - if not iswin() then + if iswin() then + eq("%PATH%\n", eval("system(['powershell', '-NoProfile', '-NoLogo', '-ExecutionPolicy', 'RemoteSigned', '-Command', 'echo', '%PATH%'])")) + else eq("* $PATH %PATH%\n", eval("system(['echo', '*', '$PATH', '%PATH%'])")) end end) -- cgit From ab1e11e44fd17ffbd4c1fdb26c2b03de3c6831ae Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Wed, 3 Jan 2018 00:18:34 -0500 Subject: test: win: yes is unavailable on Windows --- test/functional/eval/system_spec.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/test/functional/eval/system_spec.lua b/test/functional/eval/system_spec.lua index 74c3b77d91..e2b12b6bc9 100644 --- a/test/functional/eval/system_spec.lua +++ b/test/functional/eval/system_spec.lua @@ -187,6 +187,7 @@ describe('system()', function() end) it('`yes` and is interrupted with CTRL-C', function() + if helpers.pending_win32(pending) then return end feed(':call system("yes")') screen:expect([[ | -- cgit From 8c2cb81d7ecb05f5c399696798690e1861c3b96a Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sun, 7 Jan 2018 16:20:37 +0100 Subject: test: set_shell_powershell(): update flags (#7819) --- test/functional/helpers.lua | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/test/functional/helpers.lua b/test/functional/helpers.lua index bff8d065f8..31a2c3b3ff 100644 --- a/test/functional/helpers.lua +++ b/test/functional/helpers.lua @@ -383,9 +383,8 @@ end local function set_shell_powershell() source([[ - set shell=powershell shellquote=\" shellpipe=\| shellredir=> - set shellcmdflag=\ -NoLogo\ -NoProfile\ -ExecutionPolicy\ RemoteSigned\ -Command - let &shellxquote=' ' + set shell=powershell shellquote=( shellpipe=\| shellredir=> shellxquote= + set shellcmdflag=-NoLogo\ -NoProfile\ -ExecutionPolicy\ RemoteSigned\ -Command ]]) end -- cgit From 6fa0a0a516f3a3a163c50ca0f1f9a8b7443c8860 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sat, 6 Jan 2018 17:56:43 +0100 Subject: ci/travis: macOS: switch ruby version Travis macOS builds are failing because of neovim-ruby gem dependencies. Switch default ruby to a newer version to make the builds pass. --- ci/before_script.sh | 2 ++ ci/common/build.sh | 9 +++++++++ ci/install.sh | 4 ++++ 3 files changed, 15 insertions(+) diff --git a/ci/before_script.sh b/ci/before_script.sh index 445996a8df..e8ecd8c990 100755 --- a/ci/before_script.sh +++ b/ci/before_script.sh @@ -25,6 +25,8 @@ if [[ "${TRAVIS_OS_NAME}" == osx ]]; then # That allows to test changing the group of the file by `os_fchown`. sudo dscl . -create /Groups/chown_test sudo dscl . -append /Groups/chown_test GroupMembership "${USER}" + + macos_rvm_dance fi # Compile dependencies. diff --git a/ci/common/build.sh b/ci/common/build.sh index f398a1a1cc..adfd7b9e8a 100644 --- a/ci/common/build.sh +++ b/ci/common/build.sh @@ -87,3 +87,12 @@ build_nvim() { cd "${TRAVIS_BUILD_DIR}" } + +macos_rvm_dance() { + # neovim-ruby gem requires a ruby newer than the macOS default. + source ~/.rvm/scripts/rvm + rvm get stable --auto-dotfiles + rvm reload + rvm use 2.2.5 + rvm use +} diff --git a/ci/install.sh b/ci/install.sh index 2fe4f88822..f28227d95c 100755 --- a/ci/install.sh +++ b/ci/install.sh @@ -22,6 +22,10 @@ echo "Install neovim module for Python 3." CC=cc pip3 -q install --user --upgrade neovim || true echo "Install neovim RubyGem." +if [ "${TRAVIS_OS_NAME}" = osx ] ; then + macos_rvm_dance + gem update --system +fi gem install --no-document --version ">= 0.2.0" neovim if [[ "${TRAVIS_OS_NAME}" == linux ]]; then -- cgit From 46a9600d0e17f460a940ac6ed605f01f5b12aaf2 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sun, 7 Jan 2018 20:09:03 +0100 Subject: ci/travis: macOS: skip ruby-neovim install With 6fa0a0a516f3 the neovim-ruby gem installs successfully, but ruby_spec.lua can't find it: g:ruby_host_prog needs to be set correctly. Just skip the whole thing for now, so that CI builds don't fail. --- ci/before_script.sh | 2 -- ci/install.sh | 8 +++----- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/ci/before_script.sh b/ci/before_script.sh index e8ecd8c990..445996a8df 100755 --- a/ci/before_script.sh +++ b/ci/before_script.sh @@ -25,8 +25,6 @@ if [[ "${TRAVIS_OS_NAME}" == osx ]]; then # That allows to test changing the group of the file by `os_fchown`. sudo dscl . -create /Groups/chown_test sudo dscl . -append /Groups/chown_test GroupMembership "${USER}" - - macos_rvm_dance fi # Compile dependencies. diff --git a/ci/install.sh b/ci/install.sh index f28227d95c..f51ba1a776 100755 --- a/ci/install.sh +++ b/ci/install.sh @@ -21,12 +21,10 @@ echo "Install neovim module for Python 3." # https://github.com/travis-ci/travis-ci/issues/8363 CC=cc pip3 -q install --user --upgrade neovim || true -echo "Install neovim RubyGem." -if [ "${TRAVIS_OS_NAME}" = osx ] ; then - macos_rvm_dance - gem update --system +if ! [ "${TRAVIS_OS_NAME}" = osx ] ; then + echo "Install neovim RubyGem." + gem install --no-document --version ">= 0.2.0" neovim fi -gem install --no-document --version ">= 0.2.0" neovim if [[ "${TRAVIS_OS_NAME}" == linux ]]; then echo "Install neovim npm package" -- cgit From 9370a0e5d859f692f6fa24c27c76410b54595be4 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sun, 7 Jan 2018 18:37:31 -0500 Subject: ci/travis: install neovim npm module on osx (#7825) Always get latest nvm on osx to fix lts aliases. --- ci/before_install.sh | 16 +++++++--------- ci/install.sh | 6 ++---- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/ci/before_install.sh b/ci/before_install.sh index f5a57ad657..9f512ee5b9 100755 --- a/ci/before_install.sh +++ b/ci/before_install.sh @@ -38,14 +38,12 @@ else pip3 -q install --user --upgrade pip || true fi -if [[ "${TRAVIS_OS_NAME}" == linux ]]; then - echo "Install node (LTS)" +echo "Install node (LTS)" - if [ ! -f ~/.nvm/nvm.sh ]; then - curl -o ~/.nvm/nvm.sh https://raw.githubusercontent.com/creationix/nvm/master/nvm.sh - fi - - source ~/.nvm/nvm.sh - nvm install --lts - nvm use --lts +if [[ "${TRAVIS_OS_NAME}" == osx ]] || [ ! -f ~/.nvm/nvm.sh ]; then + curl -o ~/.nvm/nvm.sh https://raw.githubusercontent.com/creationix/nvm/master/nvm.sh fi + +source ~/.nvm/nvm.sh +nvm install --lts +nvm use --lts diff --git a/ci/install.sh b/ci/install.sh index f51ba1a776..eb7fb14760 100755 --- a/ci/install.sh +++ b/ci/install.sh @@ -26,7 +26,5 @@ if ! [ "${TRAVIS_OS_NAME}" = osx ] ; then gem install --no-document --version ">= 0.2.0" neovim fi -if [[ "${TRAVIS_OS_NAME}" == linux ]]; then - echo "Install neovim npm package" - npm install -g neovim -fi +echo "Install neovim npm package" +npm install -g neovim -- cgit From b61a305039a58ac0c2ff095b22e4eb6d7ccfc89e Mon Sep 17 00:00:00 2001 From: ckelsel Date: Mon, 8 Jan 2018 12:23:33 +0800 Subject: vim-patch:8.0.0351: no test for concatenating an empty string Problem: No test for concatenating an empty string that results from out of bounds indexing. Solution: Add a simple test. https://github.com/vim/vim/commit/218426896cbb2129aa4e85803ea97c5b57df1eaa --- src/nvim/testdir/test_expr.vim | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/nvim/testdir/test_expr.vim b/src/nvim/testdir/test_expr.vim index d32facaa98..ad967c528c 100644 --- a/src/nvim/testdir/test_expr.vim +++ b/src/nvim/testdir/test_expr.vim @@ -439,3 +439,8 @@ func Test_funcref() call assert_equal(2, OneByRef()) call assert_fails('echo funcref("{")', 'E475:') endfunc + +func Test_empty_concatenate() + call assert_equal('b', 'a'[4:0] . 'b') + call assert_equal('b', 'b' . 'a'[4:0]) +endfunc -- cgit From e182a8c8363a72fe3204a24f5f82cd129c8ad9ef Mon Sep 17 00:00:00 2001 From: ckelsel Date: Mon, 8 Jan 2018 12:27:09 +0800 Subject: vim-patch:8.0.0352: not easy to see when a typval needs to be cleared Problem: The condition for when a typval needs to be cleared is too complicated. Solution: Init the type to VAR_UNKNOWN and clear it always. https://github.com/vim/vim/commit/f06e5a549f42396be3478ccc1b5f03be64e1173e --- src/nvim/eval.c | 53 ++++++++++++++++++----------------------------------- 1 file changed, 18 insertions(+), 35 deletions(-) diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 73bfcd4291..f34b6db8d8 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -2084,6 +2084,8 @@ static char_u *get_lval(char_u *const name, typval_T *const rettv, * Loop until no more [idx] or .key is following. */ lp->ll_tv = &v->di_tv; + var1.v_type = VAR_UNKNOWN; + var2.v_type = VAR_UNKNOWN; while (*p == '[' || (*p == '.' && lp->ll_tv->v_type == VAR_DICT)) { if (!(lp->ll_tv->v_type == VAR_LIST && lp->ll_tv->vval.v_list != NULL) && !(lp->ll_tv->v_type == VAR_DICT @@ -2134,9 +2136,7 @@ static char_u *get_lval(char_u *const name, typval_T *const rettv, if (!quiet) { EMSG(_(e_dictrange)); } - if (!empty1) { - tv_clear(&var1); - } + tv_clear(&var1); return NULL; } if (rettv != NULL && (rettv->v_type != VAR_LIST @@ -2144,9 +2144,7 @@ static char_u *get_lval(char_u *const name, typval_T *const rettv, if (!quiet) { emsgf(_("E709: [:] requires a List value")); } - if (!empty1) { - tv_clear(&var1); - } + tv_clear(&var1); return NULL; } p = skipwhite(p + 1); @@ -2155,16 +2153,12 @@ static char_u *get_lval(char_u *const name, typval_T *const rettv, } else { lp->ll_empty2 = false; if (eval1(&p, &var2, true) == FAIL) { // Recursive! - if (!empty1) { - tv_clear(&var1); - } + tv_clear(&var1); return NULL; } if (!tv_check_str(&var2)) { // Not a number or string. - if (!empty1) { - tv_clear(&var1); - } + tv_clear(&var1); tv_clear(&var2); return NULL; } @@ -2177,12 +2171,8 @@ static char_u *get_lval(char_u *const name, typval_T *const rettv, if (!quiet) { emsgf(_(e_missbrac)); } - if (!empty1) { - tv_clear(&var1); - } - if (lp->ll_range && !lp->ll_empty2) { - tv_clear(&var2); - } + tv_clear(&var1); + tv_clear(&var2); return NULL; } @@ -2236,9 +2226,7 @@ static char_u *get_lval(char_u *const name, typval_T *const rettv, if (!quiet) { emsgf(_(e_dictkey), key); } - if (len == -1) { - tv_clear(&var1); - } + tv_clear(&var1); return NULL; } if (len == -1) { @@ -2246,32 +2234,28 @@ static char_u *get_lval(char_u *const name, typval_T *const rettv, } else { lp->ll_newkey = vim_strnsave(key, len); } - if (len == -1) { - tv_clear(&var1); - } + tv_clear(&var1); break; // existing variable, need to check if it can be changed } else if (!(flags & GLV_READ_ONLY) && var_check_ro(lp->ll_di->di_flags, (const char *)name, (size_t)(p - name))) { - if (len == -1) { - tv_clear(&var1); - } + tv_clear(&var1); return NULL; } - if (len == -1) { - tv_clear(&var1); - } + tv_clear(&var1); lp->ll_tv = &lp->ll_di->di_tv; } else { // Get the number and item for the only or first index of the List. if (empty1) { lp->ll_n1 = 0; } else { - lp->ll_n1 = (long)tv_get_number(&var1); // Is number or string. - tv_clear(&var1); + // Is number or string. + lp->ll_n1 = (long)tv_get_number(&var1); } + tv_clear(&var1); + lp->ll_dict = NULL; lp->ll_list = lp->ll_tv->vval.v_list; lp->ll_li = tv_list_find(lp->ll_list, lp->ll_n1); @@ -2282,9 +2266,7 @@ static char_u *get_lval(char_u *const name, typval_T *const rettv, } } if (lp->ll_li == NULL) { - if (lp->ll_range && !lp->ll_empty2) { - tv_clear(&var2); - } + tv_clear(&var2); if (!quiet) { EMSGN(_(e_listidx), lp->ll_n1); } @@ -2326,6 +2308,7 @@ static char_u *get_lval(char_u *const name, typval_T *const rettv, } } + tv_clear(&var1); return p; } -- cgit From a8ad6b4d51eb5cb8a610753b6c7ade3cce3db9cc Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Tue, 9 Jan 2018 10:37:49 +0100 Subject: cmake: install *.lua files --- runtime/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/CMakeLists.txt b/runtime/CMakeLists.txt index 52a8609fec..d992c2cc77 100644 --- a/runtime/CMakeLists.txt +++ b/runtime/CMakeLists.txt @@ -138,7 +138,7 @@ endforeach() file(GLOB_RECURSE RUNTIME_FILES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} rgb.txt - *.vim *.dict *.py *.rb *.ps *.tutor *.tutor.json) + *.vim *.lua *.dict *.py *.rb *.ps *.tutor *.tutor.json) foreach(F ${RUNTIME_FILES}) get_filename_component(BASEDIR ${F} PATH) -- cgit From b58c17f8df95d7626aec57de36781daa277314ee Mon Sep 17 00:00:00 2001 From: dvejmz Date: Sat, 21 Oct 2017 16:48:47 +0100 Subject: vim-patch:8.0.0198 Problem: Some syntax arguments take effect even after "if 0". (Taylor Venable) Solution: Properly skip the syntax statements. Make "syn case" and "syn conceal" report the current state. Fix that "syn clear" didn't reset the conceal flag. Add tests for :syntax skipping properly. https://github.com/vim/vim/commit/de318c5c35ed0d65fd2a07196cb8acd5ee6d9bf8 --- src/nvim/syntax.c | 75 +++++++++++++++------- src/nvim/testdir/test_syntax.vim | 132 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 185 insertions(+), 22 deletions(-) diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index d1a5f0bd1c..2222f41c78 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -3019,12 +3019,19 @@ static void syn_cmd_conceal(exarg_T *eap, int syncing) return; next = skiptowhite(arg); - if (STRNICMP(arg, "on", 2) == 0 && next - arg == 2) + if (*arg == NUL) { + if (curwin->w_s->b_syn_conceal) { + MSG(_("syn conceal on")); + } else { + MSG(_("syn conceal off")); + } + } else if (STRNICMP(arg, "on", 2) == 0 && next - arg == 2) { curwin->w_s->b_syn_conceal = TRUE; - else if (STRNICMP(arg, "off", 3) == 0 && next - arg == 3) + } else if (STRNICMP(arg, "off", 3) == 0 && next - arg == 3) { curwin->w_s->b_syn_conceal = FALSE; - else + } else { EMSG2(_("E390: Illegal argument: %s"), arg); + } } /* @@ -3040,12 +3047,19 @@ static void syn_cmd_case(exarg_T *eap, int syncing) return; next = skiptowhite(arg); - if (STRNICMP(arg, "match", 5) == 0 && next - arg == 5) + if (*arg == NUL) { + if (curwin->w_s->b_syn_ic) { + MSG(_("syntax case ignore")); + } else { + MSG(_("syntax case match")); + } + } else if (STRNICMP(arg, "match", 5) == 0 && next - arg == 5) { curwin->w_s->b_syn_ic = FALSE; - else if (STRNICMP(arg, "ignore", 6) == 0 && next - arg == 6) + } else if (STRNICMP(arg, "ignore", 6) == 0 && next - arg == 6) { curwin->w_s->b_syn_ic = TRUE; - else + } else { EMSG2(_("E390: Illegal argument: %s"), arg); + } } /* @@ -3061,7 +3075,15 @@ static void syn_cmd_spell(exarg_T *eap, int syncing) return; next = skiptowhite(arg); - if (STRNICMP(arg, "toplevel", 8) == 0 && next - arg == 8) { + if (*arg == NUL) { + if (curwin->w_s->b_syn_spell == SYNSPL_TOP) { + MSG(_("syntax spell toplevel")); + } else if (curwin->w_s->b_syn_spell == SYNSPL_NOTOP) { + MSG(_("syntax spell notoplevel")); + } else { + MSG(_("syntax spell default")); + } + } else if (STRNICMP(arg, "toplevel", 8) == 0 && next - arg == 8) { curwin->w_s->b_syn_spell = SYNSPL_TOP; } else if (STRNICMP(arg, "notoplevel", 10) == 0 && next - arg == 10) { curwin->w_s->b_syn_spell = SYNSPL_NOTOP; @@ -3125,6 +3147,7 @@ void syntax_clear(synblock_T *block) block->b_syn_ic = FALSE; /* Use case, by default */ block->b_syn_spell = SYNSPL_DEFAULT; /* default spell checking */ block->b_syn_containedin = FALSE; + block->b_syn_conceal = FALSE; /* free the keywords */ clear_keywtab(&block->b_keywtab); @@ -4004,7 +4027,8 @@ static char_u * get_syn_options ( char_u *arg, /* next argument to be checked */ syn_opt_arg_T *opt, /* various things */ - int *conceal_char + int *conceal_char, + int skip /* TRUE if skipping over command */ ) { char_u *gname_start, *gname; @@ -4080,13 +4104,13 @@ get_syn_options ( EMSG(_("E395: contains argument not accepted here")); return NULL; } - if (get_id_list(&arg, 8, &opt->cont_list) == FAIL) + if (get_id_list(&arg, 8, &opt->cont_list, skip) == FAIL) return NULL; } else if (flagtab[fidx].argtype == 2) { - if (get_id_list(&arg, 11, &opt->cont_in_list) == FAIL) + if (get_id_list(&arg, 11, &opt->cont_in_list, skip) == FAIL) return NULL; } else if (flagtab[fidx].argtype == 3) { - if (get_id_list(&arg, 9, &opt->next_list) == FAIL) + if (get_id_list(&arg, 9, &opt->next_list, skip) == FAIL) return NULL; } else if (flagtab[fidx].argtype == 11 && arg[5] == '=') { /* cchar=? */ @@ -4257,7 +4281,11 @@ static void syn_cmd_keyword(exarg_T *eap, int syncing) rest = get_group_name(arg, &group_name_end); if (rest != NULL) { - syn_id = syn_check_group(arg, (int)(group_name_end - arg)); + if (eap->skip) { + syn_id = -1; + } else { + syn_id = syn_check_group(arg, (int)(group_name_end - arg)); + } if (syn_id != 0) { // Allocate a buffer, for removing backslashes in the keyword. keyword_copy = xmalloc(STRLEN(rest) + 1); @@ -4276,7 +4304,7 @@ static void syn_cmd_keyword(exarg_T *eap, int syncing) cnt = 0; p = keyword_copy; for (; rest != NULL && !ends_excmd(*rest); rest = skipwhite(rest)) { - rest = get_syn_options(rest, &syn_opt_arg, &conceal_char); + rest = get_syn_options(rest, &syn_opt_arg, &conceal_char, eap->skip); if (rest == NULL || ends_excmd(*rest)) { break; } @@ -4375,7 +4403,7 @@ syn_cmd_match ( syn_opt_arg.cont_list = NULL; syn_opt_arg.cont_in_list = NULL; syn_opt_arg.next_list = NULL; - rest = get_syn_options(rest, &syn_opt_arg, &conceal_char); + rest = get_syn_options(rest, &syn_opt_arg, &conceal_char, eap->skip); /* get the pattern. */ init_syn_patterns(); @@ -4385,7 +4413,7 @@ syn_cmd_match ( syn_opt_arg.flags |= HL_HAS_EOL; /* Get options after the pattern */ - rest = get_syn_options(rest, &syn_opt_arg, &conceal_char); + rest = get_syn_options(rest, &syn_opt_arg, &conceal_char, eap->skip); if (rest != NULL) { /* all arguments are valid */ /* @@ -4502,7 +4530,7 @@ syn_cmd_region ( */ while (rest != NULL && !ends_excmd(*rest)) { /* Check for option arguments */ - rest = get_syn_options(rest, &syn_opt_arg, &conceal_char); + rest = get_syn_options(rest, &syn_opt_arg, &conceal_char, eap->skip); if (rest == NULL || ends_excmd(*rest)) break; @@ -4926,12 +4954,14 @@ static void syn_cmd_cluster(exarg_T *eap, int syncing) break; clstr_list = NULL; - if (get_id_list(&rest, opt_len, &clstr_list) == FAIL) { + if (get_id_list(&rest, opt_len, &clstr_list, eap->skip) == FAIL) { EMSG2(_(e_invarg2), rest); break; } - syn_combine_list(&SYN_CLSTR(curwin->w_s)[scl_id].scl_list, - &clstr_list, list_op); + if (scl_id >= 0) { + syn_combine_list(&SYN_CLSTR(curwin->w_s)[scl_id].scl_list, + &clstr_list, list_op); + } got_clstr = TRUE; } @@ -5181,8 +5211,9 @@ static int get_id_list ( char_u **arg, int keylen, /* length of keyword */ - short **list /* where to store the resulting list, if not + short **list, /* where to store the resulting list, if not NULL, the list is silently skipped! */ + int skip ) { char_u *p = NULL; @@ -5251,7 +5282,9 @@ get_id_list ( id = SYNID_CONTAINED; id += current_syn_inc_tag; } else if (name[1] == '@') { - id = syn_check_cluster(name + 2, (int)(end - p - 1)); + if (!skip) { + id = syn_check_cluster(name + 2, (int)(end - p - 1)); + } } else { /* * Handle full group name. diff --git a/src/nvim/testdir/test_syntax.vim b/src/nvim/testdir/test_syntax.vim index 6c084dd2a7..64b3c14d73 100644 --- a/src/nvim/testdir/test_syntax.vim +++ b/src/nvim/testdir/test_syntax.vim @@ -157,4 +157,134 @@ func Test_syntax_completion() call feedkeys(":syn match \\\"\", 'tx') call assert_match('^"syn match Boolean Character ', @:) -endfunc \ No newline at end of file +endfunc + +func Test_syntax_arg_skipped() + syn clear + syntax case ignore + if 0 + syntax case match + endif + call assert_match('case ignore', execute('syntax case')) + + syn keyword Foo foo + call assert_match('Foo', execute('syntax')) + syn clear + call assert_match('case match', execute('syntax case')) + call assert_notmatch('Foo', execute('syntax')) + + if has('conceal') + syn clear + syntax conceal on + if 0 + syntax conceal off + endif + call assert_match('conceal on', execute('syntax conceal')) + syn clear + call assert_match('conceal off', execute('syntax conceal')) + endif + + syntax region Tar start=// + if 0 + syntax region NotTest start=// contains=@Spell + endif + call assert_match('Tar', execute('syntax')) + call assert_notmatch('NotTest', execute('syntax')) + call assert_notmatch('Spell', execute('syntax')) + + hi Foo ctermfg=blue + let a = execute('hi Foo') + if 0 + syntax rest + endif + call assert_equal(a, execute('hi Foo')) + + set ft=tags + syn off + if 0 + syntax enable + endif + call assert_match('No Syntax items defined', execute('syntax')) + syntax enable + call assert_match('tagComment', execute('syntax')) + set ft= + + syn clear + if 0 + syntax include @Spell nothing + endif + call assert_notmatch('Spell', execute('syntax')) + + syn clear + syn iskeyword 48-57,$,_ + call assert_match('48-57,$,_', execute('syntax iskeyword')) + if 0 + syn clear + syn iskeyword clear + endif + call assert_match('48-57,$,_', execute('syntax iskeyword')) + syn iskeyword clear + call assert_match('not set', execute('syntax iskeyword')) + syn iskeyword 48-57,$,_ + syn clear + call assert_match('not set', execute('syntax iskeyword')) + + syn clear + syn keyword Foo foo + if 0 + syn keyword NotAdded bar + endif + call assert_match('Foo', execute('syntax')) + call assert_notmatch('NotAdded', execute('highlight')) + + syn clear + syn keyword Foo foo + call assert_match('Foo', execute('syntax')) + call assert_match('Foo', execute('syntax list')) + call assert_notmatch('Foo', execute('if 0 | syntax | endif')) + call assert_notmatch('Foo', execute('if 0 | syntax list | endif')) + + syn clear + syn match Fopi /asdf/ + if 0 + syn match Fopx /asdf/ + endif + call assert_match('Fopi', execute('syntax')) + call assert_notmatch('Fopx', execute('syntax')) + + syn clear + syn spell toplevel + call assert_match('spell toplevel', execute('syntax spell')) + if 0 + syn spell notoplevel + endif + call assert_match('spell toplevel', execute('syntax spell')) + syn spell notoplevel + call assert_match('spell notoplevel', execute('syntax spell')) + syn spell default + call assert_match('spell default', execute('syntax spell')) + + syn clear + if 0 + syntax cluster Spell + endif + call assert_notmatch('Spell', execute('syntax')) + + syn clear + syn keyword Foo foo + syn sync ccomment + syn sync maxlines=5 + if 0 + syn sync maxlines=11 + endif + call assert_match('on C-style comments', execute('syntax sync')) + call assert_match('maximal 5 lines', execute('syntax sync')) + syn clear + syn keyword Foo foo + if 0 + syn sync ccomment + endif + call assert_notmatch('on C-style comments', execute('syntax sync')) + + syn clear +endfunc -- cgit From 889bc3c20cf308b2bda43358639b739fe987e1ec Mon Sep 17 00:00:00 2001 From: dvejmz Date: Mon, 8 Jan 2018 19:37:24 +0000 Subject: vim-patch:8.0.0200: some syntax arguments are not tested Problem: Some syntax arguments are not tested. Solution: Add more syntax command tests. https://github.com/vim/vim/commit/58f60ca2fcd2858faac84e386b3ccf5ced75084d --- src/nvim/testdir/test_syntax.vim | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/nvim/testdir/test_syntax.vim b/src/nvim/testdir/test_syntax.vim index 64b3c14d73..636bdb8a4d 100644 --- a/src/nvim/testdir/test_syntax.vim +++ b/src/nvim/testdir/test_syntax.vim @@ -184,6 +184,10 @@ func Test_syntax_arg_skipped() call assert_match('conceal off', execute('syntax conceal')) endif + syntax conceal on + syntax conceal off + call assert_match('conceal off', execute('syntax conceal')) + syntax region Tar start=// if 0 syntax region NotTest start=// contains=@Spell -- cgit From 7f70c5f7b7fd0c0241613f1cc189b5e26a7642dc Mon Sep 17 00:00:00 2001 From: dvejmz Date: Mon, 8 Jan 2018 21:37:12 +0000 Subject: vim-patch:8.0.0201: completion of highlight groups includes cleared names Problem: When completing a group name for a highlight or syntax command cleared groups are included. Solution: Skip groups that have been cleared. https://github.com/vim/vim/commit/d61e8aaae57bd66279def479462bf11c22ec2f1c --- src/nvim/syntax.c | 19 +++++++++++++++-- src/nvim/testdir/test_syntax.vim | 45 ++++++++++++++++++++++++++++++++++++---- 2 files changed, 58 insertions(+), 6 deletions(-) diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index 2222f41c78..b274e22caa 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -52,6 +52,7 @@ static bool did_syntax_onoff = false; struct hl_group { char_u *sg_name; ///< highlight group name char_u *sg_name_u; ///< uppercase of sg_name + int sg_cleared; ///< "hi clear" was used int sg_attr; ///< Screen attr @see ATTR_ENTRY int sg_link; ///< link to this highlight group ID int sg_set; ///< combination of flags in \ref SG_SET @@ -6490,6 +6491,7 @@ void do_highlight(const char *line, const bool forceit, const bool init) HL_TABLE()[from_id - 1].sg_set |= SG_LINK; HL_TABLE()[from_id - 1].sg_link = to_id; HL_TABLE()[from_id - 1].sg_scriptID = current_SID; + HL_TABLE()[from_id - 1].sg_cleared = false; redraw_all_later(SOME_VALID); } } @@ -6872,6 +6874,7 @@ void do_highlight(const char *line, const bool forceit, const bool init) error = true; break; } + HL_TABLE()[idx].sg_cleared = false; // When highlighting has been given for a group, don't link it. if (!init || !(HL_TABLE()[idx].sg_set & SG_LINK)) { @@ -6953,6 +6956,8 @@ static int hl_has_settings(int idx, int check_link) */ static void highlight_clear(int idx) { + HL_TABLE()[idx].sg_cleared = true; + HL_TABLE()[idx].sg_attr = 0; HL_TABLE()[idx].sg_cterm = 0; HL_TABLE()[idx].sg_cterm_bold = FALSE; @@ -7763,10 +7768,20 @@ const char *get_highlight_name(expand_T *const xp, const int idx) } else if (idx == highlight_ga.ga_len + include_none + include_default + 1 && include_link != 0) { return "clear"; - } else if (idx < 0 || idx >= highlight_ga.ga_len) { + } else if (idx < 0) { return NULL; } - return (const char *)HL_TABLE()[idx].sg_name; + + /* Items are never removed from the table, skip the ones that were cleared. + */ + int current_idx = idx; + while (current_idx < highlight_ga.ga_len && HL_TABLE()[current_idx].sg_cleared) { + ++current_idx; + } + if (current_idx >= highlight_ga.ga_len) { + return NULL; + } + return (const char *)HL_TABLE()[current_idx].sg_name; } color_name_table_T color_name_table[] = { diff --git a/src/nvim/testdir/test_syntax.vim b/src/nvim/testdir/test_syntax.vim index 636bdb8a4d..39259fe680 100644 --- a/src/nvim/testdir/test_syntax.vim +++ b/src/nvim/testdir/test_syntax.vim @@ -152,6 +152,12 @@ func Test_syntax_completion() call feedkeys(":syn sync \\\"\", 'tx') call assert_equal('"syn sync ccomment clear fromstart linebreaks= linecont lines= match maxlines= minlines= region', @:) + " Check that clearing "Aap" avoids it showing up before Boolean. + hi Aap ctermfg=blue + call feedkeys(":syn list \\\"\", 'tx') + call assert_match('^"syn list Aap Boolean Character ', @:) + hi clear Aap + call feedkeys(":syn list \\\"\", 'tx') call assert_match('^"syn list Boolean Character ', @:) @@ -188,11 +194,11 @@ func Test_syntax_arg_skipped() syntax conceal off call assert_match('conceal off', execute('syntax conceal')) - syntax region Tar start=// + syntax region Bar start=// if 0 syntax region NotTest start=// contains=@Spell endif - call assert_match('Tar', execute('syntax')) + call assert_match('Bar', execute('syntax')) call assert_notmatch('NotTest', execute('syntax')) call assert_notmatch('Spell', execute('syntax')) @@ -202,6 +208,8 @@ func Test_syntax_arg_skipped() syntax rest endif call assert_equal(a, execute('hi Foo')) + hi clear Bar + hi clear Foo set ft=tags syn off @@ -283,8 +291,7 @@ func Test_syntax_arg_skipped() endif call assert_match('on C-style comments', execute('syntax sync')) call assert_match('maximal 5 lines', execute('syntax sync')) - syn clear - syn keyword Foo foo + syn sync clear if 0 syn sync ccomment endif @@ -292,3 +299,33 @@ func Test_syntax_arg_skipped() syn clear endfunc + +func Test_invalid_arg() + call assert_fails('syntax case asdf', 'E390:') + call assert_fails('syntax conceal asdf', 'E390:') + call assert_fails('syntax spell asdf', 'E390:') +endfunc + +func Test_syn_sync() + syntax region HereGroup start=/this/ end=/that/ + syntax sync match SyncHere grouphere HereGroup "pattern" + call assert_match('SyncHere', execute('syntax sync')) + syn sync clear + call assert_notmatch('SyncHere', execute('syntax sync')) + syn clear +endfunc + +func Test_syn_clear() + syntax keyword Foo foo + syntax keyword Bar tar + call assert_match('Foo', execute('syntax')) + call assert_match('Bar', execute('syntax')) + syn clear Foo + call assert_notmatch('Foo', execute('syntax')) + call assert_match('Bar', execute('syntax')) + syn clear Foo Bar + call assert_notmatch('Foo', execute('syntax')) + call assert_notmatch('Bar', execute('syntax')) + hi clear Foo + hi clear Bar +endfunc -- cgit From e888439f5589d00f2ac545e38a29924f91dad38e Mon Sep 17 00:00:00 2001 From: dvejmz Date: Mon, 8 Jan 2018 22:36:05 +0000 Subject: vim-patch:8.0.0202: no test for invalid syntax group name Problem: No test for invalid syntax group name. Solution: Add a test for group name error and warning. https://github.com/vim/vim/commit/4007ed4a5e8c34197078e9d5718bd1d4a429dd23 --- src/nvim/testdir/test_syntax.vim | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/nvim/testdir/test_syntax.vim b/src/nvim/testdir/test_syntax.vim index 39259fe680..6456ca2b23 100644 --- a/src/nvim/testdir/test_syntax.vim +++ b/src/nvim/testdir/test_syntax.vim @@ -329,3 +329,14 @@ func Test_syn_clear() hi clear Foo hi clear Bar endfunc + +func Test_invalid_name() + syn clear + syn keyword Nop yes + call assert_fails("syntax keyword Wr\x17ong bar", 'E669:') + syntax keyword @Wrong bar + call assert_match('W18:', execute('1messages')) + syn clear + hi clear Nop + hi clear @Wrong +endfunc -- cgit From b23fc444b52a7ea10ffce4eca01d9429859147c9 Mon Sep 17 00:00:00 2001 From: dvejmz Date: Mon, 8 Jan 2018 22:40:21 +0000 Subject: vim-patch:8.0.0204: compiler warns for uninitialized variable Problem: Compiler warns for uninitialized variable. (Tony Mechelynck) Solution: When skipping set "id" to -1. https://github.com/vim/vim/commit/eb46f8fa14a586779f55b1c7f1648f559618322e --- src/nvim/syntax.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index b274e22caa..cf68971f76 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -5283,7 +5283,9 @@ get_id_list ( id = SYNID_CONTAINED; id += current_syn_inc_tag; } else if (name[1] == '@') { - if (!skip) { + if (skip) { + id = -1; + } else { id = syn_check_cluster(name + 2, (int)(end - p - 1)); } } else { -- cgit From 34de6d33c9e4ea7b2c67319eafe8345db7fe0151 Mon Sep 17 00:00:00 2001 From: dvejmz Date: Tue, 9 Jan 2018 22:58:24 +0000 Subject: lint --- src/nvim/syntax.c | 73 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 38 insertions(+), 35 deletions(-) diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index cf68971f76..01d2728f3d 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -52,7 +52,7 @@ static bool did_syntax_onoff = false; struct hl_group { char_u *sg_name; ///< highlight group name char_u *sg_name_u; ///< uppercase of sg_name - int sg_cleared; ///< "hi clear" was used + int sg_cleared; ///< "hi clear" was used int sg_attr; ///< Screen attr @see ATTR_ENTRY int sg_link; ///< link to this highlight group ID int sg_set; ///< combination of flags in \ref SG_SET @@ -3027,9 +3027,9 @@ static void syn_cmd_conceal(exarg_T *eap, int syncing) MSG(_("syn conceal off")); } } else if (STRNICMP(arg, "on", 2) == 0 && next - arg == 2) { - curwin->w_s->b_syn_conceal = TRUE; + curwin->w_s->b_syn_conceal = true; } else if (STRNICMP(arg, "off", 3) == 0 && next - arg == 3) { - curwin->w_s->b_syn_conceal = FALSE; + curwin->w_s->b_syn_conceal = false; } else { EMSG2(_("E390: Illegal argument: %s"), arg); } @@ -3055,9 +3055,9 @@ static void syn_cmd_case(exarg_T *eap, int syncing) MSG(_("syntax case match")); } } else if (STRNICMP(arg, "match", 5) == 0 && next - arg == 5) { - curwin->w_s->b_syn_ic = FALSE; + curwin->w_s->b_syn_ic = false; } else if (STRNICMP(arg, "ignore", 6) == 0 && next - arg == 6) { - curwin->w_s->b_syn_ic = TRUE; + curwin->w_s->b_syn_ic = true; } else { EMSG2(_("E390: Illegal argument: %s"), arg); } @@ -3144,11 +3144,11 @@ static void syn_cmd_iskeyword(exarg_T *eap, int syncing) */ void syntax_clear(synblock_T *block) { - block->b_syn_error = FALSE; /* clear previous error */ - block->b_syn_ic = FALSE; /* Use case, by default */ - block->b_syn_spell = SYNSPL_DEFAULT; /* default spell checking */ - block->b_syn_containedin = FALSE; - block->b_syn_conceal = FALSE; + block->b_syn_error = false; // clear previous error + block->b_syn_ic = false; // Use case, by default + block->b_syn_spell = SYNSPL_DEFAULT; // default spell checking + block->b_syn_containedin = false; + block->b_syn_conceal = false; /* free the keywords */ clear_keywtab(&block->b_keywtab); @@ -4025,11 +4025,11 @@ get_group_name ( * Return NULL for any error; */ static char_u * -get_syn_options ( - char_u *arg, /* next argument to be checked */ - syn_opt_arg_T *opt, /* various things */ +get_syn_options( + char_u *arg, // next argument to be checked + syn_opt_arg_T *opt, // various things int *conceal_char, - int skip /* TRUE if skipping over command */ + int skip // TRUE if skipping over command ) { char_u *gname_start, *gname; @@ -4105,14 +4105,17 @@ get_syn_options ( EMSG(_("E395: contains argument not accepted here")); return NULL; } - if (get_id_list(&arg, 8, &opt->cont_list, skip) == FAIL) + if (get_id_list(&arg, 8, &opt->cont_list, skip) == FAIL) { return NULL; + } } else if (flagtab[fidx].argtype == 2) { - if (get_id_list(&arg, 11, &opt->cont_in_list, skip) == FAIL) + if (get_id_list(&arg, 11, &opt->cont_in_list, skip) == FAIL) { return NULL; + } } else if (flagtab[fidx].argtype == 3) { - if (get_id_list(&arg, 9, &opt->next_list, skip) == FAIL) + if (get_id_list(&arg, 9, &opt->next_list, skip) == FAIL) { return NULL; + } } else if (flagtab[fidx].argtype == 11 && arg[5] == '=') { /* cchar=? */ if (has_mbyte) { @@ -4410,10 +4413,11 @@ syn_cmd_match ( init_syn_patterns(); memset(&item, 0, sizeof(item)); rest = get_syn_pattern(rest, &item); - if (vim_regcomp_had_eol() && !(syn_opt_arg.flags & HL_EXCLUDENL)) + if (vim_regcomp_had_eol() && !(syn_opt_arg.flags & HL_EXCLUDENL)) { syn_opt_arg.flags |= HL_HAS_EOL; + } - /* Get options after the pattern */ + // Get options after the pattern rest = get_syn_options(rest, &syn_opt_arg, &conceal_char, eap->skip); if (rest != NULL) { /* all arguments are valid */ @@ -4526,14 +4530,13 @@ syn_cmd_region ( syn_opt_arg.cont_in_list = NULL; syn_opt_arg.next_list = NULL; - /* - * get the options, patterns and matchgroup. - */ + // get the options, patterns and matchgroup. while (rest != NULL && !ends_excmd(*rest)) { - /* Check for option arguments */ + // Check for option arguments rest = get_syn_options(rest, &syn_opt_arg, &conceal_char, eap->skip); - if (rest == NULL || ends_excmd(*rest)) + if (rest == NULL || ends_excmd(*rest)) { break; + } /* must be a pattern or matchgroup then */ key_end = rest; @@ -4963,7 +4966,7 @@ static void syn_cmd_cluster(exarg_T *eap, int syncing) syn_combine_list(&SYN_CLSTR(curwin->w_s)[scl_id].scl_list, &clstr_list, list_op); } - got_clstr = TRUE; + got_clstr = true; } if (got_clstr) { @@ -5211,9 +5214,9 @@ static void syn_cmd_sync(exarg_T *eap, int syncing) static int get_id_list ( char_u **arg, - int keylen, /* length of keyword */ - short **list, /* where to store the resulting list, if not - NULL, the list is silently skipped! */ + int keylen, // length of keyword + int16_t **list, // where to store the resulting list, if not + // NULL, the list is silently skipped! int skip ) { @@ -6493,7 +6496,7 @@ void do_highlight(const char *line, const bool forceit, const bool init) HL_TABLE()[from_id - 1].sg_set |= SG_LINK; HL_TABLE()[from_id - 1].sg_link = to_id; HL_TABLE()[from_id - 1].sg_scriptID = current_SID; - HL_TABLE()[from_id - 1].sg_cleared = false; + HL_TABLE()[from_id - 1].sg_cleared = false; redraw_all_later(SOME_VALID); } } @@ -7774,14 +7777,14 @@ const char *get_highlight_name(expand_T *const xp, const int idx) return NULL; } - /* Items are never removed from the table, skip the ones that were cleared. - */ - int current_idx = idx; - while (current_idx < highlight_ga.ga_len && HL_TABLE()[current_idx].sg_cleared) { - ++current_idx; + // Items are never removed from the table, skip the ones that were cleared. + int current_idx = idx; + while (current_idx < highlight_ga.ga_len + && HL_TABLE()[current_idx].sg_cleared) { + current_idx++; } if (current_idx >= highlight_ga.ga_len) { - return NULL; + return NULL; } return (const char *)HL_TABLE()[current_idx].sg_name; } -- cgit From f0845197d868735dc97aac72738b69c639c634b3 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Wed, 10 Jan 2018 23:21:30 +0100 Subject: ci/travis: require "sudo" for ASAN_UBSAN build Workaround for this fun new issue: ==27404==LeakSanitizer has encountered a fatal error. ==27404==HINT: For debugging, try setting environment variable LSAN_OPTIONS=verbosity=1:log_threads=1 ==27404==HINT: LeakSanitizer does not work under ptrace (strace, gdb, etc) Failed: E /build|logs :: Runtime errors detected. https://github.com/travis-ci/travis-ci/issues/9033 https://github.com/google/sanitizers/issues/764 --- .travis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 09ef8bba46..9a62a8a942 100644 --- a/.travis.yml +++ b/.travis.yml @@ -57,13 +57,13 @@ jobs: env: > CLANG_SANITIZER=ASAN_UBSAN CMAKE_FLAGS="$CMAKE_FLAGS -DPREFER_LUA=ON" + sudo: true - os: linux compiler: gcc env: FUNCTIONALTEST=functionaltest-lua - os: linux - # Travis creates a cache per compiler. - # Set a different value here to store 32-bit - # dependencies in a separate cache. + # Travis creates a cache per compiler. Set a different value here to + # store 32-bit dependencies in a separate cache. compiler: gcc -m32 env: BUILD_32BIT=ON - os: osx -- cgit From c095f83116eb8ef87983ca5fea61053755fbc4e5 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Fri, 5 Jan 2018 11:17:21 +0100 Subject: api: change nvim_command_output behavior Implement nvim_command_output with `execute({cmd},"silent")`. Behavior changes: - does not provoke any hit-enter prompt - no longer prepends a newline char - does not capture some noise (like the "[New File]" message, see the change to tabnewentered_spec.lua) Technically ("bug-for-bug") this a breaking change. But the previous behavior of nvim_command_output meant that it probably wasn't used for anything outside of tests. Also remove the undocumented `v:command_output` variable which was a hack introduced only for the purposes of nvim_command_output. closes #7726 --- src/nvim/api/vim.c | 40 ++++++++++++++----- src/nvim/eval.c | 1 - src/nvim/eval.h | 1 - test/functional/api/vim_spec.lua | 53 +++++++++++++++++++++++++- test/functional/autocmd/tabclose_spec.lua | 18 ++++----- test/functional/autocmd/tabnewentered_spec.lua | 8 ++-- test/functional/ex_cmds/sign_spec.lua | 4 +- test/functional/helpers.lua | 14 +++---- test/functional/ui/cmdline_highlight_spec.lua | 2 +- 9 files changed, 105 insertions(+), 36 deletions(-) diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 172f2ce18e..8929bc5b9b 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -43,14 +43,15 @@ #endif /// Executes an ex-command. -/// On VimL error: Returns the VimL error; v:errmsg is not updated. +/// +/// On parse error: forwards the Vim error; does not update v:errmsg. +/// On runtime error: forwards the Vim error; does not update v:errmsg. /// /// @param command Ex-command string -/// @param[out] err Error details (including actual VimL error), if any +/// @param[out] err Error details (Vim error), if any void nvim_command(String command, Error *err) FUNC_API_SINCE(1) { - // Run the command try_start(); do_cmdline_cmd(command.data); update_screen(VALID); @@ -207,18 +208,39 @@ String nvim_replace_termcodes(String str, Boolean from_part, Boolean do_lt, return cstr_as_string(ptr); } -String nvim_command_output(String str, Error *err) +/// Executes an ex-command and returns its (non-error) output. +/// Shell |:!| output is not captured. +/// +/// On parse error: forwards the Vim error; does not update v:errmsg. +/// On runtime error: forwards the Vim error; does not update v:errmsg. +/// +/// @param command Ex-command string +/// @param[out] err Error details (Vim error), if any +String nvim_command_output(String command, Error *err) FUNC_API_SINCE(1) { - do_cmdline_cmd("redir => v:command_output"); - nvim_command(str, err); - do_cmdline_cmd("redir END"); - + Array args = ARRAY_DICT_INIT; + ADD(args, STRING_OBJ(copy_string(command))); + ADD(args, STRING_OBJ(cstr_to_string("silent"))); + String fn = cstr_to_string("execute"); + Object rv = nvim_call_function(fn, args, err); + api_free_string(fn); + api_free_array(args); if (ERROR_SET(err)) { + assert(rv.type == kObjectTypeNil); return (String)STRING_INIT; } - return cstr_to_string((char *)get_vim_var_str(VV_COMMAND_OUTPUT)); + assert(rv.type == kObjectTypeString); + // execute() always(?) prepends a newline; remove it. + if (rv.data.string.size > 1) { + assert(rv.data.string.data[0] == '\n'); + String *s = &rv.data.string; + s->size--; + memmove(s->data, s->data + 1, s->size); + s->data[s->size] = '\0'; + } + return rv.data.string; } /// Evaluates a VimL expression (:help expression). diff --git a/src/nvim/eval.c b/src/nvim/eval.c index f34b6db8d8..875f323a28 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -402,7 +402,6 @@ static struct vimvar { VV(VV_OLDFILES, "oldfiles", VAR_LIST, 0), VV(VV_WINDOWID, "windowid", VAR_NUMBER, VV_RO_SBX), VV(VV_PROGPATH, "progpath", VAR_STRING, VV_RO), - VV(VV_COMMAND_OUTPUT, "command_output", VAR_STRING, 0), VV(VV_COMPLETED_ITEM, "completed_item", VAR_DICT, VV_RO), VV(VV_OPTION_NEW, "option_new", VAR_STRING, VV_RO), VV(VV_OPTION_OLD, "option_old", VAR_STRING, VV_RO), diff --git a/src/nvim/eval.h b/src/nvim/eval.h index 0c0a6881f6..b798eae187 100644 --- a/src/nvim/eval.h +++ b/src/nvim/eval.h @@ -86,7 +86,6 @@ typedef enum { VV_OLDFILES, VV_WINDOWID, VV_PROGPATH, - VV_COMMAND_OUTPUT, VV_COMPLETED_ITEM, VV_OPTION_NEW, VV_OPTION_OLD, diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index ff28e3d133..d213c3c34e 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -37,7 +37,7 @@ describe('api', function() os.remove(fname) end) - it("VimL error: fails (VimL error), does NOT update v:errmsg", function() + it("parse error: fails (specific error), does NOT update v:errmsg", function() -- Most API methods return generic errors (or no error) if a VimL -- expression fails; nvim_command returns the VimL error details. local status, rv = pcall(nvim, "command", "bogus_command") @@ -45,6 +45,57 @@ describe('api', function() eq("E492:", string.match(rv, "E%d*:")) -- VimL error was returned. eq("", nvim("eval", "v:errmsg")) -- v:errmsg was not updated. end) + + it("runtime error: fails (specific error)", function() + local status, rv = pcall(nvim, "command_output", "buffer 23487") + eq(false, status) -- nvim_command() failed. + eq("E86: Buffer 23487 does not exist", string.match(rv, "E%d*:.*")) + eq("", nvim("eval", "v:errmsg")) -- v:errmsg was not updated. + end) + end) + + describe('nvim_command_output', function() + it('does not induce hit-enter prompt', function() + -- Induce a hit-enter prompt use nvim_input (non-blocking). + nvim('command', 'set cmdheight=1') + nvim('input', [[:echo "hi\nhi2"]]) + + -- Verify hit-enter prompt. + eq({mode='r', blocking=true}, nvim("get_mode")) + nvim('input', [[]]) + + -- Verify NO hit-enter prompt. + nvim('command_output', [[echo "hi\nhi2"]]) + eq({mode='n', blocking=false}, nvim("get_mode")) + end) + + it('returns command output', function() + eq('this is\nspinal tap', + nvim('command_output', [[echo "this is\nspinal tap"]])) + end) + + it('does not return shell |:!| output', function() + eq(':!echo "foo"\r\n', nvim('command_output', [[!echo "foo"]])) + end) + + it("parse error: fails (specific error), does NOT update v:errmsg", function() + local status, rv = pcall(nvim, "command_output", "bogus commannnd") + eq(false, status) -- nvim_command_output() failed. + eq("E492: Not an editor command: bogus commannnd", + string.match(rv, "E%d*:.*")) + eq("", nvim("eval", "v:errmsg")) -- v:errmsg was not updated. + -- Verify NO hit-enter prompt. + eq({mode='n', blocking=false}, nvim("get_mode")) + end) + + it("runtime error: fails (specific error)", function() + local status, rv = pcall(nvim, "command_output", "buffer 42") + eq(false, status) -- nvim_command_output() failed. + eq("E86: Buffer 42 does not exist", string.match(rv, "E%d*:.*")) + eq("", nvim("eval", "v:errmsg")) -- v:errmsg was not updated. + -- Verify NO hit-enter prompt. + eq({mode='n', blocking=false}, nvim("get_mode")) + end) end) describe('nvim_eval', function() diff --git a/test/functional/autocmd/tabclose_spec.lua b/test/functional/autocmd/tabclose_spec.lua index fb777e7eea..b7c33dc3d8 100644 --- a/test/functional/autocmd/tabclose_spec.lua +++ b/test/functional/autocmd/tabclose_spec.lua @@ -11,10 +11,10 @@ describe('TabClosed', function() repeat nvim('command', 'tabnew') until nvim('eval', 'tabpagenr()') == 6 -- current tab is now 6 - eq("\ntabclosed:6:6:5", nvim('command_output', 'tabclose')) -- close last 6, current tab is now 5 - eq("\ntabclosed:5:5:4", nvim('command_output', 'close')) -- close last window on tab, closes tab - eq("\ntabclosed:2:2:3", nvim('command_output', '2tabclose')) -- close tab 2, current tab is now 3 - eq("\ntabclosed:1:1:2\ntabclosed:1:1:1", nvim('command_output', 'tabonly')) -- close tabs 1 and 2 + eq("tabclosed:6:6:5", nvim('command_output', 'tabclose')) -- close last 6, current tab is now 5 + eq("tabclosed:5:5:4", nvim('command_output', 'close')) -- close last window on tab, closes tab + eq("tabclosed:2:2:3", nvim('command_output', '2tabclose')) -- close tab 2, current tab is now 3 + eq("tabclosed:1:1:2\ntabclosed:1:1:1", nvim('command_output', 'tabonly')) -- close tabs 1 and 2 end) it('is triggered when closing a window via bdelete from another tab', function() @@ -23,7 +23,7 @@ describe('TabClosed', function() nvim('command', '1tabedit Xtestfile') nvim('command', 'normal! 1gt') eq({1, 3}, nvim('eval', '[tabpagenr(), tabpagenr("$")]')) - eq("\ntabclosed:2:2:1\ntabclosed:2:2:1", nvim('command_output', 'bdelete Xtestfile')) + eq("tabclosed:2:2:1\ntabclosed:2:2:1", nvim('command_output', 'bdelete Xtestfile')) eq({1, 1}, nvim('eval', '[tabpagenr(), tabpagenr("$")]')) end) @@ -35,7 +35,7 @@ describe('TabClosed', function() -- Only one tab is closed, and the alternate file is used for the other. eq({2, 3}, nvim('eval', '[tabpagenr(), tabpagenr("$")]')) - eq("\ntabclosed:2:2:2", nvim('command_output', 'bdelete Xtestfile2')) + eq("tabclosed:2:2:2", nvim('command_output', 'bdelete Xtestfile2')) eq('Xtestfile1', nvim('eval', 'bufname("")')) end) end) @@ -48,9 +48,9 @@ describe('TabClosed', function() nvim('command', 'tabnew') until nvim('eval', 'tabpagenr()') == 7 -- current tab is now 7 -- sanity check, we shouldn't match on tabs with numbers other than 2 - eq("\ntabclosed:7:7:6", nvim('command_output', 'tabclose')) + eq("tabclosed:7:7:6", nvim('command_output', 'tabclose')) -- close tab page 2, current tab is now 5 - eq("\ntabclosed:2:2:5\ntabclosed:match", nvim('command_output', '2tabclose')) + eq("tabclosed:2:2:5\ntabclosed:match", nvim('command_output', '2tabclose')) end) end) @@ -59,7 +59,7 @@ describe('TabClosed', function() nvim('command', 'au! TabClosed * echom "tabclosed:".expand("").":".expand("").":".tabpagenr()') nvim('command', 'tabedit Xtestfile') eq({2, 2}, nvim('eval', '[tabpagenr(), tabpagenr("$")]')) - eq("\ntabclosed:2:2:1", nvim('command_output', 'close')) + eq("tabclosed:2:2:1", nvim('command_output', 'close')) eq({1, 1}, nvim('eval', '[tabpagenr(), tabpagenr("$")]')) end) end) diff --git a/test/functional/autocmd/tabnewentered_spec.lua b/test/functional/autocmd/tabnewentered_spec.lua index bdbe677132..59cac07b34 100644 --- a/test/functional/autocmd/tabnewentered_spec.lua +++ b/test/functional/autocmd/tabnewentered_spec.lua @@ -7,14 +7,14 @@ describe('TabNewEntered', function() it('matches when entering any new tab', function() clear() nvim('command', 'au! TabNewEntered * echom "tabnewentered:".tabpagenr().":".bufnr("")') - eq("\ntabnewentered:2:2", nvim('command_output', 'tabnew')) - eq("\n\"test.x2\" [New File]\ntabnewentered:3:3", nvim('command_output', 'tabnew test.x2')) + eq("tabnewentered:2:2", nvim('command_output', 'tabnew')) + eq("tabnewentered:3:3", nvim('command_output', 'tabnew test.x2')) end) end) describe('with FILE as ', function() it('matches when opening a new tab for FILE', function() nvim('command', 'au! TabNewEntered Xtest-tabnewentered echom "tabnewentered:match"') - eq('\n"Xtest-tabnewentered" [New File]\ntabnewentered:4:4\ntabnewentered:match', + eq('tabnewentered:4:4\ntabnewentered:match', nvim('command_output', 'tabnew Xtest-tabnewentered')) end) end) @@ -24,7 +24,7 @@ describe('TabNewEntered', function() nvim('command', 'au! TabNewEntered * echom "entered"') nvim('command', 'tabnew test.x2') nvim('command', 'split') - eq('\nentered', nvim('command_output', 'execute "normal \\T"')) + eq('entered', nvim('command_output', 'execute "normal \\T"')) end) end) end) diff --git a/test/functional/ex_cmds/sign_spec.lua b/test/functional/ex_cmds/sign_spec.lua index b37e6e8563..df0f5db860 100644 --- a/test/functional/ex_cmds/sign_spec.lua +++ b/test/functional/ex_cmds/sign_spec.lua @@ -16,8 +16,8 @@ describe('sign', function() nvim('command', 'sign place 34 line=3 name=Foo buffer='..buf2) -- now unplace without specifying a buffer nvim('command', 'sign unplace 34') - eq("\n--- Signs ---\n", nvim('command_output', 'sign place buffer='..buf1)) - eq("\n--- Signs ---\n", nvim('command_output', 'sign place buffer='..buf2)) + eq("--- Signs ---\n", nvim('command_output', 'sign place buffer='..buf1)) + eq("--- Signs ---\n", nvim('command_output', 'sign place buffer='..buf2)) end) end) end) diff --git a/test/functional/helpers.lua b/test/functional/helpers.lua index 31a2c3b3ff..dfc4694272 100644 --- a/test/functional/helpers.lua +++ b/test/functional/helpers.lua @@ -644,7 +644,7 @@ local function alter_slashes(obj) end local function hexdump(str) - local len = string.len( str ) + local len = string.len(str) local dump = "" local hex = "" local asc = "" @@ -652,22 +652,20 @@ local function hexdump(str) for i = 1, len do if 1 == i % 8 then dump = dump .. hex .. asc .. "\n" - hex = string.format( "%04x: ", i - 1 ) + hex = string.format("%04x: ", i - 1) asc = "" end - local ord = string.byte( str, i ) - hex = hex .. string.format( "%02x ", ord ) + local ord = string.byte(str, i) + hex = hex .. string.format("%02x ", ord) if ord >= 32 and ord <= 126 then - asc = asc .. string.char( ord ) + asc = asc .. string.char(ord) else asc = asc .. "." end end - return dump .. hex - .. string.rep( " ", 8 - len % 8 ) .. asc - + return dump .. hex .. string.rep(" ", 8 - len % 8) .. asc end local module = { diff --git a/test/functional/ui/cmdline_highlight_spec.lua b/test/functional/ui/cmdline_highlight_spec.lua index 73fe94c056..ffb6a26aef 100644 --- a/test/functional/ui/cmdline_highlight_spec.lua +++ b/test/functional/ui/cmdline_highlight_spec.lua @@ -849,7 +849,7 @@ describe('Ex commands coloring support', function() {EOB:~ }| | ]]) - eq('\nError detected while processing :\nE605: Exception not caught: 42', + eq('Error detected while processing :\nE605: Exception not caught: 42', meths.command_output('messages')) end) it('errors out when failing to get callback', function() -- cgit From 5055d4a755d3c100cddf51bded77abca04beb971 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Tue, 9 Jan 2018 10:36:25 +0100 Subject: api: nvim_command_output: direct impl --- src/nvim/README.md | 57 +++++++++++++++++++++++++++++++--------- src/nvim/api/vim.c | 46 +++++++++++++++++++------------- src/nvim/eval.c | 3 +-- test/functional/api/vim_spec.lua | 27 ++++++++++++++++++- 4 files changed, 99 insertions(+), 34 deletions(-) diff --git a/src/nvim/README.md b/src/nvim/README.md index da87a0208e..1ece92e44d 100644 --- a/src/nvim/README.md +++ b/src/nvim/README.md @@ -1,10 +1,22 @@ -Nvim core source -================ +Nvim core +========= Module-specific details are documented at the top of each module (`terminal.c`, -`screen.c`, ...). +`screen.c`, …). -See `:help development` for more guidelines. +See `:help dev` for guidelines. + +Filename conventions +-------------------- + +The source files use extensions to hint about their purpose. + +- `*.c`, `*.generated.c` - full C files, with all includes, etc. +- `*.c.h` - parametrized C files, contain all necessary includes, but require + defining macros before actually using. Example: `typval_encode.c.h` +- `*.h` - full headers, with all includes. Does *not* apply to `*.generated.h`. +- `*.h.generated.h` - exported functions’ declarations. +- `*.c.generated.h` - static functions’ declarations. Logs ---- @@ -20,17 +32,36 @@ UI events are logged at level 0 (`DEBUG_LOG_LEVEL`). rm -rf build/ make CMAKE_EXTRA_FLAGS="-DMIN_LOG_LEVEL=0" -Filename conventions --------------------- +Build with ASAN +--------------- -The source files use extensions to hint about their purpose. +Building Nvim with Clang sanitizers (Address Sanitizer: ASan, Undefined +Behavior Sanitizer: UBSan, Memory Sanitizer: MSan, Thread Sanitizer: TSan) is +a good way to catch undefined behavior, leaks and other errors as soon as they +happen. It's significantly faster than Valgrind. -- `*.c`, `*.generated.c` - full C files, with all includes, etc. -- `*.c.h` - parametrized C files, contain all necessary includes, but require - defining macros before actually using. Example: `typval_encode.c.h` -- `*.h` - full headers, with all includes. Does *not* apply to `*.generated.h`. -- `*.h.generated.h` - exported functions’ declarations. -- `*.c.generated.h` - static functions’ declarations. +Requires clang 3.4 or later: + + clang --version + +Build Nvim with sanitizer instrumentation: + + CC=clang make CMAKE_EXTRA_FLAGS="-DCLANG_ASAN_UBSAN=ON" + +Create a directory to store logs: + + mkdir -p "$HOME/logs" + +Enable the sanitizer(s) via these environment variables: + + # Change to detect_leaks=1 to detect memory leaks (slower). + export ASAN_OPTIONS="detect_leaks=0:log_path=$HOME/logs/asan" + export ASAN_SYMBOLIZER_PATH=/usr/lib/llvm-5.0/bin/llvm-symbolizer + + export MSAN_SYMBOLIZER_PATH=/usr/lib/llvm-5.0/bin/llvm-symbolizer + export TSAN_OPTIONS="external_symbolizer_path=/usr/lib/llvm-5.0/bin/llvm-symbolizer log_path=${HOME}/logs/tsan" + +Logs will be written to `${HOME}/logs/*san.PID`. TUI debugging ------------- diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 8929bc5b9b..0e7cc428d4 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -219,28 +219,38 @@ String nvim_replace_termcodes(String str, Boolean from_part, Boolean do_lt, String nvim_command_output(String command, Error *err) FUNC_API_SINCE(1) { - Array args = ARRAY_DICT_INIT; - ADD(args, STRING_OBJ(copy_string(command))); - ADD(args, STRING_OBJ(cstr_to_string("silent"))); - String fn = cstr_to_string("execute"); - Object rv = nvim_call_function(fn, args, err); - api_free_string(fn); - api_free_array(args); + const int save_msg_silent = msg_silent; + garray_T *const save_capture_ga = capture_ga; + garray_T capture_local; + ga_init(&capture_local, 1, 80); + + try_start(); + msg_silent++; + capture_ga = &capture_local; + do_cmdline_cmd(command.data); + capture_ga = save_capture_ga; + msg_silent = save_msg_silent; + try_end(err); + if (ERROR_SET(err)) { - assert(rv.type == kObjectTypeNil); - return (String)STRING_INIT; + goto theend; } - assert(rv.type == kObjectTypeString); - // execute() always(?) prepends a newline; remove it. - if (rv.data.string.size > 1) { - assert(rv.data.string.data[0] == '\n'); - String *s = &rv.data.string; - s->size--; - memmove(s->data, s->data + 1, s->size); - s->data[s->size] = '\0'; + if (capture_local.ga_len > 1) { + // redir always(?) prepends a newline; remove it. + char *s = capture_local.ga_data; + assert(s[0] == '\n'); + memmove(s, s + 1, (size_t)capture_local.ga_len); + s[capture_local.ga_len - 1] = '\0'; + return (String) { // Caller will free the memory. + .data = s, + .size = (size_t)(capture_local.ga_len - 1), + }; } - return rv.data.string; + +theend: + ga_clear(&capture_local); + return (String)STRING_INIT; } /// Evaluates a VimL expression (:help expression). diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 875f323a28..2e8bf18f2d 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -8116,8 +8116,7 @@ static void f_execute(typval_T *argvars, typval_T *rettv, FunPtr fptr) ga_append(capture_ga, NUL); rettv->v_type = VAR_STRING; - rettv->vval.v_string = vim_strsave(capture_ga->ga_data); - ga_clear(capture_ga); + rettv->vval.v_string = capture_ga->ga_data; capture_ga = save_capture_ga; } diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index d213c3c34e..39db831fe3 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -69,11 +69,36 @@ describe('api', function() eq({mode='n', blocking=false}, nvim("get_mode")) end) - it('returns command output', function() + it('captures command output', function() eq('this is\nspinal tap', nvim('command_output', [[echo "this is\nspinal tap"]])) end) + it('captures empty command output', function() + eq('', nvim('command_output', 'echo')) + end) + + it('captures single-char command output', function() + eq('x', nvim('command_output', 'echo "x"')) + end) + + it('captures multiple commands', function() + eq('foo\n 1 %a "[No Name]" line 1', + nvim('command_output', 'echo "foo" | ls')) + end) + + it('captures nested execute()', function() + eq('\nnested1\nnested2\n 1 %a "[No Name]" line 1', + nvim('command_output', + [[echo execute('echo "nested1\nnested2"') | ls]])) + end) + + it('captures nested nvim_command_output()', function() + eq('nested1\nnested2\n 1 %a "[No Name]" line 1', + nvim('command_output', + [[echo nvim_command_output('echo "nested1\nnested2"') | ls]])) + end) + it('does not return shell |:!| output', function() eq(':!echo "foo"\r\n', nvim('command_output', [[!echo "foo"]])) end) -- cgit From 18d244eded434e6bc47b351ef00088378883bf1d Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Thu, 11 Jan 2018 01:32:41 +0100 Subject: coverity/169163: decode_string: Null pointer deref *** CID 169163: Null pointer dereferences (FORWARD_NULL) /src/nvim/eval/decode.c: 290 in decode_string() 284 if (elw_ret == -1) { 285 tv_clear(&tv); 286 return (typval_T) { .v_type = VAR_UNKNOWN, .v_lock = VAR_UNLOCKED }; 287 } 288 return tv; 289 } else { >>> CID 169163: Null pointer dereferences (FORWARD_NULL) >>> Passing null pointer "s" to "xmemdupz", which dereferences it. (The dereference is assumed on the basis of the 'nonnull' parameter attribute.) 290 return (typval_T) { 291 .v_type = VAR_STRING, 292 .v_lock = VAR_UNLOCKED, 293 .vval = { .v_string = (char_u *)( 294 s_allocated ? (char *)s : xmemdupz(s, len)) }, 295 }; --- src/nvim/eval/decode.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nvim/eval/decode.c b/src/nvim/eval/decode.c index af4e055d23..cd967ed5c5 100644 --- a/src/nvim/eval/decode.c +++ b/src/nvim/eval/decode.c @@ -285,7 +285,7 @@ typval_T decode_string(const char *const s, const size_t len, .v_type = VAR_STRING, .v_lock = VAR_UNLOCKED, .vval = { .v_string = (char_u *)( - s_allocated ? (char *)s : xmemdupz(s, len)) }, + (s == NULL || s_allocated) ? (char *)s : xmemdupz(s, len)) }, }; } } -- cgit From 624ac8aede93cc521d7ea27ae406ad4780642fcb Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Thu, 11 Jan 2018 01:36:37 +0100 Subject: coverity/161216: get_user_input: RETURN_LOCAL *** CID 161216: Memory - illegal accesses (RETURN_LOCAL) /src/nvim/eval.c: 11143 in get_user_input() 11137 rettv->vval.v_string = 11138 (char_u *)getcmdline_prompt(inputsecret_flag ? NUL : '@', p, echo_attr, 11139 xp_type, xp_arg, input_callback); 11140 ex_normal_busy = save_ex_normal_busy; 11141 callback_free(&input_callback); 11142 >>> CID 161216: Memory - illegal accesses (RETURN_LOCAL) >>> Using "cancelreturn", which points to an out-of-scope variable "def". 11143 if (rettv->vval.v_string == NULL && cancelreturn != NULL) { 11144 rettv->vval.v_string = (char_u *)xstrdup(cancelreturn); 11145 } 11146 11147 xfree(xp_arg); 11148 --- src/nvim/eval.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 2e8bf18f2d..a642a3c0dd 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -11110,6 +11110,7 @@ void get_user_input(const typval_T *const argvars, char defstr_buf[NUMBUFLEN]; char cancelreturn_buf[NUMBUFLEN]; char xp_name_buf[NUMBUFLEN]; + char def[1] = { 0 }; if (argvars[0].v_type == VAR_DICT) { if (argvars[1].v_type != VAR_UNKNOWN) { emsgf(_("E5050: {opts} must be the only argument")); @@ -11124,7 +11125,6 @@ void get_user_input(const typval_T *const argvars, if (defstr == NULL) { return; } - char def[1] = { 0 }; cancelreturn = tv_dict_get_string_buf_chk(dict, S_LEN("cancelreturn"), cancelreturn_buf, def); if (cancelreturn == NULL) { // error -- cgit From 9ddeb6e187e6ef6045bf037e4225dc46c8efb693 Mon Sep 17 00:00:00 2001 From: KunMing Xie Date: Sun, 14 Jan 2018 02:26:21 +0800 Subject: vim-patch:8.0.0364 (#7837) vim-patch:8.0.0364: ]s does not move cursor with two spell errors in one line Problem: ]s does not move cursor with two spell errors in one line. (Manuel Ortega) Solution: Don't stop search immediately when wrapped, search the line first. (Ken Takata) Add a test. https://github.com/vim/vim/commit/d3f78dc9ebd729475a7f24a50a91112e300d5ac9 * disable spell test for now --- src/nvim/spell.c | 25 ++++++++++++++----------- src/nvim/testdir/Makefile | 1 + src/nvim/testdir/test_spell.vim | 20 ++++++++++++++++++++ 3 files changed, 35 insertions(+), 11 deletions(-) create mode 100644 src/nvim/testdir/test_spell.vim diff --git a/src/nvim/spell.c b/src/nvim/spell.c index d2b2575f6a..b3e80bd768 100644 --- a/src/nvim/spell.c +++ b/src/nvim/spell.c @@ -1484,21 +1484,23 @@ spell_move_to ( return found_len; } - if (curline) + if (curline) { break; // only check cursor line + } + + // If we are back at the starting line and searched it again there + // is no match, give up. + if (lnum == wp->w_cursor.lnum && wrapped) { + break; + } // Advance to next line. if (dir == BACKWARD) { - // If we are back at the starting line and searched it again there - // is no match, give up. - if (lnum == wp->w_cursor.lnum && wrapped) - break; - - if (lnum > 1) - --lnum; - else if (!p_ws) + if (lnum > 1) { + lnum--; + } else if (!p_ws) { break; // at first line and 'nowrapscan' - else { + } else { // Wrap around to the end of the buffer. May search the // starting line again and accept the last match. lnum = wp->w_buffer->b_ml.ml_line_count; @@ -1523,8 +1525,9 @@ spell_move_to ( // If we are back at the starting line and there is no match then // give up. - if (lnum == wp->w_cursor.lnum && (!found_one || wrapped)) + if (lnum == wp->w_cursor.lnum && !found_one) { break; + } // Skip the characters at the start of the next line that were // included in a match crossing line boundaries. diff --git a/src/nvim/testdir/Makefile b/src/nvim/testdir/Makefile index 5af8dd20cd..a23dacba15 100644 --- a/src/nvim/testdir/Makefile +++ b/src/nvim/testdir/Makefile @@ -81,6 +81,7 @@ NEW_TESTS ?= \ test_search.res \ test_signs.res \ test_smartindent.res \ + test_spell.res \ test_stat.res \ test_startup.res \ test_startup_utf8.res \ diff --git a/src/nvim/testdir/test_spell.vim b/src/nvim/testdir/test_spell.vim new file mode 100644 index 0000000000..334568aadb --- /dev/null +++ b/src/nvim/testdir/test_spell.vim @@ -0,0 +1,20 @@ +" Test spell checking +" TODO: move test58 tests here + +if v:true + finish +endif + +func Test_wrap_search() + new + call setline(1, ['The', '', 'A plong line with two zpelling mistakes', '', 'End']) + set spell wrapscan + normal ]s + call assert_equal('plong', expand('')) + normal ]s + call assert_equal('zpelling', expand('')) + normal ]s + call assert_equal('plong', expand('')) + bwipe! + set nospell +endfunc -- cgit From 8eb0888a5de2481e15fcae63fa477f0ac634c9f2 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sat, 13 Jan 2018 19:26:30 +0100 Subject: vim-patch:8.0.0582: illegal memory access with z= command Problem: Illegal memory access with z= command. (Dominique Pelle) Solution: Avoid case folded text to be longer than the original text. Use MB_PTR2LEN() instead of MB_BYTE2LEN(). https://github.com/vim/vim/commit/5b276aa80e112ae1993bd43e28f599f257827c54 --- src/nvim/spell.c | 40 +++++++++++++++++++++------------------- src/nvim/testdir/test_spell.vim | 9 +++++++++ 2 files changed, 30 insertions(+), 19 deletions(-) diff --git a/src/nvim/spell.c b/src/nvim/spell.c index b3e80bd768..d74360bcc0 100644 --- a/src/nvim/spell.c +++ b/src/nvim/spell.c @@ -2568,7 +2568,7 @@ static bool spell_iswordp(char_u *p, win_T *wp) int c; if (has_mbyte) { - l = MB_BYTE2LEN(*p); + l = MB_PTR2LEN(p); s = p; if (l == 1) { // be quick for ASCII @@ -3141,6 +3141,12 @@ spell_find_suggest ( STRLCPY(su->su_badword, su->su_badptr, su->su_badlen + 1); (void)spell_casefold(su->su_badptr, su->su_badlen, su->su_fbadword, MAXWLEN); + + // TODO(vim): make this work if the case-folded text is longer than the + // original text. Currently an illegal byte causes wrong pointer + // computations. + su->su_fbadword[su->su_badlen] = NUL; + // get caps flags for bad word su->su_badflags = badword_captype(su->su_badptr, su->su_badptr + su->su_badlen); @@ -4110,10 +4116,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so && goodword_ends) { int l; - if (has_mbyte) - l = MB_BYTE2LEN(fword[sp->ts_fidx]); - else - l = 1; + l = MB_PTR2LEN(fword + sp->ts_fidx); if (fword_ends) { // Copy the skipped character to preword. memmove(preword + sp->ts_prewordlen, @@ -4259,8 +4262,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so // Correct ts_fidx for the byte length of the // character (we didn't check that before). sp->ts_fidx = sp->ts_fcharstart - + MB_BYTE2LEN( - fword[sp->ts_fcharstart]); + + MB_PTR2LEN(fword + sp->ts_fcharstart); // For changing a composing character adjust // the score from SCORE_SUBST to @@ -4366,7 +4368,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so // results. if (has_mbyte) { c = mb_ptr2char(fword + sp->ts_fidx); - stack[depth].ts_fidx += MB_BYTE2LEN(fword[sp->ts_fidx]); + stack[depth].ts_fidx += MB_PTR2LEN(fword + sp->ts_fidx); if (enc_utf8 && utf_iscomposing(c)) stack[depth].ts_score -= SCORE_DEL - SCORE_DELCOMP; else if (c == mb_ptr2char(fword + stack[depth].ts_fidx)) @@ -4552,9 +4554,9 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so // Undo the STATE_SWAP swap: "21" -> "12". p = fword + sp->ts_fidx; if (has_mbyte) { - n = MB_BYTE2LEN(*p); + n = MB_PTR2LEN(p); c = mb_ptr2char(p + n); - memmove(p + MB_BYTE2LEN(p[n]), p, n); + memmove(p + MB_PTR2LEN(p + n), p, n); mb_char2bytes(c, p); } else { c = *p; @@ -4627,11 +4629,11 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so // Undo STATE_SWAP3: "321" -> "123" p = fword + sp->ts_fidx; if (has_mbyte) { - n = MB_BYTE2LEN(*p); + n = MB_PTR2LEN(p); c2 = mb_ptr2char(p + n); - fl = MB_BYTE2LEN(p[n]); + fl = MB_PTR2LEN(p + n); c = mb_ptr2char(p + n + fl); - tl = MB_BYTE2LEN(p[n + fl]); + tl = MB_PTR2LEN(p + n + fl); memmove(p + fl + tl, p, n); mb_char2bytes(c, p); mb_char2bytes(c2, p + tl); @@ -4690,10 +4692,10 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so // Undo ROT3L: "231" -> "123" p = fword + sp->ts_fidx; if (has_mbyte) { - n = MB_BYTE2LEN(*p); - n += MB_BYTE2LEN(p[n]); + n = MB_PTR2LEN(p); + n += MB_PTR2LEN(p + n); c = mb_ptr2char(p + n); - tl = MB_BYTE2LEN(p[n]); + tl = MB_PTR2LEN(p + n); memmove(p + tl, p, n); mb_char2bytes(c, p); } else { @@ -4743,9 +4745,9 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so p = fword + sp->ts_fidx; if (has_mbyte) { c = mb_ptr2char(p); - tl = MB_BYTE2LEN(*p); - n = MB_BYTE2LEN(p[tl]); - n += MB_BYTE2LEN(p[tl + n]); + tl = MB_PTR2LEN(p); + n = MB_PTR2LEN(p + tl); + n += MB_PTR2LEN(p + tl + n); memmove(p, p + tl, n); mb_char2bytes(c, p + n); } else { diff --git a/src/nvim/testdir/test_spell.vim b/src/nvim/testdir/test_spell.vim index 334568aadb..66be5c2441 100644 --- a/src/nvim/testdir/test_spell.vim +++ b/src/nvim/testdir/test_spell.vim @@ -18,3 +18,12 @@ func Test_wrap_search() bwipe! set nospell endfunc + +func Test_z_equal_on_invalid_utf8_word() + split + set spell + call setline(1, "\xff") + norm z= + set nospell + bwipe! +endfunc -- cgit From 9ea1752d60589e8fc5e7184144bc6d1c1b9f16a3 Mon Sep 17 00:00:00 2001 From: ZyX Date: Wed, 3 Jan 2018 00:00:16 +0300 Subject: *: Provide list length when allocating lists --- src/nvim/api/private/helpers.c | 2 +- src/nvim/channel.c | 37 +++---- src/nvim/eval.c | 225 ++++++++++++++++++++--------------------- src/nvim/eval/decode.c | 32 +++--- src/nvim/eval/typval.c | 18 +++- src/nvim/eval/typval.h | 19 ++++ src/nvim/ex_cmds2.c | 6 +- src/nvim/lua/converter.c | 7 +- src/nvim/main.c | 2 +- src/nvim/menu.c | 2 +- src/nvim/ops.c | 21 ++-- src/nvim/quickfix.c | 2 +- src/nvim/regexp.c | 4 +- src/nvim/shada.c | 2 +- src/nvim/tag.c | 2 +- src/nvim/undo.c | 29 +++--- src/nvim/window.c | 4 +- test/unit/eval/helpers.lua | 5 +- test/unit/eval/typval_spec.lua | 2 +- 19 files changed, 221 insertions(+), 200 deletions(-) diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 26ad7ac1a6..9baa996560 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -783,7 +783,7 @@ bool object_to_vim(Object obj, typval_T *tv, Error *err) break; case kObjectTypeArray: { - list_T *const list = tv_list_alloc(); + list_T *const list = tv_list_alloc((ptrdiff_t)obj.data.array.size); for (uint32_t i = 0; i < obj.data.array.size; i++) { Object item = obj.data.array.items[i]; diff --git a/src/nvim/channel.c b/src/nvim/channel.c index efef95de01..9d0a7d3c21 100644 --- a/src/nvim/channel.c +++ b/src/nvim/channel.c @@ -4,6 +4,7 @@ #include "nvim/api/ui.h" #include "nvim/channel.h" #include "nvim/eval.h" +#include "nvim/eval/encode.h" #include "nvim/event/socket.h" #include "nvim/msgpack_rpc/channel.h" #include "nvim/msgpack_rpc/server.h" @@ -522,32 +523,18 @@ err: return 0; } -/// NB: mutates buf in place! -static list_T *buffer_to_tv_list(char *buf, size_t count) +/// Convert binary byte array to a readfile()-style list +/// +/// @param[in] buf Array to convert. +/// @param[in] len Array length. +/// +/// @return [allocated] Converted list. +static inline list_T *buffer_to_tv_list(const char *const buf, const size_t len) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_ALWAYS_INLINE { - list_T *ret = tv_list_alloc(); - char *ptr = buf; - size_t remaining = count; - size_t off = 0; - - while (off < remaining) { - // append the line - if (ptr[off] == NL) { - tv_list_append_string(ret, ptr, (ssize_t)off); - size_t skip = off + 1; - ptr += skip; - remaining -= skip; - off = 0; - continue; - } - if (ptr[off] == NUL) { - // Translate NUL to NL - ptr[off] = NL; - } - off++; - } - tv_list_append_string(ret, ptr, (ssize_t)off); - return ret; + list_T *const l = tv_list_alloc(kListLenMayKnow); + encode_list_write(l, buf, len); + return l; } // vimscript job callbacks must be executed on Nvim main loop diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 2e8bf18f2d..ec32f1821d 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -568,7 +568,7 @@ void eval_init(void) dict_T *const msgpack_types_dict = tv_dict_alloc(); for (size_t i = 0; i < ARRAY_SIZE(msgpack_type_names); i++) { - list_T *const type_list = tv_list_alloc(); + list_T *const type_list = tv_list_alloc(0); tv_list_set_lock(type_list, VAR_FIXED); tv_list_ref(type_list); dictitem_T *const di = tv_dict_item_alloc(msgpack_type_names[i]); @@ -591,7 +591,7 @@ void eval_init(void) dict_T *v_event = tv_dict_alloc(); v_event->dv_lock = VAR_FIXED; set_vim_var_dict(VV_EVENT, v_event); - set_vim_var_list(VV_ERRORS, tv_list_alloc()); + set_vim_var_list(VV_ERRORS, tv_list_alloc(kListLenUnknown)); set_vim_var_nr(VV_STDERR, CHAN_STDERR); set_vim_var_nr(VV_SEARCHFORWARD, 1L); set_vim_var_nr(VV_HLSEARCH, 1L); @@ -1546,6 +1546,7 @@ ex_let_vars ( assert(l != NULL); listitem_T *item = tv_list_first(l); + size_t rest_len = tv_list_len(l); while (*arg != ']') { arg = skipwhite(arg + 1); arg = ex_let_one(arg, TV_LIST_ITEM_TV(item), true, (const char_u *)",;]", @@ -1553,13 +1554,14 @@ ex_let_vars ( if (arg == NULL) { return FAIL; } + rest_len--; item = TV_LIST_ITEM_NEXT(l, item); arg = skipwhite(arg); if (*arg == ';') { /* Put the rest of the list (may be empty) in the var after ';'. * Create a new list for this. */ - list_T *const rest_list = tv_list_alloc(); + list_T *const rest_list = tv_list_alloc(rest_len); while (item != NULL) { tv_list_append_tv(rest_list, TV_LIST_ITEM_TV(item)); item = TV_LIST_ITEM_NEXT(l, item); @@ -4512,7 +4514,7 @@ eval_index ( if (!empty2 && (n2 < 0 || n2 + 1 < n1)) { n2 = -1; } - l = tv_list_alloc(); + l = tv_list_alloc(n2 - n1 + 1); item = tv_list_find(rettv->vval.v_list, n1); while (n1++ <= n2) { tv_list_append_tv(l, TV_LIST_ITEM_TV(item)); @@ -4870,7 +4872,7 @@ static int get_list_tv(char_u **arg, typval_T *rettv, int evaluate) list_T *l = NULL; if (evaluate) { - l = tv_list_alloc(); + l = tv_list_alloc(kListLenShouldKnow); } *arg = skipwhite(*arg + 1); @@ -6666,7 +6668,7 @@ static void f_argv(typval_T *argvars, typval_T *rettv, FunPtr fptr) } rettv->v_type = VAR_STRING; } else { - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, ARGCOUNT); for (idx = 0; idx < ARGCOUNT; idx++) { tv_list_append_string(rettv->vval.v_list, (const char *)alist_name(&ARGLIST[idx]), -1); @@ -6776,7 +6778,7 @@ static void assert_error(garray_T *gap) if (vp->vv_type != VAR_LIST || vimvars[VV_ERRORS].vv_list == NULL) { // Make sure v:errors is a list. - set_vim_var_list(VV_ERRORS, tv_list_alloc()); + set_vim_var_list(VV_ERRORS, tv_list_alloc(1)); } tv_list_append_string(vimvars[VV_ERRORS].vv_list, (const char *)gap->ga_data, (ptrdiff_t)gap->ga_len); @@ -8225,7 +8227,7 @@ static void f_expand(typval_T *argvars, typval_T *rettv, FunPtr fptr) result = eval_vars((char_u *)s, (char_u *)s, &len, NULL, &errormsg, NULL); emsg_off--; if (rettv->v_type == VAR_LIST) { - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, (result != NULL)); if (result != NULL) { tv_list_append_string(rettv->vval.v_list, (const char *)result, -1); } @@ -8248,8 +8250,8 @@ static void f_expand(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_string = ExpandOne(&xpc, (char_u *)s, NULL, options, WILD_ALL); } else { - tv_list_alloc_ret(rettv); ExpandOne(&xpc, (char_u *)s, NULL, options, WILD_ALL_KEEP); + tv_list_alloc_ret(rettv, xpc.xp_numfiles); for (int i = 0; i < xpc.xp_numfiles; i++) { tv_list_append_string(rettv->vval.v_list, (const char *)xpc.xp_files[i], -1); @@ -8266,7 +8268,7 @@ static void f_expand(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// "menu_get(path [, modes])" function static void f_menu_get(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, kListLenMayKnow); int modes = MENU_ALL_MODES; if (argvars[1].v_type == VAR_STRING) { const char_u *const strmodes = (char_u *)tv_get_string(&argvars[1]); @@ -8427,7 +8429,7 @@ static void findfilendir(typval_T *argvars, typval_T *rettv, int find_what) } if (count < 0) { - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, kListLenUnknown); } if (*fname != NUL && !error) { @@ -9108,7 +9110,7 @@ static void f_get(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } else if (strcmp(what, "args") == 0) { rettv->v_type = VAR_LIST; - if (tv_list_alloc_ret(rettv) != NULL) { + if (tv_list_alloc_ret(rettv, pt->pt_argc) != NULL) { for (int i = 0; i < pt->pt_argc; i++) { tv_list_append_tv(rettv->vval.v_list, &pt->pt_argv[i]); } @@ -9132,8 +9134,10 @@ static void f_get(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// Returns information about signs placed in a buffer as list of dicts. -static void get_buffer_signs(buf_T *buf, list_T *l) +static list_T *get_buffer_signs(buf_T *buf) + FUNC_ATTR_NONNULL_RET FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { + list_T *const l = tv_list_alloc(kListLenMayKnow); for (signlist_T *sign = buf->b_signlist; sign; sign = sign->next) { dict_T *const d = tv_dict_alloc(); @@ -9144,6 +9148,7 @@ static void get_buffer_signs(buf_T *buf, list_T *l) tv_list_append_dict(l, d); } + return l; } /// Returns buffer options, variables and other attributes in a dictionary. @@ -9167,7 +9172,7 @@ static dict_T *get_buffer_info(buf_T *buf) tv_dict_add_dict(dict, S_LEN("variables"), buf->b_vars); // List of windows displaying this buffer - list_T *const windows = tv_list_alloc(); + list_T *const windows = tv_list_alloc(kListLenMayKnow); FOR_ALL_TAB_WINDOWS(tp, wp) { if (wp->w_buffer == buf) { tv_list_append_number(windows, (varnumber_T)wp->handle); @@ -9177,9 +9182,7 @@ static dict_T *get_buffer_info(buf_T *buf) if (buf->b_signlist != NULL) { // List of signs placed in this buffer - list_T *const signs = tv_list_alloc(); - get_buffer_signs(buf, signs); - tv_dict_add_list(dict, S_LEN("signs"), signs); + tv_dict_add_list(dict, S_LEN("signs"), get_buffer_signs(buf)); } return dict; @@ -9193,7 +9196,7 @@ static void f_getbufinfo(typval_T *argvars, typval_T *rettv, FunPtr fptr) bool sel_buflisted = false; bool sel_bufloaded = false; - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, kListLenMayKnow); // List of all the buffers or selected buffers if (argvars[0].v_type == VAR_DICT) { @@ -9252,35 +9255,31 @@ static void f_getbufinfo(typval_T *argvars, typval_T *rettv, FunPtr fptr) */ static void get_buffer_lines(buf_T *buf, linenr_T start, linenr_T end, int retlist, typval_T *rettv) { - char_u *p; - - rettv->v_type = VAR_STRING; + rettv->v_type = (retlist ? VAR_LIST : VAR_STRING); rettv->vval.v_string = NULL; - if (retlist) { - tv_list_alloc_ret(rettv); - } - if (buf == NULL || buf->b_ml.ml_mfp == NULL || start < 0) + if (buf == NULL || buf->b_ml.ml_mfp == NULL || start < 0 || end < start) { + tv_list_alloc_ret(rettv, 0); return; + } - if (!retlist) { - if (start >= 1 && start <= buf->b_ml.ml_line_count) - p = ml_get_buf(buf, start, FALSE); - else - p = (char_u *)""; - rettv->vval.v_string = vim_strsave(p); - } else { - if (end < start) - return; - - if (start < 1) + if (retlist) { + if (start < 1) { start = 1; - if (end > buf->b_ml.ml_line_count) + } + if (end > buf->b_ml.ml_line_count) { end = buf->b_ml.ml_line_count; + } + tv_list_alloc_ret(rettv, end - start + 1); while (start <= end) { tv_list_append_string(rettv->vval.v_list, (const char *)ml_get_buf(buf, start++, false), -1); } + } else { + rettv->v_type = VAR_STRING; + rettv->vval.v_string = ((start >= 1 && start <= buf->b_ml.ml_line_count) + ? vim_strsave(ml_get_buf(buf, start, false)) + : NULL); } } @@ -9605,8 +9604,8 @@ static void f_getcompletion(typval_T *argvars, typval_T *rettv, FunPtr fptr) theend: pat = addstar(xpc.xp_pattern, xpc.xp_pattern_len, xpc.xp_context); - tv_list_alloc_ret(rettv); ExpandOne(&xpc, pat, NULL, options, WILD_ALL_KEEP); + tv_list_alloc_ret(rettv, xpc.xp_numfiles); for (int i = 0; i < xpc.xp_numfiles; i++) { tv_list_append_string(rettv->vval.v_list, (const char *)xpc.xp_files[i], @@ -9900,7 +9899,7 @@ static void f_getline(typval_T *argvars, typval_T *rettv, FunPtr fptr) const linenr_T lnum = tv_get_lnum(argvars); if (argvars[1].v_type == VAR_UNKNOWN) { - end = 0; + end = lnum; retlist = false; } else { end = tv_get_lnum(&argvars[1]); @@ -9914,7 +9913,7 @@ static void get_qf_loc_list(int is_qf, win_T *wp, typval_T *what_arg, typval_T *rettv) { if (what_arg->v_type == VAR_UNKNOWN) { - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, kListLenMayKnow); if (is_qf || wp != NULL) { (void)get_errorlist(wp, -1, rettv->vval.v_list); } @@ -9949,7 +9948,7 @@ static void f_getmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) matchitem_T *cur = curwin->w_match_head; int i; - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, kListLenMayKnow); while (cur != NULL) { dict_T *dict = tv_dict_alloc(); if (cur->match.regprog == NULL) { @@ -9962,7 +9961,7 @@ static void f_getmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (llpos->lnum == 0) { break; } - list_T *l = tv_list_alloc(); + list_T *const l = tv_list_alloc(1 + (llpos->col > 0 ? 2 : 0)); tv_list_append_number(l, (varnumber_T)llpos->lnum); if (llpos->col > 0) { tv_list_append_number(l, (varnumber_T)llpos->col); @@ -10011,7 +10010,7 @@ static void getpos_both(typval_T *argvars, typval_T *rettv, bool getcurpos) fp = var2fpos(&argvars[0], true, &fnum); } - list_T *l = tv_list_alloc_ret(rettv); + list_T *const l = tv_list_alloc_ret(rettv, 4 + (!!getcurpos)); tv_list_append_number(l, (fnum != -1) ? (varnumber_T)fnum : (varnumber_T)0); tv_list_append_number(l, ((fp != NULL) ? (varnumber_T)fp->lnum @@ -10084,10 +10083,10 @@ static void f_getreg(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (return_list) { rettv->v_type = VAR_LIST; - rettv->vval.v_list = + rettv->vval.v_list = get_reg_contents(regname, (arg2 ? kGRegExprSrc : 0) | kGRegList); if (rettv->vval.v_list == NULL) { - rettv->vval.v_list = tv_list_alloc(); + rettv->vval.v_list = tv_list_alloc(0); } tv_list_ref(rettv->vval.v_list); } else { @@ -10137,7 +10136,7 @@ static dict_T *get_tabpage_info(tabpage_T *tp, int tp_idx) tv_dict_add_nr(dict, S_LEN("tabnr"), tp_idx); - list_T *const l = tv_list_alloc(); + list_T *const l = tv_list_alloc(kListLenMayKnow); FOR_ALL_WINDOWS_IN_TAB(wp, tp) { tv_list_append_number(l, (varnumber_T)wp->handle); } @@ -10154,7 +10153,9 @@ static void f_gettabinfo(typval_T *argvars, typval_T *rettv, FunPtr fptr) { tabpage_T *tparg = NULL; - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, (argvars[0].v_type == VAR_UNKNOWN + ? 1 + : kListLenMayKnow)); if (argvars[0].v_type != VAR_UNKNOWN) { // Information about one tab page @@ -10253,7 +10254,7 @@ static void f_getwininfo(typval_T *argvars, typval_T *rettv, FunPtr fptr) { win_T *wparg = NULL; - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, kListLenMayKnow); if (argvars[0].v_type != VAR_UNKNOWN) { wparg = win_id2wp(argvars); @@ -10478,9 +10479,9 @@ static void f_glob(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_string = ExpandOne( &xpc, (char_u *)tv_get_string(&argvars[0]), NULL, options, WILD_ALL); } else { - tv_list_alloc_ret(rettv); ExpandOne(&xpc, (char_u *)tv_get_string(&argvars[0]), NULL, options, WILD_ALL_KEEP); + tv_list_alloc_ret(rettv, xpc.xp_numfiles); for (int i = 0; i < xpc.xp_numfiles; i++) { tv_list_append_string(rettv->vval.v_list, (const char *)xpc.xp_files[i], -1); @@ -10529,7 +10530,7 @@ static void f_globpath(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (rettv->v_type == VAR_STRING) { rettv->vval.v_string = ga_concat_strings_sep(&ga, "\n"); } else { - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, ga.ga_len); for (int i = 0; i < ga.ga_len; i++) { tv_list_append_string(rettv->vval.v_list, ((const char **)(ga.ga_data))[i], -1); @@ -11429,7 +11430,7 @@ static void dict_list(typval_T *const tv, typval_T *const rettv, return; } - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, tv_dict_len(tv->vval.v_dict)); TV_DICT_ITER(tv->vval.v_dict, di, { typval_T tv = { .v_lock = VAR_UNLOCKED }; @@ -11446,7 +11447,7 @@ static void dict_list(typval_T *const tv, typval_T *const rettv, } case kDictListItems: { // items() - list_T *const sub_l = tv_list_alloc(); + list_T *const sub_l = tv_list_alloc(2); tv.v_type = VAR_LIST; tv.vval.v_list = sub_l; tv_list_ref(sub_l); @@ -11776,7 +11777,7 @@ static void f_jobwait(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } - list_T *rv = tv_list_alloc(); + list_T *const rv = tv_list_alloc(tv_list_len(args)); // restore the parent queue for any jobs still alive for (i = 0; i < tv_list_len(args); i++) { @@ -12204,12 +12205,12 @@ static void find_some_match(typval_T *const argvars, typval_T *const rettv, switch (type) { // matchlist(): return empty list when there are no matches. case kSomeMatchList: { - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, kListLenMayKnow); break; } // matchstrpos(): return ["", -1, -1, -1] case kSomeMatchStrPos: { - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, 4); tv_list_append_string(rettv->vval.v_list, "", 0); tv_list_append_number(rettv->vval.v_list, -1); tv_list_append_number(rettv->vval.v_list, -1); @@ -12516,10 +12517,12 @@ static void f_matchaddpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) */ static void f_matcharg(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - tv_list_alloc_ret(rettv); - const int id = tv_get_number(&argvars[0]); + tv_list_alloc_ret(rettv, (id >= 1 && id <= 3 + ? 2 + : 0)); + if (id >= 1 && id <= 3) { matchitem_T *const m = (matchitem_T *)get_match(curwin, id); @@ -12705,8 +12708,8 @@ static void f_msgpackdump(typval_T *argvars, typval_T *rettv, FunPtr fptr) EMSG2(_(e_listarg), "msgpackdump()"); return; } - list_T *ret_list = tv_list_alloc_ret(rettv); - list_T *list = argvars[0].vval.v_list; + list_T *const ret_list = tv_list_alloc_ret(rettv, kListLenMayKnow); + list_T *const list = argvars[0].vval.v_list; msgpack_packer *lpacker = msgpack_packer_new(ret_list, &encode_list_write); const char *const msg = _("msgpackdump() argument, index %i"); // Assume that translation will not take more then 4 times more space @@ -12730,8 +12733,8 @@ static void f_msgpackparse(typval_T *argvars, typval_T *rettv, FunPtr fptr) EMSG2(_(e_listarg), "msgpackparse()"); return; } - list_T *ret_list = tv_list_alloc_ret(rettv); - const list_T *list = argvars[0].vval.v_list; + list_T *const ret_list = tv_list_alloc_ret(rettv, kListLenMayKnow); + const list_T *const list = argvars[0].vval.v_list; if (tv_list_len(list) == 0) { return; } @@ -12986,7 +12989,7 @@ static void f_range(typval_T *argvars, typval_T *rettv, FunPtr fptr) } else if (stride > 0 ? end + 1 < start : end - 1 > start) { emsgf(_("E727: Start past end")); } else { - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, (end - start) / stride); for (i = start; stride > 0 ? i <= end : i >= end; i += stride) { tv_list_append_number(rettv->vval.v_list, (varnumber_T)i); } @@ -13017,8 +13020,7 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } - tv_list_alloc_ret(rettv); - list_T *const l = rettv->vval.v_list; + list_T *const l = tv_list_alloc_ret(rettv, kListLenUnknown); // Always open the file in binary mode, library functions have a mind of // their own about CR-LF conversion. @@ -13231,7 +13233,7 @@ static void f_reltime(typval_T *argvars, typval_T *rettv, FunPtr fptr) STATIC_ASSERT(sizeof(u.prof) == sizeof(u) && sizeof(u.split) == sizeof(u), "type punning will produce incorrect results on this platform"); - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, 2); tv_list_append_number(rettv->vval.v_list, u.split.high); tv_list_append_number(rettv->vval.v_list, u.split.low); } @@ -13324,7 +13326,8 @@ static void f_remove(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (li == NULL) { // Didn't find "item2" after "item". emsgf(_(e_invrange)); } else { - tv_list_move_items(l, item, item2, tv_list_alloc_ret(rettv), cnt); + tv_list_move_items(l, item, item2, tv_list_alloc_ret(rettv, cnt), + cnt); } } } @@ -13354,7 +13357,7 @@ static void f_repeat(typval_T *argvars, typval_T *rettv, FunPtr fptr) { varnumber_T n = tv_get_number(&argvars[1]); if (argvars[0].v_type == VAR_LIST) { - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, (n > 0) * n * tv_list_len(argvars[0].vval.v_list)); while (n-- > 0) { tv_list_extend(rettv->vval.v_list, argvars[0].vval.v_list, NULL); } @@ -14124,7 +14127,7 @@ static void f_searchpairpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) int lnum = 0; int col = 0; - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, 2); if (searchpair_cmn(argvars, &match_pos) > 0) { lnum = match_pos.lnum; @@ -14292,18 +14295,14 @@ do_searchpair ( static void f_searchpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) { pos_T match_pos; - int lnum = 0; - int col = 0; - int n; int flags = 0; - tv_list_alloc_ret(rettv); + const int n = search_cmn(argvars, &match_pos, &flags); - n = search_cmn(argvars, &match_pos, &flags); - if (n > 0) { - lnum = match_pos.lnum; - col = match_pos.col; - } + tv_list_alloc_ret(rettv, 2 + (!!(flags & SP_SUBPAT))); + + const int lnum = (n > 0 ? match_pos.lnum : 0); + const int col = (n > 0 ? match_pos.col : 0); tv_list_append_number(rettv->vval.v_list, (varnumber_T)lnum); tv_list_append_number(rettv->vval.v_list, (varnumber_T)col); @@ -14319,7 +14318,7 @@ static void f_serverlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) char **addrs = server_address_list(&n); // Copy addrs into a linked list. - list_T *l = tv_list_alloc_ret(rettv); + list_T *const l = tv_list_alloc_ret(rettv, n); for (size_t i = 0; i < n; i++) { tv_list_append_allocated_string(l, addrs[i]); } @@ -14715,7 +14714,7 @@ static void f_setmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) dictitem_T *const di = tv_dict_find(d, S_LEN("pattern")); if (di == NULL) { if (s == NULL) { - s = tv_list_alloc(); + s = tv_list_alloc(9); } // match from matchaddpos() @@ -15531,8 +15530,6 @@ static void f_spellbadword(typval_T *argvars, typval_T *rettv, FunPtr fptr) hlf_T attr = HLF_COUNT; size_t len = 0; - tv_list_alloc_ret(rettv); - if (argvars[0].v_type == VAR_UNKNOWN) { // Find the start and length of the badly spelled word. len = spell_move_to(curwin, FORWARD, true, true, &attr); @@ -15557,6 +15554,7 @@ static void f_spellbadword(typval_T *argvars, typval_T *rettv, FunPtr fptr) } assert(len <= INT_MAX); + tv_list_alloc_ret(rettv, 2); tv_list_append_string(rettv->vval.v_list, word, len); tv_list_append_string(rettv->vval.v_list, (attr == HLF_SPB ? "bad" @@ -15573,35 +15571,36 @@ static void f_spellsuggest(typval_T *argvars, typval_T *rettv, FunPtr fptr) { bool typeerr = false; int maxcount; - garray_T ga; + garray_T ga = GA_EMPTY_INIT_VALUE; bool need_capital = false; - tv_list_alloc_ret(rettv); - if (curwin->w_p_spell && *curwin->w_s->b_p_spl != NUL) { const char *const str = tv_get_string(&argvars[0]); if (argvars[1].v_type != VAR_UNKNOWN) { maxcount = tv_get_number_chk(&argvars[1], &typeerr); if (maxcount <= 0) { - return; + goto f_spellsuggest_return; } if (argvars[2].v_type != VAR_UNKNOWN) { need_capital = tv_get_number_chk(&argvars[2], &typeerr); if (typeerr) { - return; + goto f_spellsuggest_return; } } - } else + } else { maxcount = 25; + } spell_suggest_list(&ga, (char_u *)str, maxcount, need_capital, false); + } - for (int i = 0; i < ga.ga_len; i++) { - char *p = ((char **)ga.ga_data)[i]; - tv_list_append_allocated_string(rettv->vval.v_list, p); - } - ga_clear(&ga); +f_spellsuggest_return: + tv_list_alloc_ret(rettv, (ptrdiff_t)ga.ga_len); + for (int i = 0; i < ga.ga_len; i++) { + char *const p = ((char **)ga.ga_data)[i]; + tv_list_append_allocated_string(rettv->vval.v_list, p); } + ga_clear(&ga); } static void f_split(typval_T *argvars, typval_T *rettv, FunPtr fptr) @@ -15633,10 +15632,11 @@ static void f_split(typval_T *argvars, typval_T *rettv, FunPtr fptr) pat = "[\\x01- ]\\+"; } - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, kListLenMayKnow); - if (typeerr) + if (typeerr) { return; + } regmatch.regprog = vim_regcomp((char_u *)pat, RE_MAGIC + RE_STRING); if (regmatch.regprog != NULL) { @@ -16263,7 +16263,6 @@ static void f_synconcealed(typval_T *argvars, typval_T *rettv, FunPtr fptr) memset(str, NUL, sizeof(str)); - tv_list_alloc_ret(rettv); if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count && col >= 0 && (size_t)col <= STRLEN(ml_get(lnum)) && curwin->w_p_cole > 0) { (void)syn_get_id(curwin, lnum, col, false, NULL, false); @@ -16276,14 +16275,12 @@ static void f_synconcealed(typval_T *argvars, typval_T *rettv, FunPtr fptr) cchar = lcs_conceal; } if (cchar != NUL) { - if (has_mbyte) - (*mb_char2bytes)(cchar, str); - else - str[0] = cchar; + utf_char2bytes(cchar, str); } } } + tv_list_alloc_ret(rettv, 3); tv_list_append_number(rettv->vval.v_list, (syntax_flags & HL_CONCEAL) != 0); // -1 to auto-determine strlen tv_list_append_string(rettv->vval.v_list, (const char *)str, -1); @@ -16306,7 +16303,7 @@ static void f_synstack(typval_T *argvars, typval_T *rettv, FunPtr fptr) && lnum <= curbuf->b_ml.ml_line_count && col >= 0 && (size_t)col <= STRLEN(ml_get(lnum))) { - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, kListLenMayKnow); (void)syn_get_id(curwin, lnum, col, false, NULL, true); int id; @@ -16322,7 +16319,7 @@ static list_T *string_to_list(const char *str, size_t len, const bool keepempty) if (!keepempty && str[len - 1] == NL) { len--; } - list_T *const list = tv_list_alloc(); + list_T *const list = tv_list_alloc(kListLenMayKnow); encode_list_write(list, str, len); return list; } @@ -16368,7 +16365,7 @@ static void get_system_output_as_rettv(typval_T *argvars, typval_T *rettv, if (res == NULL) { if (retlist) { // return an empty list when there's no output - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, 0); } else { rettv->vval.v_string = (char_u *) xstrdup(""); } @@ -16434,7 +16431,7 @@ static void f_tabpagebuflist(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } if (wp != NULL) { - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, kListLenMayKnow); while (wp != NULL) { tv_list_append_number(rettv->vval.v_list, wp->w_buffer->b_fnum); wp = wp->w_next; @@ -16532,7 +16529,7 @@ static void f_tagfiles(typval_T *argvars, typval_T *rettv, FunPtr fptr) char *fname; tagname_T tn; - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, kListLenUnknown); fname = xmalloc(MAXPATHL); bool first = true; @@ -16561,8 +16558,8 @@ static void f_taglist(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (argvars[1].v_type != VAR_UNKNOWN) { fname = tv_get_string(&argvars[1]); } - (void)get_tags(tv_list_alloc_ret(rettv), (char_u *)tag_pattern, - (char_u *)fname); + (void)get_tags(tv_list_alloc_ret(rettv, kListLenUnknown), + (char_u *)tag_pattern, (char_u *)fname); } /* @@ -16803,7 +16800,9 @@ static void add_timer_info_all(typval_T *rettv) /// "timer_info([timer])" function static void f_timer_info(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, (argvars[0].v_type != VAR_UNKNOWN + ? 1 + : timers->table->n_occupied)); if (argvars[0].v_type != VAR_UNKNOWN) { if (argvars[0].v_type != VAR_NUMBER) { EMSG(_(e_number_exp)); @@ -17163,7 +17162,6 @@ static void f_undotree(typval_T *argvars, typval_T *rettv, FunPtr fptr) tv_dict_alloc_ret(rettv); dict_T *dict = rettv->vval.v_dict; - list_T *list; tv_dict_add_nr(dict, S_LEN("synced"), (varnumber_T)curbuf->b_u_synced); tv_dict_add_nr(dict, S_LEN("seq_last"), (varnumber_T)curbuf->b_u_seq_last); @@ -17173,9 +17171,7 @@ static void f_undotree(typval_T *argvars, typval_T *rettv, FunPtr fptr) tv_dict_add_nr(dict, S_LEN("time_cur"), (varnumber_T)curbuf->b_u_time_cur); tv_dict_add_nr(dict, S_LEN("save_cur"), (varnumber_T)curbuf->b_u_save_nr_cur); - list = tv_list_alloc(); - u_eval_tree(curbuf->b_u_oldhead, list); - tv_dict_add_list(dict, S_LEN("entries"), list); + tv_dict_add_list(dict, S_LEN("entries"), u_eval_tree(curbuf->b_u_oldhead)); } /* @@ -17234,7 +17230,7 @@ static void f_wildmenumode(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// "win_findbuf()" function static void f_win_findbuf(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, kListLenMayKnow); win_findbuf(argvars, rettv->vval.v_list); } @@ -17253,8 +17249,7 @@ static void f_win_gotoid(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// "win_id2tabwin()" function static void f_win_id2tabwin(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - tv_list_alloc_ret(rettv); - win_id2tabwin(argvars, rettv->vval.v_list); + win_id2tabwin(argvars, rettv); } /// "win_id2win()" function @@ -22367,7 +22362,7 @@ static void script_host_eval(char *name, typval_T *argvars, typval_T *rettv) return; } - list_T *args = tv_list_alloc(); + list_T *args = tv_list_alloc(1); tv_list_append_string(args, (const char *)argvars[0].vval.v_string, -1); *rettv = eval_call_provider(name, "eval", args); } diff --git a/src/nvim/eval/decode.c b/src/nvim/eval/decode.c index af4e055d23..c729d00ae7 100644 --- a/src/nvim/eval/decode.c +++ b/src/nvim/eval/decode.c @@ -150,7 +150,7 @@ static inline int json_decoder_pop(ValuesStackItem obj, } obj_di->di_tv = obj.val; } else { - list_T *const kv_pair = tv_list_alloc(); + list_T *const kv_pair = tv_list_alloc(2); tv_list_append_list(last_container.special_val, kv_pair); tv_list_append_owned_tv(kv_pair, key.val); tv_list_append_owned_tv(kv_pair, obj.val); @@ -221,13 +221,18 @@ static inline int json_decoder_pop(ValuesStackItem obj, /// Create a new special dictionary that ought to represent a MAP /// /// @param[out] ret_tv Address where new special dictionary is saved. +/// @param[in] len Expected number of items to be populated before list +/// becomes accessible from VimL. It is still valid to +/// underpopulate a list, value only controls how many elements +/// will be allocated in advance. @see ListLenSpecials. /// /// @return [allocated] list which should contain key-value pairs. Return value /// may be safely ignored. -list_T *decode_create_map_special_dict(typval_T *const ret_tv) +list_T *decode_create_map_special_dict(typval_T *const ret_tv, + const ptrdiff_t len) FUNC_ATTR_NONNULL_ALL { - list_T *const list = tv_list_alloc(); + list_T *const list = tv_list_alloc(len); tv_list_ref(list); create_special_dict(ret_tv, kMPMap, ((typval_T) { .v_type = VAR_LIST, @@ -263,7 +268,7 @@ typval_T decode_string(const char *const s, const size_t len, ? ((s != NULL) && (memchr(s, NUL, len) != NULL)) : (bool)hasnul); if (really_hasnul) { - list_T *const list = tv_list_alloc(); + list_T *const list = tv_list_alloc(kListLenMayKnow); tv_list_ref(list); typval_T tv; create_special_dict(&tv, binary ? kMPBinary : kMPString, ((typval_T) { @@ -843,7 +848,7 @@ json_decode_string_cycle_start: break; } case '[': { - list_T *list = tv_list_alloc(); + list_T *list = tv_list_alloc(kListLenMayKnow); tv_list_ref(list); typval_T tv = { .v_type = VAR_LIST, @@ -864,7 +869,7 @@ json_decode_string_cycle_start: list_T *val_list = NULL; if (next_map_special) { next_map_special = false; - val_list = decode_create_map_special_dict(&tv); + val_list = decode_create_map_special_dict(&tv, kListLenMayKnow); } else { dict_T *dict = tv_dict_alloc(); dict->dv_refcount++; @@ -964,7 +969,7 @@ int msgpack_to_vim(const msgpack_object mobj, typval_T *const rettv) .vval = { .v_number = (varnumber_T) mobj.via.u64 }, }; } else { - list_T *const list = tv_list_alloc(); + list_T *const list = tv_list_alloc(4); tv_list_ref(list); create_special_dict(rettv, kMPInteger, ((typval_T) { .v_type = VAR_LIST, @@ -987,7 +992,7 @@ int msgpack_to_vim(const msgpack_object mobj, typval_T *const rettv) .vval = { .v_number = (varnumber_T) mobj.via.i64 }, }; } else { - list_T *const list = tv_list_alloc(); + list_T *const list = tv_list_alloc(4); tv_list_ref(list); create_special_dict(rettv, kMPInteger, ((typval_T) { .v_type = VAR_LIST, @@ -1033,7 +1038,7 @@ int msgpack_to_vim(const msgpack_object mobj, typval_T *const rettv) break; } case MSGPACK_OBJECT_ARRAY: { - list_T *const list = tv_list_alloc(); + list_T *const list = tv_list_alloc((ptrdiff_t)mobj.via.array.size); tv_list_ref(list); *rettv = (typval_T) { .v_type = VAR_LIST, @@ -1085,9 +1090,10 @@ int msgpack_to_vim(const msgpack_object mobj, typval_T *const rettv) } break; msgpack_to_vim_generic_map: {} - list_T *const list = decode_create_map_special_dict(rettv); + list_T *const list = decode_create_map_special_dict( + rettv, (ptrdiff_t)mobj.via.map.size); for (size_t i = 0; i < mobj.via.map.size; i++) { - list_T *const kv_pair = tv_list_alloc(); + list_T *const kv_pair = tv_list_alloc(2); tv_list_append_list(list, kv_pair); typval_T key_tv = { .v_type = VAR_UNKNOWN }; @@ -1107,10 +1113,10 @@ msgpack_to_vim_generic_map: {} break; } case MSGPACK_OBJECT_EXT: { - list_T *const list = tv_list_alloc(); + list_T *const list = tv_list_alloc(2); tv_list_ref(list); tv_list_append_number(list, mobj.via.ext.type); - list_T *const ext_val_list = tv_list_alloc(); + list_T *const ext_val_list = tv_list_alloc(kListLenMayKnow); tv_list_append_list(list, ext_val_list); create_special_dict(rettv, kMPExt, ((typval_T) { .v_type = VAR_LIST, diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index ac6c8c8aa6..6f829be0b4 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -132,8 +132,14 @@ void tv_list_watch_fix(list_T *const l, const listitem_T *const item) /// /// Caller should take care of the reference count. /// +/// @param[in] len Expected number of items to be populated before list +/// becomes accessible from VimL. It is still valid to +/// underpopulate a list, value only controls how many elements +/// will be allocated in advance. Currently does nothing. +/// @see ListLenSpecials. +/// /// @return [allocated] new list. -list_T *tv_list_alloc(void) +list_T *tv_list_alloc(const ptrdiff_t len) FUNC_ATTR_NONNULL_RET { list_T *const list = xcalloc(1, sizeof(list_T)); @@ -521,7 +527,7 @@ list_T *tv_list_copy(const vimconv_T *const conv, list_T *const orig, return NULL; } - list_T *copy = tv_list_alloc(); + list_T *copy = tv_list_alloc(tv_list_len(orig)); tv_list_ref(copy); if (copyID != 0) { // Do this before adding the items, because one of the items may @@ -1817,12 +1823,16 @@ void tv_dict_set_keys_readonly(dict_T *const dict) /// Also sets reference count. /// /// @param[out] ret_tv Structure where list is saved. +/// @param[in] len Expected number of items to be populated before list +/// becomes accessible from VimL. It is still valid to +/// underpopulate a list, value only controls how many elements +/// will be allocated in advance. @see ListLenSpecials. /// /// @return [allocated] pointer to the created list. -list_T *tv_list_alloc_ret(typval_T *const ret_tv) +list_T *tv_list_alloc_ret(typval_T *const ret_tv, const ptrdiff_t len) FUNC_ATTR_NONNULL_ALL { - list_T *const l = tv_list_alloc(); + list_T *const l = tv_list_alloc(len); ret_tv->vval.v_list = l; ret_tv->v_type = VAR_LIST; ret_tv->v_lock = VAR_UNLOCKED; diff --git a/src/nvim/eval/typval.h b/src/nvim/eval/typval.h index c9a9a3e7e8..3993a384d2 100644 --- a/src/nvim/eval/typval.h +++ b/src/nvim/eval/typval.h @@ -31,6 +31,25 @@ typedef double float_T; /// Refcount for dict or list that should not be freed enum { DO_NOT_FREE_CNT = (INT_MAX / 2) }; +/// Additional values for tv_list_alloc() len argument +enum { + /// List length is not known in advance + /// + /// To be used when there is neither a way to know how many elements will be + /// needed nor are any educated guesses. + kListLenUnknown = -1, + /// List length *should* be known, but is actually not + /// + /// All occurrences of this value should be eventually removed. This is for + /// the case when the only reason why list length is not known is that it + /// would be hard to code without refactoring, but refactoring is needed. + kListLenShouldKnow = -2, + /// List length may be known in advance, but it requires too much effort + /// + /// To be used when it looks impractical to determine list length. + kListLenMayKnow = -3, +} ListLenSpecials; + /// Maximal possible value of varnumber_T variable #define VARNUMBER_MAX INT64_MAX #define UVARNUMBER_MAX UINT64_MAX diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c index ec4ce63e17..28b021d4e4 100644 --- a/src/nvim/ex_cmds2.c +++ b/src/nvim/ex_cmds2.c @@ -3758,7 +3758,7 @@ static void script_host_execute(char *name, exarg_T *eap) char *const script = script_get(eap, &len); if (script != NULL) { - list_T *const args = tv_list_alloc(); + list_T *const args = tv_list_alloc(3); // script tv_list_append_allocated_string(args, script); // current range @@ -3773,7 +3773,7 @@ static void script_host_execute_file(char *name, exarg_T *eap) uint8_t buffer[MAXPATHL]; vim_FullName((char *)eap->arg, (char *)buffer, sizeof(buffer), false); - list_T *args = tv_list_alloc(); + list_T *args = tv_list_alloc(3); // filename tv_list_append_string(args, (const char *)buffer, -1); // current range @@ -3784,7 +3784,7 @@ static void script_host_execute_file(char *name, exarg_T *eap) static void script_host_do_range(char *name, exarg_T *eap) { - list_T *args = tv_list_alloc(); + list_T *args = tv_list_alloc(3); tv_list_append_number(args, (int)eap->line1); tv_list_append_number(args, (int)eap->line2); tv_list_append_string(args, (const char *)eap->arg, -1); diff --git a/src/nvim/lua/converter.c b/src/nvim/lua/converter.c index 61cb428923..9e3063b164 100644 --- a/src/nvim/lua/converter.c +++ b/src/nvim/lua/converter.c @@ -211,7 +211,7 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) size_t len; const char *s = lua_tolstring(lstate, -2, &len); if (cur.special) { - list_T *const kv_pair = tv_list_alloc(); + list_T *const kv_pair = tv_list_alloc(2); typval_T s_tv = decode_string(s, len, kTrue, false, false); if (s_tv.v_type == VAR_UNKNOWN) { @@ -321,7 +321,7 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) switch (table_props.type) { case kObjectTypeArray: { cur.tv->v_type = VAR_LIST; - cur.tv->vval.v_list = tv_list_alloc(); + cur.tv->vval.v_list = tv_list_alloc((ptrdiff_t)table_props.maxidx); tv_list_ref(cur.tv->vval.v_list); if (table_props.maxidx != 0) { cur.container = true; @@ -338,7 +338,8 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) } else { cur.special = table_props.has_string_with_nul; if (table_props.has_string_with_nul) { - decode_create_map_special_dict(cur.tv); + decode_create_map_special_dict( + cur.tv, (ptrdiff_t)table_props.string_keys_num); assert(cur.tv->v_type == VAR_DICT); dictitem_T *const val_di = tv_dict_find(cur.tv->vval.v_dict, S_LEN("_VAL")); diff --git a/src/nvim/main.c b/src/nvim/main.c index 0b24023ad0..015df5d070 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -412,7 +412,7 @@ int main(int argc, char **argv) } // It's better to make v:oldfiles an empty list than NULL. if (get_vim_var_list(VV_OLDFILES) == NULL) { - set_vim_var_list(VV_OLDFILES, tv_list_alloc()); + set_vim_var_list(VV_OLDFILES, tv_list_alloc(0)); } /* diff --git a/src/nvim/menu.c b/src/nvim/menu.c index 01c8e94bac..42417f75d5 100644 --- a/src/nvim/menu.c +++ b/src/nvim/menu.c @@ -714,7 +714,7 @@ static dict_T *menu_get_recursive(const vimmenu_T *menu, int modes) } } else { // visit recursively all children - list_T *children_list = tv_list_alloc(); + list_T *const children_list = tv_list_alloc(kListLenMayKnow); for (menu = menu->children; menu != NULL; menu = menu->next) { dict_T *dic = menu_get_recursive(menu, modes); if (tv_dict_len(dict) > 0) { diff --git a/src/nvim/ops.c b/src/nvim/ops.c index a5e131190d..b606f10b88 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -2564,7 +2564,7 @@ static void do_autocmd_textyankpost(oparg_T *oap, yankreg_T *reg) dict_T *dict = get_vim_var_dict(VV_EVENT); // the yanked text - list_T *list = tv_list_alloc(); + list_T *const list = tv_list_alloc((ptrdiff_t)reg->y_size); for (size_t i = 0; i < reg->y_size; i++) { tv_list_append_string(list, (const char *)reg->y_array[i], -1); } @@ -4854,7 +4854,7 @@ static void *get_reg_wrap_one_line(char_u *s, int flags) if (!(flags & kGRegList)) { return s; } - list_T *const list = tv_list_alloc(); + list_T *const list = tv_list_alloc(1); tv_list_append_allocated_string(list, (char *)s); return list; } @@ -4904,7 +4904,7 @@ void *get_reg_contents(int regname, int flags) return NULL; if (flags & kGRegList) { - list_T *list = tv_list_alloc(); + list_T *const list = tv_list_alloc((ptrdiff_t)reg->y_size); for (size_t i = 0; i < reg->y_size; i++) { tv_list_append_string(list, (const char *)reg->y_array[i], -1); } @@ -5593,7 +5593,7 @@ static bool get_clipboard(int name, yankreg_T **target, bool quiet) } free_register(reg); - list_T *const args = tv_list_alloc(); + list_T *const args = tv_list_alloc(1); const char regname = (char)name; tv_list_append_string(args, ®name, 1); @@ -5712,15 +5712,13 @@ static void set_clipboard(int name, yankreg_T *reg) return; } - list_T *lines = tv_list_alloc(); + list_T *const lines = tv_list_alloc( + (ptrdiff_t)reg->y_size + (reg->y_type != kMTCharWise)); for (size_t i = 0; i < reg->y_size; i++) { tv_list_append_string(lines, (const char *)reg->y_array[i], -1); } - list_T *args = tv_list_alloc(); - tv_list_append_list(args, lines); - char regtype; switch (reg->y_type) { case kMTLineWise: { @@ -5741,10 +5739,11 @@ static void set_clipboard(int name, yankreg_T *reg) assert(false); } } - tv_list_append_string(args, ®type, 1); - const char regname = (char)name; - tv_list_append_string(args, ®name, 1); + list_T *args = tv_list_alloc(3); + tv_list_append_list(args, lines); + tv_list_append_string(args, ®type, 1); + tv_list_append_string(args, ((char[]) { (char)name }), 1); (void)eval_call_provider("clipboard", "set", args); } diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 224e43008d..63252df3dc 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -4193,7 +4193,7 @@ int get_errorlist_properties(win_T *wp, dict_T *what, dict_T *retdict) } } if ((status == OK) && (flags & QF_GETLIST_ITEMS)) { - list_T *l = tv_list_alloc(); + list_T *l = tv_list_alloc(kListLenMayKnow); (void)get_errorlist(wp, qf_idx, l); tv_dict_add_list(retdict, S_LEN("items"), l); } diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c index ddc3681867..e4de43b49e 100644 --- a/src/nvim/regexp.c +++ b/src/nvim/regexp.c @@ -7044,7 +7044,7 @@ list_T *reg_submatch_list(int no) colnr_T scol = rsm.sm_mmatch->startpos[no].col; colnr_T ecol = rsm.sm_mmatch->endpos[no].col; - list = tv_list_alloc(); + list = tv_list_alloc(elnum - slnum + 1); s = (const char *)reg_getline_submatch(slnum) + scol; if (slnum == elnum) { @@ -7063,7 +7063,7 @@ list_T *reg_submatch_list(int no) if (s == NULL || rsm.sm_match->endp[no] == NULL) { return NULL; } - list = tv_list_alloc(); + list = tv_list_alloc(1); tv_list_append_string(list, s, (const char *)rsm.sm_match->endp[no] - s); } diff --git a/src/nvim/shada.c b/src/nvim/shada.c index ce9303f14d..c00fd912ec 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -1217,7 +1217,7 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) khash_t(fnamebufs) fname_bufs = KHASH_EMPTY_TABLE(fnamebufs); khash_t(strset) oldfiles_set = KHASH_EMPTY_TABLE(strset); if (get_old_files && (oldfiles_list == NULL || force)) { - oldfiles_list = tv_list_alloc(); + oldfiles_list = tv_list_alloc(kListLenUnknown); set_vim_var_list(VV_OLDFILES, oldfiles_list); } ShaDaReadResult srni_ret; diff --git a/src/nvim/tag.c b/src/nvim/tag.c index be9d621c7d..f23465e501 100644 --- a/src/nvim/tag.c +++ b/src/nvim/tag.c @@ -667,7 +667,7 @@ do_tag ( fname = xmalloc(MAXPATHL + 1); cmd = xmalloc(CMDBUFFSIZE + 1); - list = tv_list_alloc(); + list = tv_list_alloc(num_matches); for (i = 0; i < num_matches; ++i) { int len, cmd_len; diff --git a/src/nvim/undo.c b/src/nvim/undo.c index f611a3bb29..b902f82f31 100644 --- a/src/nvim/undo.c +++ b/src/nvim/undo.c @@ -2941,17 +2941,20 @@ bool curbufIsChanged(void) && (curbuf->b_changed || file_ff_differs(curbuf, true))); } -/* - * For undotree(): Append the list of undo blocks at "first_uhp" to "list". - * Recursive. - */ -void u_eval_tree(u_header_T *first_uhp, list_T *list) +/// Append the list of undo blocks to a newly allocated list +/// +/// For use in undotree(). Recursive. +/// +/// @param[in] first_uhp Undo blocks list to start with. +/// +/// @return [allocated] List with a representation of undo blocks. +list_T *u_eval_tree(const u_header_T *const first_uhp) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_RET { - u_header_T *uhp = first_uhp; - dict_T *dict; + list_T *const list = tv_list_alloc(kListLenMayKnow); - while (uhp != NULL) { - dict = tv_dict_alloc(); + for (const u_header_T *uhp = first_uhp; uhp != NULL; uhp = uhp->uh_prev.ptr) { + dict_T *const dict = tv_dict_alloc(); tv_dict_add_nr(dict, S_LEN("seq"), (varnumber_T)uhp->uh_seq); tv_dict_add_nr(dict, S_LEN("time"), (varnumber_T)uhp->uh_time); if (uhp == curbuf->b_u_newhead) { @@ -2965,14 +2968,12 @@ void u_eval_tree(u_header_T *first_uhp, list_T *list) } if (uhp->uh_alt_next.ptr != NULL) { - list_T *alt_list = tv_list_alloc(); - // Recursive call to add alternate undo tree. - u_eval_tree(uhp->uh_alt_next.ptr, alt_list); - tv_dict_add_list(dict, S_LEN("alt"), alt_list); + tv_dict_add_list(dict, S_LEN("alt"), u_eval_tree(uhp->uh_alt_next.ptr)); } tv_list_append_dict(list, dict); - uhp = uhp->uh_prev.ptr; } + + return list; } diff --git a/src/nvim/window.c b/src/nvim/window.c index b687781dfb..4dfc72f212 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -5921,13 +5921,15 @@ void win_get_tabwin(handle_T id, int *tabnr, int *winnr) } } -void win_id2tabwin(typval_T *argvars, list_T *list) +void win_id2tabwin(typval_T *const argvars, typval_T *const rettv) { int winnr = 1; int tabnr = 1; handle_T id = (handle_T)tv_get_number(&argvars[0]); win_get_tabwin(id, &tabnr, &winnr); + + list_T *const list = tv_list_alloc_ret(rettv, 2); tv_list_append_number(list, tabnr); tv_list_append_number(list, winnr); } diff --git a/test/unit/eval/helpers.lua b/test/unit/eval/helpers.lua index 6babd4be77..3d1c42c3a0 100644 --- a/test/unit/eval/helpers.lua +++ b/test/unit/eval/helpers.lua @@ -312,7 +312,7 @@ local lua2typvalt_type_tab = { processed[l].lv_refcount = processed[l].lv_refcount + 1 return typvalt(eval.VAR_LIST, {v_list=processed[l]}) end - local lst = populate_list(eval.tv_list_alloc(), l, processed) + local lst = populate_list(eval.tv_list_alloc(#l), l, processed) return typvalt(eval.VAR_LIST, {v_list=lst}) end, [dict_type] = function(l, processed) @@ -433,7 +433,8 @@ local function int(n) end local function list(...) - return populate_list(ffi.gc(eval.tv_list_alloc(), eval.tv_list_unref), + return populate_list(ffi.gc(eval.tv_list_alloc(select('#', ...)), + eval.tv_list_unref), {...}, {}) end diff --git a/test/unit/eval/typval_spec.lua b/test/unit/eval/typval_spec.lua index b668144175..919a42fbb9 100644 --- a/test/unit/eval/typval_spec.lua +++ b/test/unit/eval/typval_spec.lua @@ -2407,7 +2407,7 @@ describe('typval.c', function() describe('list ret()', function() itp('works', function() local rettv = typvalt(lib.VAR_UNKNOWN) - local l = lib.tv_list_alloc_ret(rettv) + local l = lib.tv_list_alloc_ret(rettv, 0) eq(empty_list, typvalt2lua(rettv)) eq(rettv.vval.v_list, l) end) -- cgit From c10ae4bc8548ca170876e00489fb5d907801e553 Mon Sep 17 00:00:00 2001 From: ZyX Date: Wed, 3 Jan 2018 03:31:10 +0300 Subject: os/fileio: Fix some flag names in file_* functions documentation --- src/nvim/os/fileio.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/nvim/os/fileio.c b/src/nvim/os/fileio.c index 5d68473982..d294f9139b 100644 --- a/src/nvim/os/fileio.c +++ b/src/nvim/os/fileio.c @@ -39,9 +39,9 @@ /// @param[in] fname File name to open. /// @param[in] flags Flags, @see FileOpenFlags. Currently reading from and /// writing to the file at once is not supported, so either -/// FILE_WRITE_ONLY or FILE_READ_ONLY is required. +/// kFileWriteOnly or kFileReadOnly is required. /// @param[in] mode Permissions for the newly created file (ignored if flags -/// does not have FILE_CREATE\*). +/// does not have kFileCreate\*). /// /// @return Error code (@see os_strerror()) or 0. int file_open(FileDescriptor *const ret_fp, const char *const fname, @@ -120,7 +120,7 @@ int file_open_fd(FileDescriptor *const ret_fp, const int fd, const bool wr) /// @param[in] fname File name to open. /// @param[in] flags Flags, @see FileOpenFlags. /// @param[in] mode Permissions for the newly created file (ignored if flags -/// does not have FILE_CREATE\*). +/// does not have kFileCreate\*). /// /// @return [allocated] Opened file or NULL in case of error. FileDescriptor *file_open_new(int *const error, const char *const fname, -- cgit From 6a1557f2f496bf8e8f4dad7ed0d423051a7b65e2 Mon Sep 17 00:00:00 2001 From: ZyX Date: Wed, 3 Jan 2018 04:25:42 +0300 Subject: eval/typval: Log list actions New logging is guarded by cmake LOG_LIST_ACTIONS define. To make it more efficient it is allocated as a linked list with chunks of length 2^(7+chunk_num); that uses basically the same idea as behind increasing kvec length (make appending O(1) (amortized)), but reduces constant by not bothering to move memory around what realloc() would surely do: it is not like we need random access to log entries here to justify usage of a single continuous memory block. --- CMakeLists.txt | 2 + config/config.h.in | 1 + src/nvim/eval.c | 12 ++++++ src/nvim/eval.lua | 1 + src/nvim/eval/typval.c | 81 +++++++++++++++++++++++++++++++++++++++++ src/nvim/eval/typval.h | 99 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/nvim/memory.c | 2 + 7 files changed, 198 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index fb0cc262b1..74ff32a23d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -277,6 +277,8 @@ else() set(DEBUG 0) endif() +option(LOG_LIST_ACTIONS "Add list actions logging" OFF) + add_definitions(-DINCLUDE_GENERATED_DECLARATIONS) if(CMAKE_COMPILER_IS_GNUCXX AND CMAKE_SYSTEM_NAME STREQUAL "Linux") diff --git a/config/config.h.in b/config/config.h.in index 962eefd7a7..410d9cc825 100644 --- a/config/config.h.in +++ b/config/config.h.in @@ -62,6 +62,7 @@ #ifndef UNIT_TESTING #cmakedefine HAVE_JEMALLOC +#cmakedefine LOG_LIST_ACTIONS #endif #cmakedefine HAVE_BE64TOH diff --git a/src/nvim/eval.c b/src/nvim/eval.c index ec32f1821d..55396b070d 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -16665,6 +16665,18 @@ static void f_test_garbagecollect_now(typval_T *argvars, garbage_collect(true); } +// "test_write_list_log()" function +static void f_test_write_list_log(typval_T *const argvars, + typval_T *const rettv, + FunPtr fptr) +{ + const char *const fname = tv_get_string_chk(&argvars[0]); + if (fname == NULL) { + return; + } + list_write_log(fname); +} + bool callback_from_typval(Callback *const callback, typval_T *const arg) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index 54cbc54d78..daa3b637a3 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -313,6 +313,7 @@ return { tempname={}, termopen={args={1, 2}}, test_garbagecollect_now={}, + test_write_list_log={args=1}, timer_info={args={0,1}}, timer_pause={args=2}, timer_start={args={2,3}}, diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 6f829be0b4..c8b550f902 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -31,6 +31,7 @@ #include "nvim/message.h" // TODO(ZyX-I): Move line_breakcheck out of misc1 #include "nvim/misc1.h" // For line_breakcheck +#include "nvim/os/fileio.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "eval/typval.c.generated.h" @@ -45,6 +46,70 @@ bool tv_in_free_unref_items = false; const char *const tv_empty_string = ""; //{{{1 Lists +//{{{2 List log +#ifdef LOG_LIST_ACTIONS +ListLog *list_log_first = NULL; +ListLog *list_log_last = NULL; + +/// Write list log to the given file +/// +/// @param[in] fname File to write log to. Will be appended to if already +/// present. +void list_write_log(const char *const fname) + FUNC_ATTR_NONNULL_ALL +{ + FileDescriptor fp; + const int fo_ret = file_open(&fp, fname, kFileCreate|kFileAppend, 0600); + if (fo_ret != 0) { + emsgf(_("E5142: Failed to open file %s: %s"), fname, os_strerror(fo_ret)); + return; + } + for (ListLog *chunk = list_log_first; chunk != NULL;) { + for (size_t i = 0; i < chunk->size; i++) { + char buf[10 + 1 + ((16 + 3) * 3) + (8 + 2) + 2]; + // act : hex " c:" len "[]" "\n\0" + const ListLogEntry entry = chunk->entries[i]; + const size_t snp_len = (size_t)snprintf( + buf, sizeof(buf), + "%-10.10s: l:%016" PRIxPTR "[%08d] 1:%016" PRIxPTR " 2:%016" PRIxPTR + "\n", + entry.action, entry.l, entry.len, entry.li1, entry.li2); + assert(snp_len + 1 == sizeof(buf)); + const ptrdiff_t fw_ret = file_write(&fp, buf, snp_len); + if (fw_ret != (ptrdiff_t)snp_len) { + assert(fw_ret < 0); + if (i) { + memmove(chunk->entries, chunk->entries + i, + sizeof(chunk->entries[0]) * (chunk->size - i)); + chunk->size -= i; + } + emsgf(_("E5143: Failed to write to file %s: %s"), + fname, os_strerror((int)fw_ret)); + return; + } + } + list_log_first = chunk->next; + xfree(chunk); + chunk = list_log_first; + } + const int fc_ret = file_close(&fp, true); + if (fc_ret != 0) { + emsgf(_("E5144: Failed to close file %s: %s"), fname, os_strerror(fc_ret)); + } +} + +#ifdef EXITFREE +/// Free list log +void list_free_log(void) +{ + for (ListLog *chunk = list_log_first; chunk != NULL;) { + list_log_first = chunk->next; + xfree(chunk); + chunk = list_log_first; + } +} +#endif +#endif //{{{2 List item /// Allocate a list item @@ -151,6 +216,7 @@ list_T *tv_list_alloc(const ptrdiff_t len) list->lv_used_prev = NULL; list->lv_used_next = gc_first_list; gc_first_list = list; + list_log(list, NULL, (void *)(uintptr_t)len, "alloc"); return list; } @@ -180,6 +246,8 @@ void tv_list_init_static10(staticList10_T *const sl) li->li_prev = li - 1; li->li_next = li + 1; } + list_log((const list_T *)sl, &sl->sl_items[0], &sl->sl_items[SL_SIZE - 1], + "s10init"); #undef SL_SIZE } @@ -191,6 +259,7 @@ void tv_list_init_static(list_T *const l) { memset(l, 0, sizeof(*l)); l->lv_refcount = DO_NOT_FREE_CNT; + list_log(l, NULL, NULL, "sinit"); } /// Free items contained in a list @@ -199,6 +268,7 @@ void tv_list_init_static(list_T *const l) void tv_list_free_contents(list_T *const l) FUNC_ATTR_NONNULL_ALL { + list_log(l, NULL, NULL, "freecont"); for (listitem_T *item = l->lv_first; item != NULL; item = l->lv_first) { // Remove the item before deleting it. l->lv_first = item->li_next; @@ -228,6 +298,7 @@ void tv_list_free_list(list_T *const l) if (l->lv_used_next != NULL) { l->lv_used_next->lv_used_prev = l->lv_used_prev; } + list_log(l, NULL, NULL, "freelist"); xfree(l); } @@ -272,6 +343,7 @@ void tv_list_drop_items(list_T *const l, listitem_T *const item, listitem_T *const item2) FUNC_ATTR_NONNULL_ALL { + list_log(l, item, item2, "drop"); // Notify watchers. for (listitem_T *ip = item; ip != item2->li_next; ip = ip->li_next) { l->lv_len--; @@ -289,6 +361,7 @@ void tv_list_drop_items(list_T *const l, listitem_T *const item, item->li_prev->li_next = item2->li_next; } l->lv_idx_item = NULL; + list_log(l, l->lv_first, l->lv_last, "afterdrop"); } /// Like tv_list_drop_items, but also frees all removed items @@ -296,6 +369,7 @@ void tv_list_remove_items(list_T *const l, listitem_T *const item, listitem_T *const item2) FUNC_ATTR_NONNULL_ALL { + list_log(l, item, item2, "remove"); tv_list_drop_items(l, item, item2); for (listitem_T *li = item;;) { tv_clear(TV_LIST_ITEM_TV(li)); @@ -320,6 +394,7 @@ void tv_list_move_items(list_T *const l, listitem_T *const item, const int cnt) FUNC_ATTR_NONNULL_ALL { + list_log(l, item, item2, "move"); tv_list_drop_items(l, item, item2); item->li_prev = tgt_l->lv_last; item2->li_next = NULL; @@ -330,6 +405,7 @@ void tv_list_move_items(list_T *const l, listitem_T *const item, } tgt_l->lv_last = item2; tgt_l->lv_len += cnt; + list_log(tgt_l, tgt_l->lv_first, tgt_l->lv_last, "movetgt"); } /// Insert list item @@ -358,6 +434,7 @@ void tv_list_insert(list_T *const l, listitem_T *const ni, } item->li_prev = ni; l->lv_len++; + list_log(l, ni, item, "insert"); } } @@ -384,6 +461,7 @@ void tv_list_insert_tv(list_T *const l, typval_T *const tv, void tv_list_append(list_T *const l, listitem_T *const item) FUNC_ATTR_NONNULL_ALL { + list_log(l, item, NULL, "append"); if (l->lv_last == NULL) { // empty list l->lv_first = item; @@ -747,6 +825,7 @@ void tv_list_reverse(list_T *const l) if (tv_list_len(l) <= 1) { return; } + list_log(l, NULL, NULL, "reverse"); #define SWAP(a, b) \ do { \ tmp = a; \ @@ -785,6 +864,7 @@ void tv_list_item_sort(list_T *const l, ListSortItem *const ptrs, if (len <= 1) { return; } + list_log(l, NULL, NULL, "sort"); int i = 0; TV_LIST_ITER(l, li, { ptrs[i].item = li; @@ -873,6 +953,7 @@ listitem_T *tv_list_find(list_T *const l, int n) // Cache the used index. l->lv_idx = idx; l->lv_idx_item = item; + list_log(l, l->lv_idx_item, (void *)(uintptr_t)l->lv_idx, "find"); return item; } diff --git a/src/nvim/eval/typval.h b/src/nvim/eval/typval.h index 3993a384d2..40a1738d9e 100644 --- a/src/nvim/eval/typval.h +++ b/src/nvim/eval/typval.h @@ -20,6 +20,9 @@ #include "nvim/gettext.h" #include "nvim/message.h" #include "nvim/macros.h" +#ifdef LOG_LIST_ACTIONS +# include "nvim/memory.h" +#endif /// Type used for VimL VAR_NUMBER values typedef int64_t varnumber_T; @@ -323,6 +326,96 @@ typedef struct { typedef int (*ListSorter)(const void *, const void *); +#ifdef LOG_LIST_ACTIONS + +/// List actions log entry +typedef struct { + uintptr_t l; ///< List log entry belongs to. + uintptr_t li1; ///< First list item log entry belongs to, if applicable. + uintptr_t li2; ///< Second list item log entry belongs to, if applicable. + int len; ///< List length when log entry was created. + const char *action; ///< Logged action. +} ListLogEntry; + +typedef struct list_log ListLog; + +/// List actions log +struct list_log { + ListLog *next; ///< Next chunk or NULL. + size_t capacity; ///< Number of entries in current chunk. + size_t size; ///< Current chunk size. + ListLogEntry entries[]; ///< Actual log entries. +}; + +extern ListLog *list_log_first; ///< First list log chunk, NULL if missing +extern ListLog *list_log_last; ///< Last list log chunk + +static inline ListLog *list_log_alloc(const size_t size) + REAL_FATTR_ALWAYS_INLINE REAL_FATTR_WARN_UNUSED_RESULT; + +/// Allocate a new log chunk and update globals +/// +/// @param[in] size Number of entries in a new chunk. +/// +/// @return [allocated] Newly allocated chunk. +static inline ListLog *list_log_new(const size_t size) +{ + ListLog *ret = xmalloc(offsetof(ListLog, entries) + + size * sizeof(ret->entries[0])); + ret->size = 0; + ret->capacity = size; + ret->next = NULL; + if (list_log_first == NULL) { + list_log_first = ret; + } else { + list_log_last->next = ret; + } + list_log_last = ret; + return ret; +} + +static inline void list_log(const list_T *const l, + const listitem_T *const li1, + const listitem_T *const li2, + const char *const action) + REAL_FATTR_ALWAYS_INLINE; + +/// Add new entry to log +/// +/// If last chunk was filled it uses twice as much memory to allocate the next +/// chunk. +/// +/// @param[in] l List to which entry belongs. +/// @param[in] li1 List item 1. +/// @param[in] li2 List item 2, often used for integers and not list items. +/// @param[in] action Logged action. +static inline void list_log(const list_T *const l, + const listitem_T *const li1, + const listitem_T *const li2, + const char *const action) +{ + ListLog *tgt; + if (list_log_first == NULL) { + tgt = list_log_new(128); + } else if (list_log_last->size == list_log_last->capacity) { + tgt = list_log_new(list_log_last->capacity * 2); + } else { + tgt = list_log_last; + } + tgt->entries[tgt->size++] = (ListLogEntry) { + .l = (uintptr_t)l, + .li1 = (uintptr_t)li1, + .li2 = (uintptr_t)li2, + .len = (l == NULL ? 0 : l->lv_len), + .action = action, + }; +} +#else +# define list_log(...) +# define list_write_log(...) +# define list_free_log() +#endif + // In a hashtab item "hi_key" points to "di_key" in a dictitem. // This avoids adding a pointer to the hashtab item. @@ -396,6 +489,7 @@ static inline int tv_list_len(const list_T *const l) /// @param[in] l List to check. static inline int tv_list_len(const list_T *const l) { + list_log(l, NULL, NULL, "len"); if (l == NULL) { return 0; } @@ -479,8 +573,10 @@ static inline listitem_T *tv_list_first(const list_T *const l) static inline listitem_T *tv_list_first(const list_T *const l) { if (l == NULL) { + list_log(l, NULL, NULL, "first"); return NULL; } + list_log(l, l->lv_first, NULL, "first"); return l->lv_first; } @@ -495,8 +591,10 @@ static inline listitem_T *tv_list_last(const list_T *const l) static inline listitem_T *tv_list_last(const list_T *const l) { if (l == NULL) { + list_log(l, NULL, NULL, "last"); return NULL; } + list_log(l, l->lv_last, NULL, "last"); return l->lv_last; } @@ -564,6 +662,7 @@ extern bool tv_in_free_unref_items; #define _TV_LIST_ITER_MOD(modifier, l, li, code) \ do { \ modifier list_T *const l_ = (l); \ + list_log(l_, NULL, NULL, "iter" #modifier); \ if (l_ != NULL) { \ for (modifier listitem_T *li = l_->lv_first; \ li != NULL; li = li->li_next) { \ diff --git a/src/nvim/memory.c b/src/nvim/memory.c index 328b96fd5c..a66ab6a3cc 100644 --- a/src/nvim/memory.c +++ b/src/nvim/memory.c @@ -559,6 +559,7 @@ void time_to_bytes(time_t time_, uint8_t buf[8]) #include "nvim/tag.h" #include "nvim/window.h" #include "nvim/os/os.h" +#include "nvim/eval/typval.h" /* * Free everything that we allocated. @@ -692,6 +693,7 @@ void free_all_mem(void) free_screenlines(); clear_hl_tables(); + list_free_log(); } #endif -- cgit From a8cb510a2ed2f53f60ba4b2e722f4bc64954c606 Mon Sep 17 00:00:00 2001 From: ZyX Date: Tue, 9 Jan 2018 12:39:15 +0300 Subject: channel: Make empty output be represented by `['']` again --- src/nvim/channel.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/nvim/channel.c b/src/nvim/channel.c index 9d0a7d3c21..265d4d8b89 100644 --- a/src/nvim/channel.c +++ b/src/nvim/channel.c @@ -533,6 +533,9 @@ static inline list_T *buffer_to_tv_list(const char *const buf, const size_t len) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_ALWAYS_INLINE { list_T *const l = tv_list_alloc(kListLenMayKnow); + // Empty buffer should be represented by [''], encode_list_write() thinks + // empty list is fine for the case. + tv_list_append_string(l, "", 0); encode_list_write(l, buf, len); return l; } -- cgit From 4b8d6caf48e28730767ae87a985a345ed6f0d2b3 Mon Sep 17 00:00:00 2001 From: ckelsel Date: Sun, 14 Jan 2018 20:50:05 +0800 Subject: vim-patch:8.0.0380: with 'linebreak' double wide char wraps badly Problem: With 'linebreak' set and 'breakat' includes ">" a double-wide character results in "<<" displayed. Solution: Check for the character not to be replaced. (Ozaki Kiichi, closes vim/vim#1456) https://github.com/vim/vim/commit/38632faf635f6434441827e136bceb5a930c59ad --- src/nvim/screen.c | 26 +++++++++++++------------- src/nvim/testdir/test_listlbr_utf8.vim | 27 +++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 13 deletions(-) diff --git a/src/nvim/screen.c b/src/nvim/screen.c index ed96e98d32..8a29734025 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -3143,14 +3143,15 @@ win_line ( } --n_extra; } else { + int c0; + if (p_extra_free != NULL) { xfree(p_extra_free); p_extra_free = NULL; } - /* - * Get a character from the line itself. - */ - c = *ptr; + + // Get a character from the line itself. + c0 = c = *ptr; if (has_mbyte) { mb_c = c; if (enc_utf8) { @@ -3160,11 +3161,12 @@ win_line ( mb_utf8 = FALSE; if (mb_l > 1) { mb_c = utfc_ptr2char(ptr, u8cc); - /* Overlong encoded ASCII or ASCII with composing char - * is displayed normally, except a NUL. */ - if (mb_c < 0x80) - c = mb_c; - mb_utf8 = TRUE; + // Overlong encoded ASCII or ASCII with composing char + // is displayed normally, except a NUL. + if (mb_c < 0x80) { + c0 = c = mb_c; + } + mb_utf8 = true; /* At start of the line we can have a composing char. * Draw it as a space with a composing char. */ @@ -3428,10 +3430,8 @@ win_line ( char_attr = hl_combine_attr(char_attr, term_attrs[vcol]); } - /* - * Found last space before word: check for line break. - */ - if (wp->w_p_lbr && vim_isbreak(c) && !vim_isbreak(*ptr)) { + // Found last space before word: check for line break. + if (wp->w_p_lbr && c0 == c && vim_isbreak(c) && !vim_isbreak(*ptr)) { int mb_off = has_mbyte ? (*mb_head_off)(line, ptr - 1) : 0; char_u *p = ptr - (mb_off + 1); // TODO: is passing p for start of the line OK? diff --git a/src/nvim/testdir/test_listlbr_utf8.vim b/src/nvim/testdir/test_listlbr_utf8.vim index 980d67d49d..56a4cc9b31 100644 --- a/src/nvim/testdir/test_listlbr_utf8.vim +++ b/src/nvim/testdir/test_listlbr_utf8.vim @@ -194,6 +194,33 @@ func Test_multibyte_sign_and_colorcolumn() call s:close_windows() endfunc +func Test_illegal_byte_and_breakat() + call s:test_windows("setl sbr= brk+=<") + vert resize 18 + call setline(1, repeat("\x80", 6)) + redraw! + let lines = s:screen_lines([1, 2], winwidth(0)) + let expect = [ +\ "<80><80><80><80><8", +\ "0><80> ", +\ ] + call s:compare_lines(expect, lines) + call s:close_windows('setl brk&vim') +endfunc + +func Test_multibyte_wrap_and_breakat() + call s:test_windows("setl sbr= brk+=>") + call setline(1, repeat('a', 17) . repeat('あ', 2)) + redraw! + let lines = s:screen_lines([1, 2], winwidth(0)) + let expect = [ +\ "aaaaaaaaaaaaaaaaaあ>", +\ "あ ", +\ ] + call s:compare_lines(expect, lines) + call s:close_windows('setl brk&vim') +endfunc + func Test_chinese_char_on_wrap_column() call s:test_windows("setl nolbr wrap sbr=") syntax off -- cgit From 7faeaf9f243a8b955b73960d3bba9a34fce3132d Mon Sep 17 00:00:00 2001 From: ckelsel Date: Sun, 14 Jan 2018 20:50:49 +0800 Subject: vim-patch:8.0.0381: diff mode is not sufficiently tested Problem: Diff mode is not sufficiently tested. Solution: Add more diff mode tests. (Dominique Pelle, closes vim/vim#1515) https://github.com/vim/vim/commit/aeb661e1f4a491286ef7af8c3105aff1f3b16f1c --- src/nvim/testdir/test_diffmode.vim | 75 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/src/nvim/testdir/test_diffmode.vim b/src/nvim/testdir/test_diffmode.vim index 8ee82bd538..c3d4be9cf9 100644 --- a/src/nvim/testdir/test_diffmode.vim +++ b/src/nvim/testdir/test_diffmode.vim @@ -272,3 +272,78 @@ func Test_setting_cursor() call delete('Xtest1') call delete('Xtest2') endfunc + +func Test_diff_move_to() + new + call setline(1, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) + diffthis + vnew + call setline(1, [1, '2x', 3, 4, 4, 5, '6x', 7, '8x', 9, '10x']) + diffthis + norm ]c + call assert_equal(2, line('.')) + norm 3]c + call assert_equal(9, line('.')) + norm 10]c + call assert_equal(11, line('.')) + norm [c + call assert_equal(9, line('.')) + norm 2[c + call assert_equal(5, line('.')) + norm 10[c + call assert_equal(2, line('.')) + %bwipe! +endfunc + +func Test_diffpatch() + " The patch program on MS-Windows may fail or hang. + if !executable('patch') || !has('unix') + return + endif + new + insert +*************** +*** 1,3 **** + 1 +! 2 + 3 +--- 1,4 ---- + 1 +! 2x + 3 ++ 4 +. + saveas Xpatch + bwipe! + new + call assert_fails('diffpatch Xpatch', 'E816:') + call setline(1, ['1', '2', '3']) + diffpatch Xpatch + call assert_equal(['1', '2x', '3', '4'], getline(1, '$')) + call delete('Xpatch') + bwipe! +endfunc + +func Test_diff_too_many_buffers() + for i in range(1, 8) + exe "new Xtest" . i + diffthis + endfor + new Xtest9 + call assert_fails('diffthis', 'E96:') + %bwipe! +endfunc + +func Test_diff_nomodifiable() + new + call setline(1, [1, 2, 3, 4]) + setl nomodifiable + diffthis + vnew + call setline(1, ['1x', 2, 3, 3, 4]) + diffthis + call assert_fails('norm dp', 'E793:') + setl nomodifiable + call assert_fails('norm do', 'E21:') + %bwipe! +endfunc -- cgit From fe4ba6995895f95f461d40eda356cc7e1dbb1abf Mon Sep 17 00:00:00 2001 From: ckelsel Date: Sun, 14 Jan 2018 20:52:51 +0800 Subject: vim-patch:8.0.0385: no tests for arabic Problem: No tests for arabic. Solution: Add a first test for arabic. (Dominique Pelle, closes vim/vim#1518) https://github.com/vim/vim/commit/b5e8377364110ee70090274da15d202778e96a64 --- src/nvim/testdir/Makefile | 1 + src/nvim/testdir/test_arabic.vim | 92 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+) create mode 100644 src/nvim/testdir/test_arabic.vim diff --git a/src/nvim/testdir/Makefile b/src/nvim/testdir/Makefile index a23dacba15..0c25945274 100644 --- a/src/nvim/testdir/Makefile +++ b/src/nvim/testdir/Makefile @@ -40,6 +40,7 @@ SCRIPTS ?= $(SCRIPTS_DEFAULT) # Tests using runtest.vim. # Keep test_alot*.res as the last one, sort the others. NEW_TESTS ?= \ + test_arabic.vim \ test_autocmd.res \ test_bufwintabinfo.res \ test_changedtick.res \ diff --git a/src/nvim/testdir/test_arabic.vim b/src/nvim/testdir/test_arabic.vim new file mode 100644 index 0000000000..b9e033462e --- /dev/null +++ b/src/nvim/testdir/test_arabic.vim @@ -0,0 +1,92 @@ +" Simplistic testing of Arabic mode. + +if !has('arabic') + finish +endif + +set encoding=utf-8 +scriptencoding utf-8 + +" Return list of utf8 sequences of each character at line lnum. +" Combining characters are treated as a single item. +func GetCharsUtf8(lnum) + call cursor(a:lnum, 1) + let chars = [] + let numchars = strchars(getline('.'), 1) + for i in range(1, numchars) + exe 'norm ' i . '|' + call add(chars, execute('norm g8')) + endfor + return chars +endfunc + +func Test_arabic_toggle() + set arabic + call assert_equal(1, &rightleft) + call assert_equal(1, &arabicshape) + call assert_equal('arabic', &keymap) + call assert_equal(1, &delcombine) + + set iminsert=1 imsearch=1 + set arabic& + call assert_equal(0, &rightleft) + call assert_equal(1, &arabicshape) + call assert_equal('arabic', &keymap) + call assert_equal(1, &delcombine) + call assert_equal(0, &iminsert) + call assert_equal(-1, &imsearch) + + set arabicshape& keymap= delcombine& +endfunc + +func Test_arabic_input() + new + set arabic + " Typing sghl in Arabic insert mode should show the + " Arabic word 'Salaam' i.e. 'peace'. + call feedkeys('isghl', 'tx') + redraw + call assert_equal([ + \ "\nd8 b3 ", + \ "\nd9 84 + d8 a7 ", + \ "\nd9 85 "], GetCharsUtf8(1)) + + " Without shaping, it should give individual Arabic letters. + set noarabicshape + redraw + call assert_equal([ + \ "\nd8 b3 ", + \ "\nd9 84 ", + \ "\nd8 a7 ", + \ "\nd9 85 "], GetCharsUtf8(1)) + + set arabicshape& + set arabic& + bwipe! +endfunc + +func Test_arabic_toggle_keymap() + new + set arabic + call feedkeys("i12\12\12", 'tx') + redraw + call assert_equal('١٢12١٢', getline('.')) + set arabic& + bwipe! +endfunc + +func Test_delcombine() + new + set arabic + call feedkeys("isghl\\", 'tx') + redraw + call assert_equal(["\nd8 b3 ", "\nd9 84 "], GetCharsUtf8(1)) + + " Now the same with nodelcombine + set nodelcombine + %d + call feedkeys("isghl\\", 'tx') + call assert_equal(["\nd8 b3 "], GetCharsUtf8(1)) + set arabic& + bwipe! +endfunc -- cgit From fc97e9fbdf372e52370ca8640422327d8a0c8cb5 Mon Sep 17 00:00:00 2001 From: ckelsel Date: Mon, 15 Jan 2018 17:32:54 +0800 Subject: vim-patch:8.0.0389: test for arabic does not check what is displayed Problem: Test for arabic does not check what is displayed. Solution: Improve what is asserted. (Dominique Pelle, closes vim/vim#1523) Add a first shaping test. https://github.com/vim/vim/commit/5342f00ff95ed0256b8183063a83d72112f1243c --- src/nvim/testdir/test_arabic.vim | 89 +++++++++++++++++++++++++++++----------- 1 file changed, 65 insertions(+), 24 deletions(-) diff --git a/src/nvim/testdir/test_arabic.vim b/src/nvim/testdir/test_arabic.vim index b9e033462e..9e648a9617 100644 --- a/src/nvim/testdir/test_arabic.vim +++ b/src/nvim/testdir/test_arabic.vim @@ -1,21 +1,23 @@ " Simplistic testing of Arabic mode. -if !has('arabic') +if !has('arabic') || !has('multi_byte') finish endif -set encoding=utf-8 -scriptencoding utf-8 +source view_util.vim -" Return list of utf8 sequences of each character at line lnum. +" Return list of Unicode characters at line lnum. " Combining characters are treated as a single item. -func GetCharsUtf8(lnum) +func s:get_chars(lnum) call cursor(a:lnum, 1) let chars = [] let numchars = strchars(getline('.'), 1) for i in range(1, numchars) exe 'norm ' i . '|' - call add(chars, execute('norm g8')) + let c=execute('ascii') + let c=substitute(c, '\n\?<.\{-}Hex\s*', 'U+', 'g') + let c=substitute(c, ',\s*Octal\s*\d*', '', 'g') + call add(chars, c) endfor return chars endfunc @@ -43,25 +45,28 @@ func Test_arabic_input() new set arabic " Typing sghl in Arabic insert mode should show the - " Arabic word 'Salaam' i.e. 'peace'. - call feedkeys('isghl', 'tx') - redraw + " Arabic word 'Salaam' i.e. 'peace', spelled: + " SEEN, LAM, ALEF, MEEM. + " See: https://www.mediawiki.org/wiki/VisualEditor/Typing/Right-to-left + call feedkeys('isghl!', 'tx') + call assert_match("^ *!\uFEE1\uFEFC\uFEB3$", ScreenLines(1, &columns)[0]) call assert_equal([ - \ "\nd8 b3 ", - \ "\nd9 84 + d8 a7 ", - \ "\nd9 85 "], GetCharsUtf8(1)) + \ 'U+0633', + \ 'U+0644 U+0627', + \ 'U+0645', + \ 'U+21'], s:get_chars(1)) " Without shaping, it should give individual Arabic letters. set noarabicshape - redraw + call assert_match("^ *!\u0645\u0627\u0644\u0633$", ScreenLines(1, &columns)[0]) call assert_equal([ - \ "\nd8 b3 ", - \ "\nd9 84 ", - \ "\nd8 a7 ", - \ "\nd9 85 "], GetCharsUtf8(1)) + \ 'U+0633', + \ 'U+0644', + \ 'U+0627', + \ 'U+0645', + \ 'U+21'], s:get_chars(1)) - set arabicshape& - set arabic& + set arabic& arabicshape& bwipe! endfunc @@ -69,7 +74,7 @@ func Test_arabic_toggle_keymap() new set arabic call feedkeys("i12\12\12", 'tx') - redraw + call assert_match("^ *٢١21٢١$", ScreenLines(1, &columns)[0]) call assert_equal('١٢12١٢', getline('.')) set arabic& bwipe! @@ -79,14 +84,50 @@ func Test_delcombine() new set arabic call feedkeys("isghl\\", 'tx') - redraw - call assert_equal(["\nd8 b3 ", "\nd9 84 "], GetCharsUtf8(1)) + call assert_match("^ *\uFEDE\uFEB3$", ScreenLines(1, &columns)[0]) + call assert_equal(['U+0633', 'U+0644'], s:get_chars(1)) - " Now the same with nodelcombine + " Now the same with 'nodelcombine' set nodelcombine %d call feedkeys("isghl\\", 'tx') - call assert_equal(["\nd8 b3 "], GetCharsUtf8(1)) + call assert_match("^ *\uFEB1$", ScreenLines(1, &columns)[0]) + call assert_equal(['U+0633'], s:get_chars(1)) set arabic& bwipe! endfunc + +let s:a_YEH_HAMZA = "\u0626" +let s:a_i_YEH_HAMZA = "\ufe8b" + +let s:a_HAMZA = "\u0621" +let s:a_s_HAMZA = "\ufe80" + +let s:a_ALEF_MADDA = "\u0622" +let s:a_s_ALEF_MADDA = "\ufe81" + +let s:a_ALEF_HAMZA_ABOVE = "\u0623" +let s:a_s_ALEF_HAMZA_ABOVE = "\ufe83" + +let s:a_GHAIN = "\u063a" +let s:a_f_GHAIN = "\ufece" +let s:a_s_GHAIN = "\ufecd" + +func Test_shape_initial() + new + set arabicshape + + " Shaping arabic {testchar} non-arabic Uses chg_c_a2i(). + " pair[0] = testchar, pair[1] = next-result, pair[2] = current-result + for pair in [[s:a_YEH_HAMZA, s:a_f_GHAIN, s:a_i_YEH_HAMZA], + \ [s:a_HAMZA, s:a_s_GHAIN, s:a_s_HAMZA], + \ [s:a_ALEF_MADDA, s:a_s_GHAIN, s:a_s_ALEF_MADDA], + \ [s:a_ALEF_HAMZA_ABOVE, s:a_s_GHAIN, s:a_s_ALEF_HAMZA_ABOVE], + \ ] + call setline(1, s:a_GHAIN . pair[0] . ' ') + call assert_equal([pair[1] . pair[2] . ' '], ScreenLines(1, 3)) + endfor + + set arabicshape& + bwipe! +endfunc -- cgit From 770ec228c7e47c63ba549e67bfdaae9aa574ecce Mon Sep 17 00:00:00 2001 From: ckelsel Date: Mon, 15 Jan 2018 17:33:34 +0800 Subject: vim-patch:8.0.0391: arabic support is verbose and not well tested Problem: Arabic support is verbose and not well tested. Solution: Simplify the code. Add more tests. https://github.com/vim/vim/commit/5f53dd3f747711be90879fa2f22a207970b86750 --- src/nvim/arabic.c | 382 ++++++++------------------------------- src/nvim/testdir/test_arabic.vim | 359 +++++++++++++++++++++++++++++++++++- 2 files changed, 425 insertions(+), 316 deletions(-) diff --git a/src/nvim/arabic.c b/src/nvim/arabic.c index 1ef51d2a2a..f0370af704 100644 --- a/src/nvim/arabic.c +++ b/src/nvim/arabic.c @@ -393,323 +393,93 @@ static bool A_is_f(int cur_c) // Change shape - from ISO-8859-6/Isolated to Form-B Isolated static int chg_c_a2s(int cur_c) { - int tempc; - switch (cur_c) { - case a_HAMZA: - tempc = a_s_HAMZA; - break; - - case a_ALEF_MADDA: - tempc = a_s_ALEF_MADDA; - break; - - case a_ALEF_HAMZA_ABOVE: - tempc = a_s_ALEF_HAMZA_ABOVE; - break; - - case a_WAW_HAMZA: - tempc = a_s_WAW_HAMZA; - break; - - case a_ALEF_HAMZA_BELOW: - tempc = a_s_ALEF_HAMZA_BELOW; - break; - - case a_YEH_HAMZA: - tempc = a_s_YEH_HAMZA; - break; - - case a_ALEF: - tempc = a_s_ALEF; - break; - - case a_TEH_MARBUTA: - tempc = a_s_TEH_MARBUTA; - break; - - case a_DAL: - tempc = a_s_DAL; - break; - - case a_THAL: - tempc = a_s_THAL; - break; - - case a_REH: - tempc = a_s_REH; - break; - - case a_ZAIN: - tempc = a_s_ZAIN; - break; - - case a_TATWEEL: // exceptions - tempc = cur_c; - break; - - case a_WAW: - tempc = a_s_WAW; - break; - - case a_ALEF_MAKSURA: - tempc = a_s_ALEF_MAKSURA; - break; - - case a_BEH: - tempc = a_s_BEH; - break; - - case a_TEH: - tempc = a_s_TEH; - break; - - case a_THEH: - tempc = a_s_THEH; - break; - - case a_JEEM: - tempc = a_s_JEEM; - break; - - case a_HAH: - tempc = a_s_HAH; - break; - - case a_KHAH: - tempc = a_s_KHAH; - break; - - case a_SEEN: - tempc = a_s_SEEN; - break; - - case a_SHEEN: - tempc = a_s_SHEEN; - break; - - case a_SAD: - tempc = a_s_SAD; - break; - - case a_DAD: - tempc = a_s_DAD; - break; - - case a_TAH: - tempc = a_s_TAH; - break; - - case a_ZAH: - tempc = a_s_ZAH; - break; - - case a_AIN: - tempc = a_s_AIN; - break; - - case a_GHAIN: - tempc = a_s_GHAIN; - break; - - case a_FEH: - tempc = a_s_FEH; - break; - - case a_QAF: - tempc = a_s_QAF; - break; - - case a_KAF: - tempc = a_s_KAF; - break; - - case a_LAM: - tempc = a_s_LAM; - break; - - case a_MEEM: - tempc = a_s_MEEM; - break; - - case a_NOON: - tempc = a_s_NOON; - break; - - case a_HEH: - tempc = a_s_HEH; - break; - - case a_YEH: - tempc = a_s_YEH; - break; - - default: - tempc = 0; + case a_HAMZA: return a_s_HAMZA; + case a_ALEF_MADDA: return a_s_ALEF_MADDA; + case a_ALEF_HAMZA_ABOVE: return a_s_ALEF_HAMZA_ABOVE; + case a_WAW_HAMZA: return a_s_WAW_HAMZA; + case a_ALEF_HAMZA_BELOW: return a_s_ALEF_HAMZA_BELOW; + case a_YEH_HAMZA: return a_s_YEH_HAMZA; + case a_ALEF: return a_s_ALEF; + case a_TEH_MARBUTA: return a_s_TEH_MARBUTA; + case a_DAL: return a_s_DAL; + case a_THAL: return a_s_THAL; + case a_REH: return a_s_REH; + case a_ZAIN: return a_s_ZAIN; + case a_TATWEEL: return cur_c; // exceptions + case a_WAW: return a_s_WAW; + case a_ALEF_MAKSURA: return a_s_ALEF_MAKSURA; + case a_BEH: return a_s_BEH; + case a_TEH: return a_s_TEH; + case a_THEH: return a_s_THEH; + case a_JEEM: return a_s_JEEM; + case a_HAH: return a_s_HAH; + case a_KHAH: return a_s_KHAH; + case a_SEEN: return a_s_SEEN; + case a_SHEEN: return a_s_SHEEN; + case a_SAD: return a_s_SAD; + case a_DAD: return a_s_DAD; + case a_TAH: return a_s_TAH; + case a_ZAH: return a_s_ZAH; + case a_AIN: return a_s_AIN; + case a_GHAIN: return a_s_GHAIN; + case a_FEH: return a_s_FEH; + case a_QAF: return a_s_QAF; + case a_KAF: return a_s_KAF; + case a_LAM: return a_s_LAM; + case a_MEEM: return a_s_MEEM; + case a_NOON: return a_s_NOON; + case a_HEH: return a_s_HEH; + case a_YEH: return a_s_YEH; } - return tempc; + return 0; } // Change shape - from ISO-8859-6/Isolated to Initial static int chg_c_a2i(int cur_c) { - int tempc; - switch (cur_c) { - case a_YEH_HAMZA: - tempc = a_i_YEH_HAMZA; - break; - - case a_HAMZA: // exceptions - tempc = a_s_HAMZA; - break; - - case a_ALEF_MADDA: // exceptions - tempc = a_s_ALEF_MADDA; - break; - - case a_ALEF_HAMZA_ABOVE: // exceptions - tempc = a_s_ALEF_HAMZA_ABOVE; - break; - - case a_WAW_HAMZA: // exceptions - tempc = a_s_WAW_HAMZA; - break; - - case a_ALEF_HAMZA_BELOW: // exceptions - tempc = a_s_ALEF_HAMZA_BELOW; - break; - - case a_ALEF: // exceptions - tempc = a_s_ALEF; - break; - - case a_TEH_MARBUTA: // exceptions - tempc = a_s_TEH_MARBUTA; - break; - - case a_DAL: // exceptions - tempc = a_s_DAL; - break; - - case a_THAL: // exceptions - tempc = a_s_THAL; - break; - - case a_REH: // exceptions - tempc = a_s_REH; - break; - - case a_ZAIN: // exceptions - tempc = a_s_ZAIN; - break; - - case a_TATWEEL: // exceptions - tempc = cur_c; - break; - - case a_WAW: // exceptions - tempc = a_s_WAW; - break; - - case a_ALEF_MAKSURA: // exceptions - tempc = a_s_ALEF_MAKSURA; - break; - - case a_BEH: - tempc = a_i_BEH; - break; - - case a_TEH: - tempc = a_i_TEH; - break; - - case a_THEH: - tempc = a_i_THEH; - break; - - case a_JEEM: - tempc = a_i_JEEM; - break; - - case a_HAH: - tempc = a_i_HAH; - break; - - case a_KHAH: - tempc = a_i_KHAH; - break; - - case a_SEEN: - tempc = a_i_SEEN; - break; - - case a_SHEEN: - tempc = a_i_SHEEN; - break; - - case a_SAD: - tempc = a_i_SAD; - break; - - case a_DAD: - tempc = a_i_DAD; - break; - - case a_TAH: - tempc = a_i_TAH; - break; - - case a_ZAH: - tempc = a_i_ZAH; - break; - - case a_AIN: - tempc = a_i_AIN; - break; - - case a_GHAIN: - tempc = a_i_GHAIN; - break; - - case a_FEH: - tempc = a_i_FEH; - break; - - case a_QAF: - tempc = a_i_QAF; - break; - - case a_KAF: - tempc = a_i_KAF; - break; - - case a_LAM: - tempc = a_i_LAM; - break; - - case a_MEEM: - tempc = a_i_MEEM; - break; - - case a_NOON: - tempc = a_i_NOON; - break; - - case a_HEH: - tempc = a_i_HEH; - break; - - case a_YEH: - tempc = a_i_YEH; - break; - - default: - tempc = 0; + case a_YEH_HAMZA: return a_i_YEH_HAMZA; + case a_HAMZA: return a_s_HAMZA; // exceptions + case a_ALEF_MADDA: return a_s_ALEF_MADDA; // exceptions + case a_ALEF_HAMZA_ABOVE: return a_s_ALEF_HAMZA_ABOVE; // exceptions + case a_WAW_HAMZA: return a_s_WAW_HAMZA; // exceptions + case a_ALEF_HAMZA_BELOW: return a_s_ALEF_HAMZA_BELOW; // exceptions + case a_ALEF: return a_s_ALEF; // exceptions + case a_TEH_MARBUTA: return a_s_TEH_MARBUTA; // exceptions + case a_DAL: return a_s_DAL; // exceptions + case a_THAL: return a_s_THAL; // exceptions + case a_REH: return a_s_REH; // exceptions + case a_ZAIN: return a_s_ZAIN; // exceptions + case a_TATWEEL: return cur_c; // exceptions + case a_WAW: return a_s_WAW; // exceptions + case a_ALEF_MAKSURA: return a_s_ALEF_MAKSURA; // exceptions + case a_BEH: return a_i_BEH; + case a_TEH: return a_i_TEH; + case a_THEH: return a_i_THEH; + case a_JEEM: return a_i_JEEM; + case a_HAH: return a_i_HAH; + case a_KHAH: return a_i_KHAH; + case a_SEEN: return a_i_SEEN; + case a_SHEEN: return a_i_SHEEN; + case a_SAD: return a_i_SAD; + case a_DAD: return a_i_DAD; + case a_TAH: return a_i_TAH; + case a_ZAH: return a_i_ZAH; + case a_AIN: return a_i_AIN; + case a_GHAIN: return a_i_GHAIN; + case a_FEH: return a_i_FEH; + case a_QAF: return a_i_QAF; + case a_KAF: return a_i_KAF; + case a_LAM: return a_i_LAM; + case a_MEEM: return a_i_MEEM; + case a_NOON: return a_i_NOON; + case a_HEH: return a_i_HEH; + case a_YEH: return a_i_YEH; } - return tempc; + return 0; } // Change shape - from ISO-8859-6/Isolated to Medial diff --git a/src/nvim/testdir/test_arabic.vim b/src/nvim/testdir/test_arabic.vim index 9e648a9617..9a16833a4c 100644 --- a/src/nvim/testdir/test_arabic.vim +++ b/src/nvim/testdir/test_arabic.vim @@ -97,32 +97,265 @@ func Test_delcombine() bwipe! endfunc +" Values from src/arabic.h (not all used yet) +let s:a_COMMA = "\u060C" +let s:a_SEMICOLON = "\u061B" +let s:a_QUESTION = "\u061F" +let s:a_HAMZA = "\u0621" +let s:a_ALEF_MADDA = "\u0622" +let s:a_ALEF_HAMZA_ABOVE = "\u0623" +let s:a_WAW_HAMZA = "\u0624" +let s:a_ALEF_HAMZA_BELOW = "\u0625" let s:a_YEH_HAMZA = "\u0626" -let s:a_i_YEH_HAMZA = "\ufe8b" +let s:a_ALEF = "\u0627" +let s:a_BEH = "\u0628" +let s:a_TEH_MARBUTA = "\u0629" +let s:a_TEH = "\u062a" +let s:a_THEH = "\u062b" +let s:a_JEEM = "\u062c" +let s:a_HAH = "\u062d" +let s:a_KHAH = "\u062e" +let s:a_DAL = "\u062f" +let s:a_THAL = "\u0630" +let s:a_REH = "\u0631" +let s:a_ZAIN = "\u0632" +let s:a_SEEN = "\u0633" +let s:a_SHEEN = "\u0634" +let s:a_SAD = "\u0635" +let s:a_DAD = "\u0636" +let s:a_TAH = "\u0637" +let s:a_ZAH = "\u0638" +let s:a_AIN = "\u0639" +let s:a_GHAIN = "\u063a" +let s:a_TATWEEL = "\u0640" +let s:a_FEH = "\u0641" +let s:a_QAF = "\u0642" +let s:a_KAF = "\u0643" +let s:a_LAM = "\u0644" +let s:a_MEEM = "\u0645" +let s:a_NOON = "\u0646" +let s:a_HEH = "\u0647" +let s:a_WAW = "\u0648" +let s:a_ALEF_MAKSURA = "\u0649" +let s:a_YEH = "\u064a" -let s:a_HAMZA = "\u0621" -let s:a_s_HAMZA = "\ufe80" +let s:a_FATHATAN = "\u064b" +let s:a_DAMMATAN = "\u064c" +let s:a_KASRATAN = "\u064d" +let s:a_FATHA = "\u064e" +let s:a_DAMMA = "\u064f" +let s:a_KASRA = "\u0650" +let s:a_SHADDA = "\u0651" +let s:a_SUKUN = "\u0652" -let s:a_ALEF_MADDA = "\u0622" -let s:a_s_ALEF_MADDA = "\ufe81" +let s:a_MADDA_ABOVE = "\u0653" +let s:a_HAMZA_ABOVE = "\u0654" +let s:a_HAMZA_BELOW = "\u0655" -let s:a_ALEF_HAMZA_ABOVE = "\u0623" -let s:a_s_ALEF_HAMZA_ABOVE = "\ufe83" +let s:a_ZERO = "\u0660" +let s:a_ONE = "\u0661" +let s:a_TWO = "\u0662" +let s:a_THREE = "\u0663" +let s:a_FOUR = "\u0664" +let s:a_FIVE = "\u0665" +let s:a_SIX = "\u0666" +let s:a_SEVEN = "\u0667" +let s:a_EIGHT = "\u0668" +let s:a_NINE = "\u0669" +let s:a_PERCENT = "\u066a" +let s:a_DECIMAL = "\u066b" +let s:a_THOUSANDS = "\u066c" +let s:a_STAR = "\u066d" +let s:a_MINI_ALEF = "\u0670" -let s:a_GHAIN = "\u063a" -let s:a_f_GHAIN = "\ufece" +let s:a_s_FATHATAN = "\ufe70" +let s:a_m_TATWEEL_FATHATAN = "\ufe71" +let s:a_s_DAMMATAN = "\ufe72" + +let s:a_s_KASRATAN = "\ufe74" + +let s:a_s_FATHA = "\ufe76" +let s:a_m_FATHA = "\ufe77" +let s:a_s_DAMMA = "\ufe78" +let s:a_m_DAMMA = "\ufe79" +let s:a_s_KASRA = "\ufe7a" +let s:a_m_KASRA = "\ufe7b" +let s:a_s_SHADDA = "\ufe7c" +let s:a_m_SHADDA = "\ufe7d" +let s:a_s_SUKUN = "\ufe7e" +let s:a_m_SUKUN = "\ufe7f" + +let s:a_s_HAMZA = "\ufe80" +let s:a_s_ALEF_MADDA = "\ufe81" +let s:a_f_ALEF_MADDA = "\ufe82" +let s:a_s_ALEF_HAMZA_ABOVE = "\ufe83" +let s:a_f_ALEF_HAMZA_ABOVE = "\ufe84" +let s:a_s_WAW_HAMZA = "\ufe85" +let s:a_f_WAW_HAMZA = "\ufe86" +let s:a_s_ALEF_HAMZA_BELOW = "\ufe87" +let s:a_f_ALEF_HAMZA_BELOW = "\ufe88" +let s:a_s_YEH_HAMZA = "\ufe89" +let s:a_f_YEH_HAMZA = "\ufe8a" +let s:a_i_YEH_HAMZA = "\ufe8b" +let s:a_m_YEH_HAMZA = "\ufe8c" +let s:a_s_ALEF = "\ufe8d" +let s:a_f_ALEF = "\ufe8e" +let s:a_s_BEH = "\ufe8f" +let s:a_f_BEH = "\ufe90" +let s:a_i_BEH = "\ufe91" +let s:a_m_BEH = "\ufe92" +let s:a_s_TEH_MARBUTA = "\ufe93" +let s:a_f_TEH_MARBUTA = "\ufe94" +let s:a_s_TEH = "\ufe95" +let s:a_f_TEH = "\ufe96" +let s:a_i_TEH = "\ufe97" +let s:a_m_TEH = "\ufe98" +let s:a_s_THEH = "\ufe99" +let s:a_f_THEH = "\ufe9a" +let s:a_i_THEH = "\ufe9b" +let s:a_m_THEH = "\ufe9c" +let s:a_s_JEEM = "\ufe9d" +let s:a_f_JEEM = "\ufe9e" +let s:a_i_JEEM = "\ufe9f" +let s:a_m_JEEM = "\ufea0" +let s:a_s_HAH = "\ufea1" +let s:a_f_HAH = "\ufea2" +let s:a_i_HAH = "\ufea3" +let s:a_m_HAH = "\ufea4" +let s:a_s_KHAH = "\ufea5" +let s:a_f_KHAH = "\ufea6" +let s:a_i_KHAH = "\ufea7" +let s:a_m_KHAH = "\ufea8" +let s:a_s_DAL = "\ufea9" +let s:a_f_DAL = "\ufeaa" +let s:a_s_THAL = "\ufeab" +let s:a_f_THAL = "\ufeac" +let s:a_s_REH = "\ufead" +let s:a_f_REH = "\ufeae" +let s:a_s_ZAIN = "\ufeaf" +let s:a_f_ZAIN = "\ufeb0" +let s:a_s_SEEN = "\ufeb1" +let s:a_f_SEEN = "\ufeb2" +let s:a_i_SEEN = "\ufeb3" +let s:a_m_SEEN = "\ufeb4" +let s:a_s_SHEEN = "\ufeb5" +let s:a_f_SHEEN = "\ufeb6" +let s:a_i_SHEEN = "\ufeb7" +let s:a_m_SHEEN = "\ufeb8" +let s:a_s_SAD = "\ufeb9" +let s:a_f_SAD = "\ufeba" +let s:a_i_SAD = "\ufebb" +let s:a_m_SAD = "\ufebc" +let s:a_s_DAD = "\ufebd" +let s:a_f_DAD = "\ufebe" +let s:a_i_DAD = "\ufebf" +let s:a_m_DAD = "\ufec0" +let s:a_s_TAH = "\ufec1" +let s:a_f_TAH = "\ufec2" +let s:a_i_TAH = "\ufec3" +let s:a_m_TAH = "\ufec4" +let s:a_s_ZAH = "\ufec5" +let s:a_f_ZAH = "\ufec6" +let s:a_i_ZAH = "\ufec7" +let s:a_m_ZAH = "\ufec8" +let s:a_s_AIN = "\ufec9" +let s:a_f_AIN = "\ufeca" +let s:a_i_AIN = "\ufecb" +let s:a_m_AIN = "\ufecc" let s:a_s_GHAIN = "\ufecd" +let s:a_f_GHAIN = "\ufece" +let s:a_i_GHAIN = "\ufecf" +let s:a_m_GHAIN = "\ufed0" +let s:a_s_FEH = "\ufed1" +let s:a_f_FEH = "\ufed2" +let s:a_i_FEH = "\ufed3" +let s:a_m_FEH = "\ufed4" +let s:a_s_QAF = "\ufed5" +let s:a_f_QAF = "\ufed6" +let s:a_i_QAF = "\ufed7" +let s:a_m_QAF = "\ufed8" +let s:a_s_KAF = "\ufed9" +let s:a_f_KAF = "\ufeda" +let s:a_i_KAF = "\ufedb" +let s:a_m_KAF = "\ufedc" +let s:a_s_LAM = "\ufedd" +let s:a_f_LAM = "\ufede" +let s:a_i_LAM = "\ufedf" +let s:a_m_LAM = "\ufee0" +let s:a_s_MEEM = "\ufee1" +let s:a_f_MEEM = "\ufee2" +let s:a_i_MEEM = "\ufee3" +let s:a_m_MEEM = "\ufee4" +let s:a_s_NOON = "\ufee5" +let s:a_f_NOON = "\ufee6" +let s:a_i_NOON = "\ufee7" +let s:a_m_NOON = "\ufee8" +let s:a_s_HEH = "\ufee9" +let s:a_f_HEH = "\ufeea" +let s:a_i_HEH = "\ufeeb" +let s:a_m_HEH = "\ufeec" +let s:a_s_WAW = "\ufeed" +let s:a_f_WAW = "\ufeee" +let s:a_s_ALEF_MAKSURA = "\ufeef" +let s:a_f_ALEF_MAKSURA = "\ufef0" +let s:a_s_YEH = "\ufef1" +let s:a_f_YEH = "\ufef2" +let s:a_i_YEH = "\ufef3" +let s:a_m_YEH = "\ufef4" +let s:a_s_LAM_ALEF_MADDA_ABOVE = "\ufef5" +let s:a_f_LAM_ALEF_MADDA_ABOVE = "\ufef6" +let s:a_s_LAM_ALEF_HAMZA_ABOVE = "\ufef7" +let s:a_f_LAM_ALEF_HAMZA_ABOVE = "\ufef8" +let s:a_s_LAM_ALEF_HAMZA_BELOW = "\ufef9" +let s:a_f_LAM_ALEF_HAMZA_BELOW = "\ufefa" +let s:a_s_LAM_ALEF = "\ufefb" +let s:a_f_LAM_ALEF = "\ufefc" + +let s:a_BYTE_ORDER_MARK = "\ufeff" func Test_shape_initial() new set arabicshape - " Shaping arabic {testchar} non-arabic Uses chg_c_a2i(). + " Shaping arabic {testchar} non-arabic Tests chg_c_a2i(). " pair[0] = testchar, pair[1] = next-result, pair[2] = current-result for pair in [[s:a_YEH_HAMZA, s:a_f_GHAIN, s:a_i_YEH_HAMZA], \ [s:a_HAMZA, s:a_s_GHAIN, s:a_s_HAMZA], \ [s:a_ALEF_MADDA, s:a_s_GHAIN, s:a_s_ALEF_MADDA], \ [s:a_ALEF_HAMZA_ABOVE, s:a_s_GHAIN, s:a_s_ALEF_HAMZA_ABOVE], + \ [s:a_WAW_HAMZA, s:a_s_GHAIN, s:a_s_WAW_HAMZA], + \ [s:a_ALEF_HAMZA_BELOW, s:a_s_GHAIN, s:a_s_ALEF_HAMZA_BELOW], + \ [s:a_ALEF, s:a_s_GHAIN, s:a_s_ALEF], + \ [s:a_TEH_MARBUTA, s:a_s_GHAIN, s:a_s_TEH_MARBUTA], + \ [s:a_DAL, s:a_s_GHAIN, s:a_s_DAL], + \ [s:a_THAL, s:a_s_GHAIN, s:a_s_THAL], + \ [s:a_REH, s:a_s_GHAIN, s:a_s_REH], + \ [s:a_ZAIN, s:a_s_GHAIN, s:a_s_ZAIN], + \ [s:a_TATWEEL, s:a_f_GHAIN, s:a_TATWEEL], + \ [s:a_WAW, s:a_s_GHAIN, s:a_s_WAW], + \ [s:a_ALEF_MAKSURA, s:a_s_GHAIN, s:a_s_ALEF_MAKSURA], + \ [s:a_BEH, s:a_f_GHAIN, s:a_i_BEH], + \ [s:a_TEH, s:a_f_GHAIN, s:a_i_TEH], + \ [s:a_THEH, s:a_f_GHAIN, s:a_i_THEH], + \ [s:a_JEEM, s:a_f_GHAIN, s:a_i_JEEM], + \ [s:a_HAH, s:a_f_GHAIN, s:a_i_HAH], + \ [s:a_KHAH, s:a_f_GHAIN, s:a_i_KHAH], + \ [s:a_SEEN, s:a_f_GHAIN, s:a_i_SEEN], + \ [s:a_SHEEN, s:a_f_GHAIN, s:a_i_SHEEN], + \ [s:a_SAD, s:a_f_GHAIN, s:a_i_SAD], + \ [s:a_DAD, s:a_f_GHAIN, s:a_i_DAD], + \ [s:a_TAH, s:a_f_GHAIN, s:a_i_TAH], + \ [s:a_ZAH, s:a_f_GHAIN, s:a_i_ZAH], + \ [s:a_AIN, s:a_f_GHAIN, s:a_i_AIN], + \ [s:a_GHAIN, s:a_f_GHAIN, s:a_i_GHAIN], + \ [s:a_FEH, s:a_f_GHAIN, s:a_i_FEH], + \ [s:a_QAF, s:a_f_GHAIN, s:a_i_QAF], + \ [s:a_KAF, s:a_f_GHAIN, s:a_i_KAF], + \ [s:a_LAM, s:a_f_GHAIN, s:a_i_LAM], + \ [s:a_MEEM, s:a_f_GHAIN, s:a_i_MEEM], + \ [s:a_NOON, s:a_f_GHAIN, s:a_i_NOON], + \ [s:a_HEH, s:a_f_GHAIN, s:a_i_HEH], + \ [s:a_YEH, s:a_f_GHAIN, s:a_i_YEH], \ ] call setline(1, s:a_GHAIN . pair[0] . ' ') call assert_equal([pair[1] . pair[2] . ' '], ScreenLines(1, 3)) @@ -131,3 +364,109 @@ func Test_shape_initial() set arabicshape& bwipe! endfunc + +func Test_shape_isolated() + new + set arabicshape + + " Shaping non-arabic {testchar} non-arabic Tests chg_c_a2s(). + " pair[0] = testchar, pair[1] = current-result + for pair in [[s:a_HAMZA, s:a_s_HAMZA], + \ [s:a_ALEF_MADDA, s:a_s_ALEF_MADDA], + \ [s:a_ALEF_HAMZA_ABOVE, s:a_s_ALEF_HAMZA_ABOVE], + \ [s:a_WAW_HAMZA, s:a_s_WAW_HAMZA], + \ [s:a_ALEF_HAMZA_BELOW, s:a_s_ALEF_HAMZA_BELOW], + \ [s:a_YEH_HAMZA, s:a_s_YEH_HAMZA], + \ [s:a_ALEF, s:a_s_ALEF], + \ [s:a_TEH_MARBUTA, s:a_s_TEH_MARBUTA], + \ [s:a_DAL, s:a_s_DAL], + \ [s:a_THAL, s:a_s_THAL], + \ [s:a_REH, s:a_s_REH], + \ [s:a_ZAIN, s:a_s_ZAIN], + \ [s:a_TATWEEL, s:a_TATWEEL], + \ [s:a_WAW, s:a_s_WAW], + \ [s:a_ALEF_MAKSURA, s:a_s_ALEF_MAKSURA], + \ [s:a_BEH, s:a_s_BEH], + \ [s:a_TEH, s:a_s_TEH], + \ [s:a_THEH, s:a_s_THEH], + \ [s:a_JEEM, s:a_s_JEEM], + \ [s:a_HAH, s:a_s_HAH], + \ [s:a_KHAH, s:a_s_KHAH], + \ [s:a_SEEN, s:a_s_SEEN], + \ [s:a_SHEEN, s:a_s_SHEEN], + \ [s:a_SAD, s:a_s_SAD], + \ [s:a_DAD, s:a_s_DAD], + \ [s:a_TAH, s:a_s_TAH], + \ [s:a_ZAH, s:a_s_ZAH], + \ [s:a_AIN, s:a_s_AIN], + \ [s:a_GHAIN, s:a_s_GHAIN], + \ [s:a_FEH, s:a_s_FEH], + \ [s:a_QAF, s:a_s_QAF], + \ [s:a_KAF, s:a_s_KAF], + \ [s:a_LAM, s:a_s_LAM], + \ [s:a_MEEM, s:a_s_MEEM], + \ [s:a_NOON, s:a_s_NOON], + \ [s:a_HEH, s:a_s_HEH], + \ [s:a_YEH, s:a_s_YEH], + \ ] + call setline(1, ' ' . pair[0] . ' ') + call assert_equal([' ' . pair[1] . ' '], ScreenLines(1, 3)) + endfor + + set arabicshape& + bwipe! +endfunc + +func Test_shape_medial() + new + set arabicshape + + " Shaping arabic {testchar} arabic Tests chg_c_a2m(). + " pair[0] = testchar, pair[1] = next-result, pair[2] = current-result, + " pair[3] = previous-result + for pair in [[s:a_HAMZA, s:a_s_GHAIN, s:a_s_HAMZA, s:a_s_BEH], + \[s:a_ALEF_MADDA, s:a_s_GHAIN, s:a_f_ALEF_MADDA, s:a_i_BEH], + \[s:a_ALEF_HAMZA_ABOVE, s:a_s_GHAIN, s:a_f_ALEF_HAMZA_ABOVE, s:a_i_BEH], + \[s:a_WAW_HAMZA, s:a_s_GHAIN, s:a_f_WAW_HAMZA, s:a_i_BEH], + \[s:a_ALEF_HAMZA_BELOW, s:a_s_GHAIN, s:a_f_ALEF_HAMZA_BELOW, s:a_i_BEH], + \[s:a_YEH_HAMZA, s:a_f_GHAIN, s:a_m_YEH_HAMZA, s:a_i_BEH], + \[s:a_ALEF, s:a_s_GHAIN, s:a_f_ALEF, s:a_i_BEH], + \[s:a_BEH, s:a_f_GHAIN, s:a_m_BEH, s:a_i_BEH], + \[s:a_TEH_MARBUTA, s:a_s_GHAIN, s:a_f_TEH_MARBUTA, s:a_i_BEH], + \[s:a_TEH, s:a_f_GHAIN, s:a_m_TEH, s:a_i_BEH], + \[s:a_THEH, s:a_f_GHAIN, s:a_m_THEH, s:a_i_BEH], + \[s:a_JEEM, s:a_f_GHAIN, s:a_m_JEEM, s:a_i_BEH], + \[s:a_HAH, s:a_f_GHAIN, s:a_m_HAH, s:a_i_BEH], + \[s:a_KHAH, s:a_f_GHAIN, s:a_m_KHAH, s:a_i_BEH], + \[s:a_DAL, s:a_s_GHAIN, s:a_f_DAL, s:a_i_BEH], + \[s:a_THAL, s:a_s_GHAIN, s:a_f_THAL, s:a_i_BEH], + \[s:a_REH, s:a_s_GHAIN, s:a_f_REH, s:a_i_BEH], + \[s:a_ZAIN, s:a_s_GHAIN, s:a_f_ZAIN, s:a_i_BEH], + \[s:a_SEEN, s:a_f_GHAIN, s:a_m_SEEN, s:a_i_BEH], + \[s:a_SHEEN, s:a_f_GHAIN, s:a_m_SHEEN, s:a_i_BEH], + \[s:a_SAD, s:a_f_GHAIN, s:a_m_SAD, s:a_i_BEH], + \[s:a_DAD, s:a_f_GHAIN, s:a_m_DAD, s:a_i_BEH], + \[s:a_TAH, s:a_f_GHAIN, s:a_m_TAH, s:a_i_BEH], + \[s:a_ZAH, s:a_f_GHAIN, s:a_m_ZAH, s:a_i_BEH], + \[s:a_AIN, s:a_f_GHAIN, s:a_m_AIN, s:a_i_BEH], + \[s:a_GHAIN, s:a_f_GHAIN, s:a_m_GHAIN, s:a_i_BEH], + \[s:a_TATWEEL, s:a_f_GHAIN, s:a_TATWEEL, s:a_i_BEH], + \[s:a_FEH, s:a_f_GHAIN, s:a_m_FEH, s:a_i_BEH], + \[s:a_QAF, s:a_f_GHAIN, s:a_m_QAF, s:a_i_BEH], + \[s:a_KAF, s:a_f_GHAIN, s:a_m_KAF, s:a_i_BEH], + \[s:a_LAM, s:a_f_GHAIN, s:a_m_LAM, s:a_i_BEH], + \[s:a_MEEM, s:a_f_GHAIN, s:a_m_MEEM, s:a_i_BEH], + \[s:a_NOON, s:a_f_GHAIN, s:a_m_NOON, s:a_i_BEH], + \[s:a_HEH, s:a_f_GHAIN, s:a_m_HEH, s:a_i_BEH], + \[s:a_WAW, s:a_s_GHAIN, s:a_f_WAW, s:a_i_BEH], + \[s:a_ALEF_MAKSURA, s:a_s_GHAIN, s:a_f_ALEF_MAKSURA, s:a_i_BEH], + \[s:a_YEH, s:a_f_GHAIN, s:a_m_YEH, s:a_i_BEH], + \ ] + call setline(1, s:a_GHAIN . pair[0] . s:a_BEH) + call assert_equal([pair[1] . pair[2] . pair[3]], ScreenLines(1, 3)) + endfor + + set arabicshape& + bwipe! +endfunc + -- cgit From b9f3805447f9e0ff277aa655dfb9513f41db5ce6 Mon Sep 17 00:00:00 2001 From: ckelsel Date: Mon, 15 Jan 2018 18:19:38 +0800 Subject: vim-patch:8.0.0406: arabic shaping code is verbose Problem: The arabic shaping code is verbose. Solution: Shorten the code without changing the functionality. https://github.com/vim/vim/commit/7f73b54631af3f0e6f0acd1a1b4c9e8436784705 --- src/nvim/arabic.c | 688 +++++++++++------------------------------------------- 1 file changed, 135 insertions(+), 553 deletions(-) diff --git a/src/nvim/arabic.c b/src/nvim/arabic.c index f0370af704..e120e6d492 100644 --- a/src/nvim/arabic.c +++ b/src/nvim/arabic.c @@ -432,7 +432,6 @@ static int chg_c_a2s(int cur_c) case a_HEH: return a_s_HEH; case a_YEH: return a_s_YEH; } - return 0; } @@ -478,176 +477,57 @@ static int chg_c_a2i(int cur_c) case a_HEH: return a_i_HEH; case a_YEH: return a_i_YEH; } - return 0; } // Change shape - from ISO-8859-6/Isolated to Medial static int chg_c_a2m(int cur_c) { - int tempc; - switch (cur_c) { - case a_HAMZA: // exception - tempc = a_s_HAMZA; - break; - - case a_ALEF_MADDA: // exception - tempc = a_f_ALEF_MADDA; - break; - - case a_ALEF_HAMZA_ABOVE: // exception - tempc = a_f_ALEF_HAMZA_ABOVE; - break; - - case a_WAW_HAMZA: // exception - tempc = a_f_WAW_HAMZA; - break; - - case a_ALEF_HAMZA_BELOW: // exception - tempc = a_f_ALEF_HAMZA_BELOW; - break; - - case a_YEH_HAMZA: - tempc = a_m_YEH_HAMZA; - break; - - case a_ALEF: // exception - tempc = a_f_ALEF; - break; - - case a_BEH: - tempc = a_m_BEH; - break; - - case a_TEH_MARBUTA: // exception - tempc = a_f_TEH_MARBUTA; - break; - - case a_TEH: - tempc = a_m_TEH; - break; - - case a_THEH: - tempc = a_m_THEH; - break; - - case a_JEEM: - tempc = a_m_JEEM; - break; - - case a_HAH: - tempc = a_m_HAH; - break; - - case a_KHAH: - tempc = a_m_KHAH; - break; - - case a_DAL: // exception - tempc = a_f_DAL; - break; - - case a_THAL: // exception - tempc = a_f_THAL; - break; - - case a_REH: // exception - tempc = a_f_REH; - break; - - case a_ZAIN: // exception - tempc = a_f_ZAIN; - break; - - case a_SEEN: - tempc = a_m_SEEN; - break; - - case a_SHEEN: - tempc = a_m_SHEEN; - break; - - case a_SAD: - tempc = a_m_SAD; - break; - - case a_DAD: - tempc = a_m_DAD; - break; - - case a_TAH: - tempc = a_m_TAH; - break; - - case a_ZAH: - tempc = a_m_ZAH; - break; - - case a_AIN: - tempc = a_m_AIN; - break; - - case a_GHAIN: - tempc = a_m_GHAIN; - break; - - case a_TATWEEL: // exception - tempc = cur_c; - break; - - case a_FEH: - tempc = a_m_FEH; - break; - - case a_QAF: - tempc = a_m_QAF; - break; - - case a_KAF: - tempc = a_m_KAF; - break; - - case a_LAM: - tempc = a_m_LAM; - break; - - case a_MEEM: - tempc = a_m_MEEM; - break; - - case a_NOON: - tempc = a_m_NOON; - break; - - case a_HEH: - tempc = a_m_HEH; - break; - - case a_WAW: // exception - tempc = a_f_WAW; - break; - - case a_ALEF_MAKSURA: // exception - tempc = a_f_ALEF_MAKSURA; - break; - - case a_YEH: - tempc = a_m_YEH; - break; - - default: - tempc = 0; + case a_HAMZA: return a_s_HAMZA; // exception + case a_ALEF_MADDA: return a_f_ALEF_MADDA; // exception + case a_ALEF_HAMZA_ABOVE: return a_f_ALEF_HAMZA_ABOVE; // exception + case a_WAW_HAMZA: return a_f_WAW_HAMZA; // exception + case a_ALEF_HAMZA_BELOW: return a_f_ALEF_HAMZA_BELOW; // exception + case a_YEH_HAMZA: return a_m_YEH_HAMZA; + case a_ALEF: return a_f_ALEF; // exception + case a_BEH: return a_m_BEH; + case a_TEH_MARBUTA: return a_f_TEH_MARBUTA; // exception + case a_TEH: return a_m_TEH; + case a_THEH: return a_m_THEH; + case a_JEEM: return a_m_JEEM; + case a_HAH: return a_m_HAH; + case a_KHAH: return a_m_KHAH; + case a_DAL: return a_f_DAL; // exception + case a_THAL: return a_f_THAL; // exception + case a_REH: return a_f_REH; // exception + case a_ZAIN: return a_f_ZAIN; // exception + case a_SEEN: return a_m_SEEN; + case a_SHEEN: return a_m_SHEEN; + case a_SAD: return a_m_SAD; + case a_DAD: return a_m_DAD; + case a_TAH: return a_m_TAH; + case a_ZAH: return a_m_ZAH; + case a_AIN: return a_m_AIN; + case a_GHAIN: return a_m_GHAIN; + case a_TATWEEL: return cur_c; // exception + case a_FEH: return a_m_FEH; + case a_QAF: return a_m_QAF; + case a_KAF: return a_m_KAF; + case a_LAM: return a_m_LAM; + case a_MEEM: return a_m_MEEM; + case a_NOON: return a_m_NOON; + case a_HEH: return a_m_HEH; + case a_WAW: return a_f_WAW; // exception + case a_ALEF_MAKSURA: return a_f_ALEF_MAKSURA; // exception + case a_YEH: return a_m_YEH; } - - return tempc; + return 0; } // Change shape - from ISO-8859-6/Isolated to final static int chg_c_a2f(int cur_c) { - int tempc; - // NOTE: these encodings need to be accounted for // // a_f_ALEF_MADDA; @@ -658,280 +538,87 @@ static int chg_c_a2f(int cur_c) // a_f_LAM_ALEF_HAMZA_BELOW; switch (cur_c) { - case a_HAMZA: // exception - tempc = a_s_HAMZA; - break; - - case a_ALEF_MADDA: - tempc = a_f_ALEF_MADDA; - break; - - case a_ALEF_HAMZA_ABOVE: - tempc = a_f_ALEF_HAMZA_ABOVE; - break; - - case a_WAW_HAMZA: - tempc = a_f_WAW_HAMZA; - break; - - case a_ALEF_HAMZA_BELOW: - tempc = a_f_ALEF_HAMZA_BELOW; - break; - - case a_YEH_HAMZA: - tempc = a_f_YEH_HAMZA; - break; - - case a_ALEF: - tempc = a_f_ALEF; - break; - - case a_BEH: - tempc = a_f_BEH; - break; - - case a_TEH_MARBUTA: - tempc = a_f_TEH_MARBUTA; - break; - - case a_TEH: - tempc = a_f_TEH; - break; - - case a_THEH: - tempc = a_f_THEH; - break; - - case a_JEEM: - tempc = a_f_JEEM; - break; - - case a_HAH: - tempc = a_f_HAH; - break; - - case a_KHAH: - tempc = a_f_KHAH; - break; - - case a_DAL: - tempc = a_f_DAL; - break; - - case a_THAL: - tempc = a_f_THAL; - break; - - case a_REH: - tempc = a_f_REH; - break; - - case a_ZAIN: - tempc = a_f_ZAIN; - break; - - case a_SEEN: - tempc = a_f_SEEN; - break; - - case a_SHEEN: - tempc = a_f_SHEEN; - break; - - case a_SAD: - tempc = a_f_SAD; - break; - - case a_DAD: - tempc = a_f_DAD; - break; - - case a_TAH: - tempc = a_f_TAH; - break; - - case a_ZAH: - tempc = a_f_ZAH; - break; - - case a_AIN: - tempc = a_f_AIN; - break; - - case a_GHAIN: - tempc = a_f_GHAIN; - break; - - case a_TATWEEL: // exception - tempc = cur_c; - break; - - case a_FEH: - tempc = a_f_FEH; - break; - - case a_QAF: - tempc = a_f_QAF; - break; - - case a_KAF: - tempc = a_f_KAF; - break; - - case a_LAM: - tempc = a_f_LAM; - break; - - case a_MEEM: - tempc = a_f_MEEM; - break; - - case a_NOON: - tempc = a_f_NOON; - break; - - case a_HEH: - tempc = a_f_HEH; - break; - - case a_WAW: - tempc = a_f_WAW; - break; - - case a_ALEF_MAKSURA: - tempc = a_f_ALEF_MAKSURA; - break; - - case a_YEH: - tempc = a_f_YEH; - break; - - default: - tempc = 0; + case a_HAMZA: return a_s_HAMZA; // exception + case a_ALEF_MADDA: return a_f_ALEF_MADDA; + case a_ALEF_HAMZA_ABOVE: return a_f_ALEF_HAMZA_ABOVE; + case a_WAW_HAMZA: return a_f_WAW_HAMZA; + case a_ALEF_HAMZA_BELOW: return a_f_ALEF_HAMZA_BELOW; + case a_YEH_HAMZA: return a_f_YEH_HAMZA; + case a_ALEF: return a_f_ALEF; + case a_BEH: return a_f_BEH; + case a_TEH_MARBUTA: return a_f_TEH_MARBUTA; + case a_TEH: return a_f_TEH; + case a_THEH: return a_f_THEH; + case a_JEEM: return a_f_JEEM; + case a_HAH: return a_f_HAH; + case a_KHAH: return a_f_KHAH; + case a_DAL: return a_f_DAL; + case a_THAL: return a_f_THAL; + case a_REH: return a_f_REH; + case a_ZAIN: return a_f_ZAIN; + case a_SEEN: return a_f_SEEN; + case a_SHEEN: return a_f_SHEEN; + case a_SAD: return a_f_SAD; + case a_DAD: return a_f_DAD; + case a_TAH: return a_f_TAH; + case a_ZAH: return a_f_ZAH; + case a_AIN: return a_f_AIN; + case a_GHAIN: return a_f_GHAIN; + case a_TATWEEL: return cur_c; // exception + case a_FEH: return a_f_FEH; + case a_QAF: return a_f_QAF; + case a_KAF: return a_f_KAF; + case a_LAM: return a_f_LAM; + case a_MEEM: return a_f_MEEM; + case a_NOON: return a_f_NOON; + case a_HEH: return a_f_HEH; + case a_WAW: return a_f_WAW; + case a_ALEF_MAKSURA: return a_f_ALEF_MAKSURA; + case a_YEH: return a_f_YEH; } - - return tempc; + return 0; } // Change shape - from Initial to Medial static int chg_c_i2m(int cur_c) { - int tempc; - switch (cur_c) { - case a_i_YEH_HAMZA: - tempc = a_m_YEH_HAMZA; - break; - - case a_i_BEH: - tempc = a_m_BEH; - break; - - case a_i_TEH: - tempc = a_m_TEH; - break; - - case a_i_THEH: - tempc = a_m_THEH; - break; - - case a_i_JEEM: - tempc = a_m_JEEM; - break; - - case a_i_HAH: - tempc = a_m_HAH; - break; - - case a_i_KHAH: - tempc = a_m_KHAH; - break; - - case a_i_SEEN: - tempc = a_m_SEEN; - break; - - case a_i_SHEEN: - tempc = a_m_SHEEN; - break; - - case a_i_SAD: - tempc = a_m_SAD; - break; - - case a_i_DAD: - tempc = a_m_DAD; - break; - - case a_i_TAH: - tempc = a_m_TAH; - break; - - case a_i_ZAH: - tempc = a_m_ZAH; - break; - - case a_i_AIN: - tempc = a_m_AIN; - break; - - case a_i_GHAIN: - tempc = a_m_GHAIN; - break; - - case a_i_FEH: - tempc = a_m_FEH; - break; - - case a_i_QAF: - tempc = a_m_QAF; - break; - - case a_i_KAF: - tempc = a_m_KAF; - break; - - case a_i_LAM: - tempc = a_m_LAM; - break; - - case a_i_MEEM: - tempc = a_m_MEEM; - break; - - case a_i_NOON: - tempc = a_m_NOON; - break; - - case a_i_HEH: - tempc = a_m_HEH; - break; - - case a_i_YEH: - tempc = a_m_YEH; - break; - - default: - tempc = 0; + case a_i_YEH_HAMZA: return a_m_YEH_HAMZA; + case a_i_BEH: return a_m_BEH; + case a_i_TEH: return a_m_TEH; + case a_i_THEH: return a_m_THEH; + case a_i_JEEM: return a_m_JEEM; + case a_i_HAH: return a_m_HAH; + case a_i_KHAH: return a_m_KHAH; + case a_i_SEEN: return a_m_SEEN; + case a_i_SHEEN: return a_m_SHEEN; + case a_i_SAD: return a_m_SAD; + case a_i_DAD: return a_m_DAD; + case a_i_TAH: return a_m_TAH; + case a_i_ZAH: return a_m_ZAH; + case a_i_AIN: return a_m_AIN; + case a_i_GHAIN: return a_m_GHAIN; + case a_i_FEH: return a_m_FEH; + case a_i_QAF: return a_m_QAF; + case a_i_KAF: return a_m_KAF; + case a_i_LAM: return a_m_LAM; + case a_i_MEEM: return a_m_MEEM; + case a_i_NOON: return a_m_NOON; + case a_i_HEH: return a_m_HEH; + case a_i_YEH: return a_m_YEH; } - - return tempc; + return 0; } // Change shape - from Final to Medial static int chg_c_f2m(int cur_c) { - int tempc; - switch (cur_c) { // NOTE: these encodings are multi-positional, no ? // case a_f_ALEF_MADDA: // case a_f_ALEF_HAMZA_ABOVE: // case a_f_ALEF_HAMZA_BELOW: - case a_f_YEH_HAMZA: - tempc = a_m_YEH_HAMZA; - break; - + case a_f_YEH_HAMZA: return a_m_YEH_HAMZA; case a_f_WAW_HAMZA: // exceptions case a_f_ALEF: case a_f_TEH_MARBUTA: @@ -941,165 +628,60 @@ static int chg_c_f2m(int cur_c) case a_f_ZAIN: case a_f_WAW: case a_f_ALEF_MAKSURA: - tempc = cur_c; - break; - - case a_f_BEH: - tempc = a_m_BEH; - break; - - case a_f_TEH: - tempc = a_m_TEH; - break; - - case a_f_THEH: - tempc = a_m_THEH; - break; - - case a_f_JEEM: - tempc = a_m_JEEM; - break; - - case a_f_HAH: - tempc = a_m_HAH; - break; - - case a_f_KHAH: - tempc = a_m_KHAH; - break; - - case a_f_SEEN: - tempc = a_m_SEEN; - break; - - case a_f_SHEEN: - tempc = a_m_SHEEN; - break; - - case a_f_SAD: - tempc = a_m_SAD; - break; - - case a_f_DAD: - tempc = a_m_DAD; - break; - - case a_f_TAH: - tempc = a_m_TAH; - break; - - case a_f_ZAH: - tempc = a_m_ZAH; - break; - - case a_f_AIN: - tempc = a_m_AIN; - break; - - case a_f_GHAIN: - tempc = a_m_GHAIN; - break; - - case a_f_FEH: - tempc = a_m_FEH; - break; - - case a_f_QAF: - tempc = a_m_QAF; - break; - - case a_f_KAF: - tempc = a_m_KAF; - break; - - case a_f_LAM: - tempc = a_m_LAM; - break; - - case a_f_MEEM: - tempc = a_m_MEEM; - break; - - case a_f_NOON: - tempc = a_m_NOON; - break; - - case a_f_HEH: - tempc = a_m_HEH; - break; - - case a_f_YEH: - tempc = a_m_YEH; - break; - + return cur_c; + case a_f_BEH: return a_m_BEH; + case a_f_TEH: return a_m_TEH; + case a_f_THEH: return a_m_THEH; + case a_f_JEEM: return a_m_JEEM; + case a_f_HAH: return a_m_HAH; + case a_f_KHAH: return a_m_KHAH; + case a_f_SEEN: return a_m_SEEN; + case a_f_SHEEN: return a_m_SHEEN; + case a_f_SAD: return a_m_SAD; + case a_f_DAD: return a_m_DAD; + case a_f_TAH: return a_m_TAH; + case a_f_ZAH: return a_m_ZAH; + case a_f_AIN: return a_m_AIN; + case a_f_GHAIN: return a_m_GHAIN; + case a_f_FEH: return a_m_FEH; + case a_f_QAF: return a_m_QAF; + case a_f_KAF: return a_m_KAF; + case a_f_LAM: return a_m_LAM; + case a_f_MEEM: return a_m_MEEM; + case a_f_NOON: return a_m_NOON; + case a_f_HEH: return a_m_HEH; + case a_f_YEH: return a_m_YEH; // NOTE: these encodings are multi-positional, no ? // case a_f_LAM_ALEF_MADDA_ABOVE: // case a_f_LAM_ALEF_HAMZA_ABOVE: // case a_f_LAM_ALEF_HAMZA_BELOW: // case a_f_LAM_ALEF: - default: - tempc = 0; } - - return tempc; + return 0; } // Change shape - from Combination (2 char) to an Isolated. static int chg_c_laa2i(int hid_c) { - int tempc; - switch (hid_c) { - case a_ALEF_MADDA: - tempc = a_s_LAM_ALEF_MADDA_ABOVE; - break; - - case a_ALEF_HAMZA_ABOVE: - tempc = a_s_LAM_ALEF_HAMZA_ABOVE; - break; - - case a_ALEF_HAMZA_BELOW: - tempc = a_s_LAM_ALEF_HAMZA_BELOW; - break; - - case a_ALEF: - tempc = a_s_LAM_ALEF; - break; - - default: - tempc = 0; + case a_ALEF_MADDA: return a_s_LAM_ALEF_MADDA_ABOVE; + case a_ALEF_HAMZA_ABOVE: return a_s_LAM_ALEF_HAMZA_ABOVE; + case a_ALEF_HAMZA_BELOW: return a_s_LAM_ALEF_HAMZA_BELOW; + case a_ALEF: return a_s_LAM_ALEF; } - - return tempc; + return 0; } // Change shape - from Combination-Isolated to Final. static int chg_c_laa2f(int hid_c) { - int tempc; - switch (hid_c) { - case a_ALEF_MADDA: - tempc = a_f_LAM_ALEF_MADDA_ABOVE; - break; - - case a_ALEF_HAMZA_ABOVE: - tempc = a_f_LAM_ALEF_HAMZA_ABOVE; - break; - - case a_ALEF_HAMZA_BELOW: - tempc = a_f_LAM_ALEF_HAMZA_BELOW; - break; - - case a_ALEF: - tempc = a_f_LAM_ALEF; - break; - - default: - tempc = 0; + case a_ALEF_MADDA: return a_f_LAM_ALEF_MADDA_ABOVE; + case a_ALEF_HAMZA_ABOVE: return a_f_LAM_ALEF_HAMZA_ABOVE; + case a_ALEF_HAMZA_BELOW: return a_f_LAM_ALEF_HAMZA_BELOW; + case a_ALEF: return a_f_LAM_ALEF; } - - return tempc; + return 0; } // Do "half-shaping" on character "c". Return zero if no shaping. -- cgit From 63bb7198dfded8a5c37195ebc8503516a1eda0f3 Mon Sep 17 00:00:00 2001 From: ckelsel Date: Mon, 15 Jan 2018 19:27:10 +0800 Subject: vim-patch:8.0.0398: illegal memory access with "t" Problem: Illegal memory access with "t". Solution: Use strncmp() instead of memcmp(). (Dominique Pelle, closes vim/vim#1528) https://github.com/vim/vim/commit/66727e16079fbac6db3897b5c3736ec9fba995bb --- src/nvim/search.c | 8 ++++---- src/nvim/testdir/test_search.vim | 7 +++++++ 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/nvim/search.c b/src/nvim/search.c index 1eb1a25a19..0a266382ec 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -1382,13 +1382,13 @@ int searchc(cmdarg_T *cap, int t_cmd) col -= (*mb_head_off)(p, p + col - 1) + 1; } if (lastc_bytelen == 1) { - if (p[col] == c && stop) + if (p[col] == c && stop) { break; - } else { - if (memcmp(p + col, lastc_bytes, lastc_bytelen) == 0 && stop) + } + } else if (STRNCMP(p + col, lastc_bytes, lastc_bytelen) == 0 && stop) { break; } - stop = TRUE; + stop = true; } } else { for (;; ) { diff --git a/src/nvim/testdir/test_search.vim b/src/nvim/testdir/test_search.vim index a333e7f206..03112df46f 100644 --- a/src/nvim/testdir/test_search.vim +++ b/src/nvim/testdir/test_search.vim @@ -298,3 +298,10 @@ func Test_searchpair() q! endfunc +func Test_searchc() + " These commands used to cause memory overflow in searchc(). + new + norm ixx + exe "norm 0t\u93cf" + bw! +endfunc -- cgit From 28998cfd815abd690ffa0b9bab786263af619008 Mon Sep 17 00:00:00 2001 From: ckelsel Date: Mon, 15 Jan 2018 19:41:29 +0800 Subject: vim-patch:8.0.0402: :map completion does not have Problem: :map completion does not have . (Dominique Pelle) Solution: Recognize in completion. Add a test. https://github.com/vim/vim/commit/cf5fdf7d1689ecb145b634dcb9c6e9fc60f63869 --- src/nvim/getchar.c | 23 +++++++++++++++-------- src/nvim/testdir/test_cmdline.vim | 20 ++++++++++++++++++++ 2 files changed, 35 insertions(+), 8 deletions(-) diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index 1b5d3472ab..7df1bf8429 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -3366,6 +3366,10 @@ set_context_in_map_cmd ( arg = skipwhite(arg + 8); continue; } + if (STRNCMP(arg, "", 9) == 0) { + arg = skipwhite(arg + 9); + continue; + } if (STRNCMP(arg, "