aboutsummaryrefslogtreecommitdiff
path: root/test/unit/viml
diff options
context:
space:
mode:
Diffstat (limited to 'test/unit/viml')
-rw-r--r--test/unit/viml/expressions/lexer_spec.lua420
-rw-r--r--test/unit/viml/expressions/parser_spec.lua7348
-rw-r--r--test/unit/viml/helpers.lua118
3 files changed, 7886 insertions, 0 deletions
diff --git a/test/unit/viml/expressions/lexer_spec.lua b/test/unit/viml/expressions/lexer_spec.lua
new file mode 100644
index 0000000000..75a641c48a
--- /dev/null
+++ b/test/unit/viml/expressions/lexer_spec.lua
@@ -0,0 +1,420 @@
+local helpers = require('test.unit.helpers')(after_each)
+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
+local cimport = helpers.cimport
+local ffi = helpers.ffi
+local eq = helpers.eq
+
+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 shallowcopy = global_helpers.shallowcopy
+local intchar2lua = global_helpers.intchar2lua
+
+local lib = cimport('./src/nvim/viml/parser/expressions.h')
+
+local eltkn_type_tab, eltkn_mul_type_tab, 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_mul_type_tab = {
+ [tonumber(lib.kExprLexMulMul)] = 'Mul',
+ [tonumber(lib.kExprLexMulDiv)] = 'Div',
+ [tonumber(lib.kExprLexMulMod)] = 'Mod',
+ }
+
+ eltkn_opt_scope_tab = {
+ [tonumber(lib.kExprOptScopeUnspecified)] = 'Unspecified',
+ [tonumber(lib.kExprOptScopeGlobal)] = 'Global',
+ [tonumber(lib.kExprOptScopeLocal)] = 'Local',
+ }
+end)
+
+local function conv_eltkn_type(typ)
+ return conv_enum(eltkn_type_tab, typ)
+end
+
+local bracket_types = {
+ Bracket = true,
+ FigureBrace = true,
+ Parenthesis = true,
+}
+
+local function eltkn2lua(pstate, tkn)
+ local ret = {
+ type = conv_eltkn_type(tkn.type),
+ }
+ 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 = {
+ 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
+ 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 == '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
+ return ret, tkn
+end
+
+local function next_eltkn(pstate, flags)
+ return eltkn2lua(pstate, lib.viml_pexpr_next_token(pstate, flags))
+end
+
+describe('Expressions lexer', function()
+ 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
+ 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)
+ 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})
+ 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', {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})
+ 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})
+ 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'})
+ 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('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, 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'})
+ 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()
+ 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, 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()
+ singl_eltkn_test('EOC', '|')
+ singl_eltkn_test('EOC', '\0')
+ singl_eltkn_test('EOC', '\n')
+ 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.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)
+ 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()
+
+ regular_scope_tests()
+ regular_is_tests()
+ regular_number_tests()
+
+ 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)
diff --git a/test/unit/viml/expressions/parser_spec.lua b/test/unit/viml/expressions/parser_spec.lua
new file mode 100644
index 0000000000..e5d0f2b84c
--- /dev/null
+++ b/test/unit/viml/expressions/parser_spec.lua
@@ -0,0 +1,7348 @@
+local helpers = require('test.unit.helpers')(after_each)
+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
+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
+local ffi = helpers.ffi
+local eq = helpers.eq
+
+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 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 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))
+ 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',
+ 'kExprNodeOpMissing',
+ 'kExprNodeTernary',
+ 'kExprNodeTernaryValue',
+ 'kExprNodeRegister',
+ 'kExprNodeSubscript',
+ 'kExprNodeListLiteral',
+ 'kExprNodeUnaryPlus',
+ 'kExprNodeBinaryPlus',
+ 'kExprNodeNested',
+ 'kExprNodeCall',
+ 'kExprNodePlainIdentifier',
+ 'kExprNodePlainKey',
+ 'kExprNodeComplexIdentifier',
+ 'kExprNodeUnknownFigure',
+ 'kExprNodeLambda',
+ 'kExprNodeDictLiteral',
+ 'kExprNodeCurlyBracesIdentifier',
+ 'kExprNodeComma',
+ 'kExprNodeColon',
+ 'kExprNodeArrow',
+ 'kExprNodeComparison',
+ 'kExprNodeConcat',
+ 'kExprNodeConcatOrSubscript',
+ 'kExprNodeInteger',
+ '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)
+ 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)))
+ 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 == '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(
+ (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))
+ 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))
+ 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
+ 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 = not ret.children
+ 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 = east.err.msg ~= nil and {
+ msg = ffi.string(east.err.msg),
+ arg = ('%s'):format(
+ 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),
+ })
+ ret[i + 1] = ('%s:%u:%u:%s'):format(
+ chunk_tbl.group,
+ chunk_tbl.start.line,
+ chunk_tbl.start.col,
+ chunk_tbl.str)
+ end
+ return ret
+end
+
+child_call_once(function()
+ assert:set_parameter('TableFormatLevel', 1000000)
+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 {}
+ for _, flags in ipairs({0, 1, 2, 3}) do
+ local err, msg = pcall(function()
+ 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)
+ 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(str, flags, ast, hls)
+ else
+ 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
+ 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
+ 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', {
+ 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', ')'),
+ })
+ 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'),
+ })
+ 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 = {
+ {
+ '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)
+ 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('"\\<C-u>"', {
+ -- 012345
+ ast = {
+ 'DoubleQuotedString(val="\\21"):0:0:"\\<C-u>"',
+ },
+ }, {
+ hl('DoubleQuote', '"'),
+ hl('DoubleQuotedEscape', '\\<C-u>'),
+ 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('"\\<C-u"', {
+ -- 0123456
+ ast = {
+ 'DoubleQuotedString(val="<C-u"):0:0:"\\<C-u"',
+ },
+ }, {
+ hl('DoubleQuote', '"'),
+ hl('DoubleQuotedUnknownEscape', '\\<'),
+ hl('DoubleQuotedBody', 'C-u'),
+ hl('DoubleQuote', '"'),
+ })
+ end)
+ itp('works with multiplication-like operators', function()
+ check_parsing('2+2*2', {
+ -- 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*', {
+ -- 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', {
+ -- 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', {
+ -- 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/', {
+ -- 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', {
+ -- 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', {
+ -- 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%', {
+ -- 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', {
+ -- 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', {
+ ast = {
+ 'Register(name=a):0:0:@a',
+ },
+ }, {
+ hl('Register', '@a'),
+ })
+ check_parsing('-@a', {
+ ast = {
+ {
+ 'UnaryMinus:0:0:-',
+ children = {
+ 'Register(name=a):0:1:@a',
+ },
+ },
+ },
+ }, {
+ hl('UnaryMinus', '-'),
+ hl('Register', '@a'),
+ })
+ check_parsing('@a-@b', {
+ 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', {
+ 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', {
+ 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', {
+ 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('-', {
+ ast = {
+ 'UnaryMinus:0:0:-',
+ },
+ err = {
+ arg = '',
+ msg = 'E15: Expected value, got EOC: %.*s',
+ },
+ }, {
+ hl('UnaryMinus', '-'),
+ })
+ check_parsing(' -', {
+ ast = {
+ 'UnaryMinus:0:0: -',
+ },
+ err = {
+ arg = '',
+ msg = 'E15: Expected value, got EOC: %.*s',
+ },
+ }, {
+ hl('UnaryMinus', '-', 1),
+ })
+ check_parsing('@a- ', {
+ 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', {
+ -- 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('IdentifierName', 'a'),
+ hl('And', '&&', 1),
+ hl('IdentifierName', 'b', 1),
+ hl('Or', '||', 1),
+ hl('IdentifierName', 'c', 1),
+ hl('And', '&&', 1),
+ hl('IdentifierName', 'd', 1),
+ })
+
+ check_parsing('&& a', {
+ -- 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('IdentifierName', 'a', 1),
+ })
+
+ check_parsing('|| a', {
+ -- 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('IdentifierName', 'a', 1),
+ })
+
+ check_parsing('a||', {
+ -- 012
+ ast = {
+ {
+ 'Or:0:1:||',
+ children = {
+ 'PlainIdentifier(scope=0,ident=a):0:0:a',
+ },
+ },
+ },
+ err = {
+ arg = '',
+ msg = 'E15: Expected value, got EOC: %.*s',
+ },
+ }, {
+ hl('IdentifierName', 'a'),
+ hl('Or', '||'),
+ })
+
+ check_parsing('a&&', {
+ -- 012
+ ast = {
+ {
+ 'And:0:1:&&',
+ children = {
+ 'PlainIdentifier(scope=0,ident=a):0:0:a',
+ },
+ },
+ },
+ err = {
+ arg = '',
+ msg = 'E15: Expected value, got EOC: %.*s',
+ },
+ }, {
+ hl('IdentifierName', 'a'),
+ hl('And', '&&'),
+ })
+
+ check_parsing('(&&)', {
+ -- 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('(||)', {
+ -- 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||)', {
+ -- 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('IdentifierName', 'a'),
+ hl('Or', '||'),
+ hl('InvalidNestingParenthesis', ')'),
+ })
+
+ check_parsing('(a&&)', {
+ -- 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('IdentifierName', 'a'),
+ hl('And', '&&'),
+ hl('InvalidNestingParenthesis', ')'),
+ })
+
+ check_parsing('(&&a)', {
+ -- 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('IdentifierName', 'a'),
+ hl('NestingParenthesis', ')'),
+ })
+
+ check_parsing('(||a)', {
+ -- 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('IdentifierName', 'a'),
+ hl('NestingParenthesis', ')'),
+ })
+ end)
+ itp('works with &opt', function()
+ check_parsing('&', {
+ -- 0
+ ast = {
+ 'Option(scope=0,ident=):0:0:&',
+ },
+ err = {
+ arg = '&',
+ msg = 'E112: Option name missing: %.*s',
+ },
+ }, {
+ hl('InvalidOptionSigil', '&'),
+ })
+
+ check_parsing('&opt', {
+ -- 0123
+ ast = {
+ 'Option(scope=0,ident=opt):0:0:&opt',
+ },
+ }, {
+ hl('OptionSigil', '&'),
+ hl('OptionName', 'opt'),
+ })
+
+ check_parsing('&l:opt', {
+ -- 012345
+ ast = {
+ 'Option(scope=l,ident=opt):0:0:&l:opt',
+ },
+ }, {
+ hl('OptionSigil', '&'),
+ hl('OptionScope', 'l'),
+ hl('OptionScopeDelimiter', ':'),
+ hl('OptionName', 'opt'),
+ })
+
+ check_parsing('&g:opt', {
+ -- 012345
+ ast = {
+ 'Option(scope=g,ident=opt):0:0:&g:opt',
+ },
+ }, {
+ hl('OptionSigil', '&'),
+ hl('OptionScope', 'g'),
+ hl('OptionScopeDelimiter', ':'),
+ hl('OptionName', 'opt'),
+ })
+
+ check_parsing('&s:opt', {
+ -- 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('OptionName', 's'),
+ hl('InvalidColon', ':'),
+ hl('IdentifierName', 'opt'),
+ })
+
+ check_parsing('& ', {
+ -- 01
+ ast = {
+ 'Option(scope=0,ident=):0:0:&',
+ },
+ err = {
+ arg = '& ',
+ msg = 'E112: Option name missing: %.*s',
+ },
+ }, {
+ hl('InvalidOptionSigil', '&'),
+ })
+
+ check_parsing('&-', {
+ -- 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', {
+ -- 01
+ ast = {
+ 'Option(scope=0,ident=A):0:0:&A',
+ },
+ }, {
+ hl('OptionSigil', '&'),
+ hl('OptionName', 'A'),
+ })
+
+ check_parsing('&xxx_yyy', {
+ -- 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('OptionName', 'xxx'),
+ hl('InvalidIdentifierName', '_yyy'),
+ }, {
+ [1] = {
+ ast = {
+ err = REMOVE_THIS,
+ ast = {
+ 'Option(scope=0,ident=xxx):0:0:&xxx',
+ },
+ },
+ hl_fs = {
+ [3] = REMOVE_THIS,
+ },
+ },
+ })
+
+ check_parsing('(1+&)', {
+ -- 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)', {
+ -- 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
+ ast = {
+ 'Environment(ident=):0:0:$',
+ },
+ err = {
+ arg = '$',
+ msg = 'E15: Environment variable name missing',
+ },
+ }, {
+ hl('InvalidEnvironmentSigil', '$'),
+ })
+
+ check_parsing('$g:A', {
+ -- 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('EnvironmentName', 'g'),
+ hl('InvalidColon', ':'),
+ hl('IdentifierName', 'A'),
+ })
+
+ check_parsing('$A', {
+ -- 01
+ ast = {
+ 'Environment(ident=A):0:0:$A',
+ },
+ }, {
+ hl('EnvironmentSigil', '$'),
+ hl('EnvironmentName', 'A'),
+ })
+
+ check_parsing('$ABC', {
+ -- 0123
+ ast = {
+ 'Environment(ident=ABC):0:0:$ABC',
+ },
+ }, {
+ hl('EnvironmentSigil', '$'),
+ hl('EnvironmentName', 'ABC'),
+ })
+
+ check_parsing('(1+$)', {
+ -- 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)', {
+ -- 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', {
+ -- 01234
+ ast = {
+ 'Environment(ident=_ABC):0:0:$_ABC',
+ },
+ }, {
+ hl('EnvironmentSigil', '$'),
+ hl('EnvironmentName', '_ABC'),
+ })
+
+ check_parsing('$_', {
+ -- 01
+ ast = {
+ 'Environment(ident=_):0:0:$_',
+ },
+ }, {
+ hl('EnvironmentSigil', '$'),
+ hl('EnvironmentName', '_'),
+ })
+
+ check_parsing('$ABC_DEF', {
+ -- 01234567
+ ast = {
+ 'Environment(ident=ABC_DEF):0:0:$ABC_DEF',
+ },
+ }, {
+ hl('EnvironmentSigil', '$'),
+ hl('EnvironmentName', 'ABC_DEF'),
+ })
+ end)
+ itp('works with unary !', function()
+ check_parsing('!', {
+ -- 0
+ ast = {
+ 'Not:0:0:!',
+ },
+ err = {
+ arg = '',
+ msg = 'E15: Expected value, got EOC: %.*s',
+ },
+ }, {
+ hl('Not', '!'),
+ })
+
+ check_parsing('!!', {
+ -- 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', {
+ -- 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', {
+ -- 01
+ ast = {
+ {
+ 'Not:0:0:!',
+ children = {
+ 'Integer(val=1):0:1:1',
+ },
+ },
+ },
+ }, {
+ hl('Not', '!'),
+ hl('Number', '1'),
+ })
+
+ check_parsing('(!1)', {
+ -- 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('(!)', {
+ -- 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)', {
+ -- 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', {
+ -- 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'),
+ }, {
+ [1] = {
+ ast = {
+ err = REMOVE_THIS,
+ ast = {
+ 'Integer(val=1):0:0:1',
+ },
+ },
+ hl_fs = {
+ [2] = REMOVE_THIS,
+ [3] = REMOVE_THIS,
+ },
+ },
+ })
+ end)
+ itp('highlights numbers with prefix', function()
+ check_parsing('0xABCDEF', {
+ -- 01234567
+ ast = {
+ 'Integer(val=11259375):0:0:0xABCDEF',
+ },
+ }, {
+ hl('NumberPrefix', '0x'),
+ hl('Number', 'ABCDEF'),
+ })
+
+ check_parsing('0Xabcdef', {
+ -- 01234567
+ ast = {
+ 'Integer(val=11259375):0:0:0Xabcdef',
+ },
+ }, {
+ hl('NumberPrefix', '0X'),
+ hl('Number', 'abcdef'),
+ })
+
+ check_parsing('0XABCDEF', {
+ -- 01234567
+ ast = {
+ 'Integer(val=11259375):0:0:0XABCDEF',
+ },
+ }, {
+ hl('NumberPrefix', '0X'),
+ hl('Number', 'ABCDEF'),
+ })
+
+ check_parsing('0xabcdef', {
+ -- 01234567
+ ast = {
+ 'Integer(val=11259375):0:0:0xabcdef',
+ },
+ }, {
+ hl('NumberPrefix', '0x'),
+ hl('Number', 'abcdef'),
+ })
+
+ check_parsing('0b001', {
+ -- 01234
+ ast = {
+ 'Integer(val=1):0:0:0b001',
+ },
+ }, {
+ hl('NumberPrefix', '0b'),
+ hl('Number', '001'),
+ })
+
+ check_parsing('0B001', {
+ -- 01234
+ ast = {
+ 'Integer(val=1):0:0:0B001',
+ },
+ }, {
+ hl('NumberPrefix', '0B'),
+ hl('Number', '001'),
+ })
+
+ check_parsing('0B00', {
+ -- 0123
+ ast = {
+ 'Integer(val=0):0:0:0B00',
+ },
+ }, {
+ hl('NumberPrefix', '0B'),
+ hl('Number', '00'),
+ })
+
+ check_parsing('00', {
+ -- 01
+ ast = {
+ 'Integer(val=0):0:0:00',
+ },
+ }, {
+ hl('NumberPrefix', '0'),
+ hl('Number', '0'),
+ })
+
+ check_parsing('001', {
+ -- 012
+ ast = {
+ 'Integer(val=1):0:0:001',
+ },
+ }, {
+ hl('NumberPrefix', '0'),
+ hl('Number', '01'),
+ })
+
+ check_parsing('01', {
+ -- 01
+ ast = {
+ 'Integer(val=1):0:0:01',
+ },
+ }, {
+ hl('NumberPrefix', '0'),
+ hl('Number', '1'),
+ })
+
+ check_parsing('1', {
+ -- 0
+ ast = {
+ 'Integer(val=1):0:0:1',
+ },
+ }, {
+ hl('Number', '1'),
+ })
+ end)
+ itp('works (KLEE tests)', function()
+ check_parsing('\0002&A:\000', {
+ ast = nil,
+ err = {
+ arg = '',
+ msg = 'E15: Expected value, got EOC: %.*s',
+ },
+ }, {
+ }, {
+ [2] = {
+ ast = {
+ ast = {
+ {
+ 'Colon:0:4::',
+ children = {
+ {
+ 'OpMissing:0:2:',
+ children = {
+ 'Integer(val=2):0:1:2',
+ 'Option(scope=0,ident=A):0:2:&A',
+ },
+ },
+ },
+ },
+ },
+ err = {
+ msg = 'E15: Unexpected EOC character: %.*s',
+ },
+ },
+ hl_fs = {
+ hl('InvalidSpacing', '\0'),
+ hl('Number', '2'),
+ hl('InvalidOptionSigil', '&'),
+ hl('InvalidOptionName', 'A'),
+ hl('InvalidColon', ':'),
+ hl('InvalidSpacing', '\0'),
+ },
+ },
+ [3] = {
+ ast = {
+ ast = {
+ 'Integer(val=2):0:1:2',
+ },
+ err = {
+ msg = 'E15: Unexpected EOC character: %.*s',
+ },
+ },
+ hl_fs = {
+ hl('InvalidSpacing', '\0'),
+ hl('Number', '2'),
+ },
+ },
+ })
+ check_parsing({data='01', size=1}, {
+ ast = {
+ 'Integer(val=0):0:0:0',
+ },
+ }, {
+ hl('Number', '0'),
+ })
+ check_parsing({data='001', size=2}, {
+ ast = {
+ 'Integer(val=0):0:0:00',
+ },
+ }, {
+ hl('NumberPrefix', '0'),
+ hl('Number', '0'),
+ })
+ check_parsing('"\\U\\', {
+ -- 0123
+ ast = {
+ [[DoubleQuotedString(val="U\\"):0:0:"\U\]],
+ },
+ err = {
+ arg = '"\\U\\',
+ msg = 'E114: Missing double quote: %.*s',
+ },
+ }, {
+ hl('InvalidDoubleQuote', '"'),
+ hl('InvalidDoubleQuotedUnknownEscape', '\\U'),
+ hl('InvalidDoubleQuotedBody', '\\'),
+ })
+ 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\\', {
+ -- 01234
+ err = {
+ arg = '|"\\U\\',
+ msg = 'E15: Expected value, got EOC: %.*s',
+ },
+ }, {
+ }, {
+ [2] = {
+ ast = {
+ ast = {
+ {
+ 'Or:0:0:|',
+ children = {
+ 'Missing:0:0:',
+ 'DoubleQuotedString(val="U\\\\"):0:1:"\\U\\',
+ },
+ },
+ },
+ err = {
+ msg = 'E15: Unexpected EOC character: %.*s',
+ },
+ },
+ hl_fs = {
+ hl('InvalidOr', '|'),
+ hl('InvalidDoubleQuote', '"'),
+ hl('InvalidDoubleQuotedUnknownEscape', '\\U'),
+ hl('InvalidDoubleQuotedBody', '\\'),
+ },
+ },
+ })
+ check_parsing('|"\\e"', {
+ -- 01234
+ err = {
+ arg = '|"\\e"',
+ msg = 'E15: Expected value, got EOC: %.*s',
+ },
+ }, {
+ }, {
+ [2] = {
+ ast = {
+ ast = {
+ {
+ 'Or:0:0:|',
+ children = {
+ 'Missing:0:0:',
+ 'DoubleQuotedString(val="\\27"):0:1:"\\e"',
+ },
+ },
+ },
+ err = {
+ msg = 'E15: Unexpected EOC character: %.*s',
+ },
+ },
+ hl_fs = {
+ hl('InvalidOr', '|'),
+ hl('DoubleQuote', '"'),
+ hl('DoubleQuotedEscape', '\\e'),
+ hl('DoubleQuote', '"'),
+ },
+ },
+ })
+ check_parsing('|\029', {
+ -- 01
+ err = {
+ arg = '|\029',
+ msg = 'E15: Expected value, got EOC: %.*s',
+ },
+ }, {
+ }, {
+ [2] = {
+ ast = {
+ ast = {
+ {
+ 'Or:0:0:|',
+ children = {
+ 'Missing:0:0:',
+ 'PlainIdentifier(scope=0,ident=\029):0:1:\029',
+ },
+ },
+ },
+ err = {
+ msg = 'E15: Unexpected EOC character: %.*s',
+ },
+ },
+ hl_fs = {
+ hl('InvalidOr', '|'),
+ hl('InvalidIdentifierName', '\029'),
+ },
+ },
+ })
+ check_parsing('"\\<', {
+ -- 012
+ ast = {
+ 'DoubleQuotedString(val="<"):0:0:"\\<',
+ },
+ err = {
+ arg = '"\\<',
+ msg = 'E114: Missing double quote: %.*s',
+ },
+ }, {
+ hl('InvalidDoubleQuote', '"'),
+ hl('InvalidDoubleQuotedUnknownEscape', '\\<'),
+ })
+ check_parsing('"\\1', {
+ -- 012
+ ast = {
+ 'DoubleQuotedString(val="\\1"):0:0:"\\1',
+ },
+ err = {
+ arg = '"\\1',
+ msg = 'E114: Missing double quote: %.*s',
+ },
+ }, {
+ hl('InvalidDoubleQuote', '"'),
+ hl('InvalidDoubleQuotedEscape', '\\1'),
+ })
+ check_parsing('}l', {
+ -- 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('InvalidIdentifierName', 'l'),
+ }, {
+ [1] = {
+ ast = {
+ ast = {
+ 'UnknownFigure(---):0:0:',
+ },
+ },
+ hl_fs = {
+ [2] = REMOVE_THIS,
+ },
+ },
+ })
+ check_parsing(':?\000\000\000\000\000\000\000', {
+ 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', '?'),
+ }, {
+ [2] = {
+ hl_fs = {
+ [3] = hl('InvalidSpacing', '\0'),
+ [4] = hl('InvalidSpacing', '\0'),
+ [5] = hl('InvalidSpacing', '\0'),
+ [6] = hl('InvalidSpacing', '\0'),
+ [7] = hl('InvalidSpacing', '\0'),
+ [8] = hl('InvalidSpacing', '\0'),
+ [9] = hl('InvalidSpacing', '\0'),
+ },
+ },
+ })
+ end)
+end)
diff --git a/test/unit/viml/helpers.lua b/test/unit/viml/helpers.lua
new file mode 100644
index 0000000000..aeb886a66f
--- /dev/null
+++ b/test/unit/viml/helpers.lua
@@ -0,0 +1,118 @@
+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
+ 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 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 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
+
+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,
+}