diff options
Diffstat (limited to 'test/unit/viml')
| -rw-r--r-- | test/unit/viml/expressions/lexer_spec.lua | 428 | ||||
| -rw-r--r-- | test/unit/viml/expressions/parser_spec.lua | 540 | ||||
| -rw-r--r-- | test/unit/viml/expressions/parser_tests.lua | 8185 | ||||
| -rw-r--r-- | test/unit/viml/helpers.lua | 130 | 
4 files changed, 9283 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..1b57a24ad5 --- /dev/null +++ b/test/unit/viml/expressions/lexer_spec.lua @@ -0,0 +1,428 @@ +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 conv_expr_asgn_type = viml_helpers.conv_expr_asgn_type + +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', + +    [tonumber(lib.kExprLexAssignment)] = 'Assignment', +  } + +  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 == 'Assignment' then +    ret.data = { type = conv_expr_asgn_type(tkn.data.ass.type) } +  elseif ret.type == 'Invalid' then +    ret.data = { error = ffi.string(tkn.data.err.msg) } +  end +  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('Assignment', '.=', {type='Concat'}) +    singl_eltkn_test('Plus', '+') +    singl_eltkn_test('Assignment', '+=', {type='Add'}) +    singl_eltkn_test('Comma', ',') +    singl_eltkn_test('Multiplication', '*', {type='Mul'}) +    singl_eltkn_test('Multiplication', '/', {type='Div'}) +    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('Assignment', '=', {type='Plain'}) +    comparison_test('==', '!=', 'Equal') +    comparison_test('=~', '!~', 'Matches') +    comparison_test('>', '<=', 'Greater') +    comparison_test('>=', '<', 'GreaterOrEqual') +    singl_eltkn_test('Minus', '-') +    singl_eltkn_test('Assignment', '-=', {type='Subtract'}) +    singl_eltkn_test('Arrow', '->') +    singl_eltkn_test('Invalid', '~', {error='E15: Unidentified character: %.*s'}) +    simple_test({{data=nil, size=0}}, 'EOC', 0, {error='start.col >= #pstr'}) +    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..79b19de833 --- /dev/null +++ b/test/unit/viml/expressions/parser_spec.lua @@ -0,0 +1,540 @@ +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 debug_log = helpers.debug_log +local ptr2key = helpers.ptr2key +local cimport = helpers.cimport +local ffi = helpers.ffi +local neq = helpers.neq +local eq = helpers.eq + +local conv_ccs = viml_helpers.conv_ccs +local new_pstate = viml_helpers.new_pstate +local conv_cmp_type = viml_helpers.conv_cmp_type +local pstate_set_str = viml_helpers.pstate_set_str +local conv_expr_asgn_type = viml_helpers.conv_expr_asgn_type + +local mergedicts_copy = global_helpers.mergedicts_copy +local format_string = global_helpers.format_string +local format_luav = global_helpers.format_luav +local intchar2lua = global_helpers.intchar2lua +local dictdiff = global_helpers.dictdiff + +local lib = cimport('./src/nvim/viml/parser/expressions.h', +                    './src/nvim/syntax.h') + +local alloc_log = alloc_log_new() + +local predefined_hl_defs = { +  -- From highlight_init_both +  Conceal=true, +  Cursor=true, +  lCursor=true, +  DiffText=true, +  ErrorMsg=true, +  IncSearch=true, +  ModeMsg=true, +  NonText=true, +  PmenuSbar=true, +  StatusLine=true, +  StatusLineNC=true, +  TabLineFill=true, +  TabLineSel=true, +  TermCursor=true, +  VertSplit=true, +  WildMenu=true, +  EndOfBuffer=true, +  QuickFixLine=true, +  Substitute=true, +  Whitespace=true, + +  -- From highlight_init_(dark|light) +  ColorColumn=true, +  CursorColumn=true, +  CursorLine=true, +  CursorLineNr=true, +  DiffAdd=true, +  DiffChange=true, +  DiffDelete=true, +  Directory=true, +  FoldColumn=true, +  Folded=true, +  LineNr=true, +  MatchParen=true, +  MoreMsg=true, +  Pmenu=true, +  PmenuSel=true, +  PmenuThumb=true, +  Question=true, +  Search=true, +  SignColumn=true, +  SpecialKey=true, +  SpellBad=true, +  SpellCap=true, +  SpellLocal=true, +  SpellRare=true, +  TabLine=true, +  Title=true, +  Visual=true, +  WarningMsg=true, +  Normal=true, + +  -- From syncolor.vim, if &background +  Comment=true, +  Constant=true, +  Special=true, +  Identifier=true, +  Statement=true, +  PreProc=true, +  Type=true, +  Underlined=true, +  Ignore=true, + +  -- From syncolor.vim, below if &background +  Error=true, +  Todo=true, + +  -- From syncolor.vim, links at the bottom +  String=true, +  Character=true, +  Number=true, +  Boolean=true, +  Float=true, +  Function=true, +  Conditional=true, +  Repeat=true, +  Label=true, +  Operator=true, +  Keyword=true, +  Exception=true, +  Include=true, +  Define=true, +  Macro=true, +  PreCondit=true, +  StorageClass=true, +  Structure=true, +  Typedef=true, +  Tag=true, +  SpecialChar=true, +  Delimiter=true, +  SpecialComment=true, +  Debug=true, +} + +local nvim_hl_defs = {} + +child_call_once(function() +  local i = 0 +  while lib.highlight_init_cmdline[i] ~= nil do +    local hl_args = lib.highlight_init_cmdline[i] +    local s = ffi.string(hl_args) +    local err, msg = pcall(function() +      if s:sub(1, 13) == 'default link ' then +        local new_grp, grp_link = s:match('^default link (%w+) (%w+)$') +        neq(nil, new_grp) +        -- Note: group to link to must be already defined at the time of +        --       linking, otherwise it will be created as cleared. So existence +        --       of the group is checked here and not in the next pass over +        --       nvim_hl_defs. +        eq(true, not not (nvim_hl_defs[grp_link] +                          or predefined_hl_defs[grp_link])) +        eq(false, not not (nvim_hl_defs[new_grp] +                           or predefined_hl_defs[new_grp])) +        nvim_hl_defs[new_grp] = {'link', grp_link} +      else +        local new_grp, grp_args = s:match('^(%w+) (.*)') +        neq(nil, new_grp) +        eq(false, not not (nvim_hl_defs[new_grp] +                           or predefined_hl_defs[new_grp])) +        nvim_hl_defs[new_grp] = {'definition', grp_args} +      end +    end) +    if not err then +      msg = format_string( +        'Error while processing string %s at position %u:\n%s', s, i, msg) +      error(msg) +    end +    i = i + 1 +  end +  for k, _ in ipairs(nvim_hl_defs) do +    eq('NVim', k:sub(1, 4)) +    -- NVimInvalid +    -- 12345678901 +    local err, msg = pcall(function() +      if k:sub(5, 11) == 'Invalid' then +        neq(nil, nvim_hl_defs['NVim' .. k:sub(12)]) +      else +        neq(nil, nvim_hl_defs['NVimInvalid' .. k:sub(5)]) +      end +    end) +    if not err then +      msg = format_string('Error while processing group %s:\n%s', k, msg) +      error(msg) +    end +  end +end) + +local function hls_to_hl_fs(hls) +  local ret = {} +  local next_col = 0 +  for i, v in ipairs(hls) do +    local group, line, col, str = v:match('^NVim([a-zA-Z]+):(%d+):(%d+):(.*)$') +    col = tonumber(col) +    line = tonumber(line) +    assert(line == 0) +    local col_shift = col - next_col +    assert(col_shift >= 0) +    next_col = col + #str +    ret[i] = format_string('hl(%r, %r%s)', +                           group, +                           str, +                           (col_shift == 0 +                            and '' +                            or (', %u'):format(col_shift))) +  end +  return ret +end + +local function format_check(expr, format_check_data, opts) +  -- That forces specific order. +  local zflags = opts.flags[1] +  local zdata = format_check_data[zflags] +  local dig_len +  if opts.funcname then +    print(format_string('\n%s(%r, {', opts.funcname, expr)) +    dig_len = #opts.funcname + 2 +  else +    print(format_string('\n_check_parsing(%r, %r, {', opts, expr)) +    dig_len = #('_check_parsing(, \'') + #(format_string('%r', opts)) +  end +  local digits = '  --' .. (' '):rep(dig_len - #('  --')) +  local digits2 = digits:sub(1, -10) +  for i = 0, #expr - 1 do +    if i % 10 == 0 then +      digits2 = ('%s%10u'):format(digits2, i / 10) +    end +    digits = ('%s%u'):format(digits, i % 10) +  end +  print(digits) +  if #expr > 10 then +    print(digits2) +  end +  if zdata.ast.len then +    print(('  len = %u,'):format(zdata.ast.len)) +  end +  print('  ast = ' .. format_luav(zdata.ast.ast, '  ') .. ',') +  if zdata.ast.err then +    print('  err = {') +    print('    arg = ' .. format_luav(zdata.ast.err.arg) .. ',') +    print('    msg = ' .. format_luav(zdata.ast.err.msg) .. ',') +    print('  },') +  end +  print('}, {') +  for _, v in ipairs(zdata.hl_fs) do +    print('  ' .. v .. ',') +  end +  local diffs = {} +  local diffs_num = 0 +  for flags, v in pairs(format_check_data) do +    if flags ~= zflags then +      diffs[flags] = dictdiff(zdata, v) +      if diffs[flags] then +        if flags == 3 + zflags then +          if (dictdiff(format_check_data[1 + zflags], +                       format_check_data[3 + zflags]) == nil +              or dictdiff(format_check_data[2 + zflags], +                          format_check_data[3 + zflags]) == nil) +          then +            diffs[flags] = nil +          else +            diffs_num = diffs_num + 1 +          end +        else +          diffs_num = diffs_num + 1 +        end +      end +    end +  end +  if diffs_num ~= 0 then +    print('}, {') +    local flags = 1 +    while diffs_num ~= 0 do +      if diffs[flags] then +        diffs_num = diffs_num - 1 +        local diff = diffs[flags] +        print(('  [%u] = {'):format(flags)) +        if diff.ast then +          print('    ast = ' .. format_luav(diff.ast, '    ') .. ',') +        end +        if diff.hl_fs then +          print('    hl_fs = ' .. format_luav(diff.hl_fs, '    ', { +            literal_strings=true +          }) .. ',') +        end +        print('  },') +      end +      flags = flags + 1 +    end +  end +  print('})') +end + +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', +  'kExprNodeAssignment', +}, '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 .. format_string('(val=%e)', tonumber(eastnode.data.flt.value)) +  elseif typ == 'SingleQuotedString' or typ == 'DoubleQuotedString' then +    if eastnode.data.str.value == nil then +      typ = typ .. '(val=NULL)' +    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)) +  elseif typ == 'Assignment' then +    typ = ('%s(%s)'):format(typ, conv_expr_asgn_type(eastnode.data.ass.type)) +  end +  ret_str = typ .. ':' .. ret_str +  local can_simplify = not ret.children +  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(str, pstate, east) +  local checked_nodes = {} +  local len = tonumber(pstate.pos.col) +  if pstate.pos.line == 1 then +    len = tonumber(pstate.reader.lines.items[0].size) +  end +  if type(str) == 'string' and len == #str then +    len = nil +  end +  return { +    err = east.err.msg ~= nil and { +      msg = ffi.string(east.err.msg), +      arg = ffi.string(east.err.arg, east.err.arg_len), +    } or nil, +    len = len, +    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(opts, str, exp_ast, exp_highlighting_fs, +                                nz_flags_exps) +    local zflags = opts.flags[1] +    nz_flags_exps = nz_flags_exps or {} +    local format_check_data = {} +    for _, flags in ipairs(opts.flags) do +      debug_log(('Running test case (%s, %u)'):format(str, flags)) +      local err, msg = pcall(function() +        if os.getenv('NVIM_TEST_PARSER_SPEC_PRINT_TEST_CASE') == '1' then +          print(str, flags) +        end +        alloc_log:check({}) + +        local pstate = new_pstate({str}) +        local east = lib.viml_pexpr_parse(pstate, flags) +        local ast = east2lua(str, pstate, east) +        local hls = phl2lua(pstate) +        if exp_ast == nil then +          format_check_data[flags] = {ast=ast, hl_fs=hls_to_hl_fs(hls)} +        else +          local exps = { +            ast = exp_ast, +            hl_fs = exp_highlighting_fs, +          } +          local add_exps = nz_flags_exps[flags] +          if not add_exps and flags == 3 + zflags then +            add_exps = nz_flags_exps[1 + zflags] or nz_flags_exps[2 + zflags] +          end +          if add_exps then +            if add_exps.ast then +              exps.ast = mergedicts_copy(exps.ast, add_exps.ast) +            end +            if add_exps.hl_fs then +              exps.hl_fs = mergedicts_copy(exps.hl_fs, add_exps.hl_fs) +            end +          end +          eq(exps.ast, ast) +          if exp_highlighting_fs then +            local exp_highlighting = {} +            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 +    if exp_ast == nil then +      format_check(str, format_check_data, opts) +    end +  end +  local function hl(group, str, shift) +    return function(next_col) +      if nvim_hl_defs['NVim' .. group] == nil then +        error(('Unknown group: NVim%s'):format(group)) +      end +      local col = next_col + (shift or 0) +      return (('%s:%u:%u:%s'):format( +        'NVim' .. group, +        0, +        col, +        str)), (col + #str) +    end +  end +  local function fmtn(typ, args, rest) +    return ('%s(%s)%s'):format(typ, args, rest) +  end +  require('test.unit.viml.expressions.parser_tests')( +      itp, _check_parsing, hl, fmtn) +end) diff --git a/test/unit/viml/expressions/parser_tests.lua b/test/unit/viml/expressions/parser_tests.lua new file mode 100644 index 0000000000..f0c2723a38 --- /dev/null +++ b/test/unit/viml/expressions/parser_tests.lua @@ -0,0 +1,8185 @@ +local global_helpers = require('test.helpers') + +local REMOVE_THIS = global_helpers.REMOVE_THIS + +return function(itp, _check_parsing, hl, fmtn) +  local function check_parsing(...) +    return _check_parsing({flags={0, 1, 2, 3}, funcname='check_parsing'}, ...) +  end +  local function check_asgn_parsing(...) +    return _check_parsing({ +      flags={4, 5, 6, 7}, +      funcname='check_asgn_parsing', +    }, ...) +  end +  itp('works with + and @a', function() +    check_parsing('@a', { +      ast = { +        'Register(name=a):0:0:@a', +      }, +    }, { +      hl('Register', '@a'), +    }) +    check_parsing('+@a', { +      ast = { +        { +          'UnaryPlus:0:0:+', +          children = { +            'Register(name=a):0:1:@a', +          }, +        }, +      }, +    }, { +      hl('UnaryPlus', '+'), +      hl('Register', '@a'), +    }) +    check_parsing('@a+@b', { +      ast = { +        { +          'BinaryPlus:0:2:+', +          children = { +            'Register(name=a):0:0:@a', +            'Register(name=b):0:3:@b', +          }, +        }, +      }, +    }, { +      hl('Register', '@a'), +      hl('BinaryPlus', '+'), +      hl('Register', '@b'), +    }) +    check_parsing('@a+@b+@c', { +      ast = { +        { +          'BinaryPlus:0:5:+', +          children = { +            { +              'BinaryPlus:0:2:+', +              children = { +                'Register(name=a):0:0:@a', +                'Register(name=b):0:3:@b', +              }, +            }, +            'Register(name=c):0:6:@c', +          }, +        }, +      }, +    }, { +      hl('Register', '@a'), +      hl('BinaryPlus', '+'), +      hl('Register', '@b'), +      hl('BinaryPlus', '+'), +      hl('Register', '@c'), +    }) +    check_parsing('+@a+@b', { +      ast = { +        { +          'BinaryPlus:0:3:+', +          children = { +            { +              'UnaryPlus:0:0:+', +              children = { +                'Register(name=a):0:1:@a', +              }, +            }, +            'Register(name=b):0:4:@b', +          }, +        }, +      }, +    }, { +      hl('UnaryPlus', '+'), +      hl('Register', '@a'), +      hl('BinaryPlus', '+'), +      hl('Register', '@b'), +    }) +    check_parsing('+@a++@b', { +      ast = { +        { +          'BinaryPlus:0:3:+', +          children = { +            { +              'UnaryPlus:0:0:+', +              children = { +                'Register(name=a):0:1:@a', +              }, +            }, +            { +              'UnaryPlus:0:4:+', +              children = { +                'Register(name=b):0:5:@b', +              }, +            }, +          }, +        }, +      }, +    }, { +      hl('UnaryPlus', '+'), +      hl('Register', '@a'), +      hl('BinaryPlus', '+'), +      hl('UnaryPlus', '+'), +      hl('Register', '@b'), +    }) +    check_parsing('@a@b', { +      ast = { +        { +          'OpMissing:0:2:', +          children = { +            'Register(name=a):0:0:@a', +            'Register(name=b):0:2:@b', +          }, +        }, +      }, +      err = { +        arg = '@b', +        msg = 'E15: Missing operator: %.*s', +      }, +    }, { +      hl('Register', '@a'), +      hl('InvalidRegister', '@b'), +    }, { +      [1] = { +        ast = { +          len = 2, +          err = REMOVE_THIS, +          ast = { +            'Register(name=a):0:0:@a' +          }, +        }, +        hl_fs = { +          [2] = REMOVE_THIS, +        }, +      }, +    }) +    check_parsing(' @a \t @b', { +      ast = { +        { +          'OpMissing:0:3:', +          children = { +            'Register(name=a):0:0: @a', +            'Register(name=b):0:3: \t @b', +          }, +        }, +      }, +      err = { +        arg = '@b', +        msg = 'E15: Missing operator: %.*s', +      }, +    }, { +      hl('Register', '@a', 1), +      hl('InvalidSpacing', ' \t '), +      hl('Register', '@b'), +    }, { +      [1] = { +        ast = { +          len = 6, +          err = REMOVE_THIS, +          ast = { +            'Register(name=a):0:0: @a' +          }, +        }, +        hl_fs = { +          [2] = REMOVE_THIS, +          [3] = REMOVE_THIS, +        }, +      }, +    }) +    check_parsing('+', { +      ast = { +        'UnaryPlus:0:0:+', +      }, +      err = { +        arg = '', +        msg = 'E15: Expected value, got EOC: %.*s', +      }, +    }, { +      hl('UnaryPlus', '+'), +    }) +    check_parsing(' +', { +      ast = { +        'UnaryPlus:0:0: +', +      }, +      err = { +        arg = '', +        msg = 'E15: Expected value, got EOC: %.*s', +      }, +    }, { +      hl('UnaryPlus', '+', 1), +    }) +    check_parsing('@a+  ', { +      ast = { +        { +          'BinaryPlus:0:2:+', +          children = { +            'Register(name=a):0:0:@a', +          }, +        }, +      }, +      err = { +        arg = '', +        msg = 'E15: Expected value, got EOC: %.*s', +      }, +    }, { +      hl('Register', '@a'), +      hl('BinaryPlus', '+'), +    }) +  end) +  itp('works with @a, + and parenthesis', function() +    check_parsing('(@a)', { +      ast = { +        { +          'Nested:0:0:(', +          children = { +            'Register(name=a):0:1:@a', +          }, +        }, +      }, +    }, { +      hl('NestingParenthesis', '('), +      hl('Register', '@a'), +      hl('NestingParenthesis', ')'), +    }) +    check_parsing('()', { +      ast = { +        { +          'Nested:0:0:(', +          children = { +            'Missing:0:1:', +          }, +        }, +      }, +      err = { +        arg = ')', +        msg = 'E15: Expected value, got parenthesis: %.*s', +      }, +    }, { +      hl('NestingParenthesis', '('), +      hl('InvalidNestingParenthesis', ')'), +    }) +    check_parsing(')', { +      ast = { +        { +          'Nested:0:0:', +          children = { +            'Missing:0:0:', +          }, +        }, +      }, +      err = { +        arg = ')', +        msg = 'E15: Expected value, got parenthesis: %.*s', +      }, +    }, { +      hl('InvalidNestingParenthesis', ')'), +    }) +    check_parsing('+)', { +      ast = { +        { +          'Nested:0:1:', +          children = { +            { +              'UnaryPlus:0:0:+', +              children = { +                'Missing:0:1:', +              }, +            }, +          }, +        }, +      }, +      err = { +        arg = ')', +        msg = 'E15: Expected value, got parenthesis: %.*s', +      }, +    }, { +      hl('UnaryPlus', '+'), +      hl('InvalidNestingParenthesis', ')'), +    }) +    check_parsing('+@a(@b)', { +      ast = { +        { +          'UnaryPlus:0:0:+', +          children = { +            { +              'Call:0:3:(', +              children = { +                'Register(name=a):0:1:@a', +                'Register(name=b):0:4:@b', +              }, +            }, +          }, +        }, +      }, +    }, { +      hl('UnaryPlus', '+'), +      hl('Register', '@a'), +      hl('CallingParenthesis', '('), +      hl('Register', '@b'), +      hl('CallingParenthesis', ')'), +    }) +    check_parsing('@a+@b(@c)', { +      ast = { +        { +          'BinaryPlus:0:2:+', +          children = { +            'Register(name=a):0:0:@a', +            { +              'Call:0:5:(', +              children = { +                'Register(name=b):0:3:@b', +                'Register(name=c):0:6:@c', +              }, +            }, +          }, +        }, +      }, +    }, { +      hl('Register', '@a'), +      hl('BinaryPlus', '+'), +      hl('Register', '@b'), +      hl('CallingParenthesis', '('), +      hl('Register', '@c'), +      hl('CallingParenthesis', ')'), +    }) +    check_parsing('@a()', { +      ast = { +        { +          'Call:0:2:(', +          children = { +            'Register(name=a):0:0:@a', +          }, +        }, +      }, +    }, { +      hl('Register', '@a'), +      hl('CallingParenthesis', '('), +      hl('CallingParenthesis', ')'), +    }) +    check_parsing('@a ()', { +      ast = { +        { +          'OpMissing:0:2:', +          children = { +            'Register(name=a):0:0:@a', +            { +              'Nested:0:2: (', +              children = { +                'Missing:0:4:', +              }, +            }, +          }, +        }, +      }, +      err = { +        arg = '()', +        msg = 'E15: Missing operator: %.*s', +      }, +    }, { +      hl('Register', '@a'), +      hl('InvalidSpacing', ' '), +      hl('NestingParenthesis', '('), +      hl('InvalidNestingParenthesis', ')'), +    }, { +      [1] = { +        ast = { +          len = 3, +          err = REMOVE_THIS, +          ast = { +            'Register(name=a):0:0:@a', +          }, +        }, +        hl_fs = { +          [2] = REMOVE_THIS, +          [3] = REMOVE_THIS, +          [4] = REMOVE_THIS, +        }, +      }, +    }) +    check_parsing('@a + (@b)', { +      ast = { +        { +          'BinaryPlus:0:2: +', +          children = { +            'Register(name=a):0:0:@a', +            { +              'Nested:0:4: (', +              children = { +                'Register(name=b):0:6:@b', +              }, +            }, +          }, +        }, +      }, +    }, { +      hl('Register', '@a'), +      hl('BinaryPlus', '+', 1), +      hl('NestingParenthesis', '(', 1), +      hl('Register', '@b'), +      hl('NestingParenthesis', ')'), +    }) +    check_parsing('@a + (+@b)', { +      ast = { +        { +          'BinaryPlus:0:2: +', +          children = { +            'Register(name=a):0:0:@a', +            { +              'Nested:0:4: (', +              children = { +                { +                  'UnaryPlus:0:6:+', +                  children = { +                    'Register(name=b):0:7:@b', +                  }, +                }, +              }, +            }, +          }, +        }, +      }, +    }, { +      hl('Register', '@a'), +      hl('BinaryPlus', '+', 1), +      hl('NestingParenthesis', '(', 1), +      hl('UnaryPlus', '+'), +      hl('Register', '@b'), +      hl('NestingParenthesis', ')'), +    }) +    check_parsing('@a + (@b + @c)', { +      ast = { +        { +          'BinaryPlus:0:2: +', +          children = { +            'Register(name=a):0:0:@a', +            { +              'Nested:0:4: (', +              children = { +                { +                  'BinaryPlus:0:8: +', +                  children = { +                    'Register(name=b):0:6:@b', +                    'Register(name=c):0:10: @c', +                  }, +                }, +              }, +            }, +          }, +        }, +      }, +    }, { +      hl('Register', '@a'), +      hl('BinaryPlus', '+', 1), +      hl('NestingParenthesis', '(', 1), +      hl('Register', '@b'), +      hl('BinaryPlus', '+', 1), +      hl('Register', '@c', 1), +      hl('NestingParenthesis', ')'), +    }) +    check_parsing('(@a)+@b', { +      ast = { +        { +          'BinaryPlus:0:4:+', +          children = { +            { +              'Nested:0:0:(', +              children = { +                'Register(name=a):0:1:@a', +              }, +            }, +            'Register(name=b):0:5:@b', +          }, +        }, +      }, +    }, { +      hl('NestingParenthesis', '('), +      hl('Register', '@a'), +      hl('NestingParenthesis', ')'), +      hl('BinaryPlus', '+'), +      hl('Register', '@b'), +    }) +    check_parsing('@a+(@b)(@c)', { +      --           01234567890 +      ast = { +        { +          'BinaryPlus:0:2:+', +          children = { +            'Register(name=a):0:0:@a', +            { +              'Call:0:7:(', +              children = { +                { +                  'Nested:0:3:(', +                  children = { 'Register(name=b):0:4:@b' }, +                }, +                'Register(name=c):0:8:@c', +              }, +            }, +          }, +        }, +      }, +    }, { +      hl('Register', '@a'), +      hl('BinaryPlus', '+'), +      hl('NestingParenthesis', '('), +      hl('Register', '@b'), +      hl('NestingParenthesis', ')'), +      hl('CallingParenthesis', '('), +      hl('Register', '@c'), +      hl('CallingParenthesis', ')'), +    }) +    check_parsing('@a+((@b))(@c)', { +      --           01234567890123456890123456789 +      --           0         1        2 +      ast = { +        { +          'BinaryPlus:0:2:+', +          children = { +            'Register(name=a):0:0:@a', +            { +              'Call:0:9:(', +              children = { +                { +                  'Nested:0:3:(', +                  children = { +                    { +                      'Nested:0:4:(', +                      children = { 'Register(name=b):0:5:@b' } +                    }, +                  }, +                }, +                'Register(name=c):0:10:@c', +              }, +            }, +          }, +        }, +      }, +    }, { +      hl('Register', '@a'), +      hl('BinaryPlus', '+'), +      hl('NestingParenthesis', '('), +      hl('NestingParenthesis', '('), +      hl('Register', '@b'), +      hl('NestingParenthesis', ')'), +      hl('NestingParenthesis', ')'), +      hl('CallingParenthesis', '('), +      hl('Register', '@c'), +      hl('CallingParenthesis', ')'), +    }) +    check_parsing('@a+((@b))+@c', { +      --           01234567890123456890123456789 +      --           0         1        2 +      ast = { +        { +          'BinaryPlus:0:9:+', +          children = { +            { +              'BinaryPlus:0:2:+', +              children = { +                'Register(name=a):0:0:@a', +                { +                  'Nested:0:3:(', +                  children = { +                    { +                      'Nested:0:4:(', +                      children = { 'Register(name=b):0:5:@b' } +                    }, +                  }, +                }, +              }, +            }, +            'Register(name=c):0:10:@c', +          }, +        }, +      }, +    }, { +      hl('Register', '@a'), +      hl('BinaryPlus', '+'), +      hl('NestingParenthesis', '('), +      hl('NestingParenthesis', '('), +      hl('Register', '@b'), +      hl('NestingParenthesis', ')'), +      hl('NestingParenthesis', ')'), +      hl('BinaryPlus', '+'), +      hl('Register', '@c'), +    }) +    check_parsing( +      '@a + (@b + @c) + @d(@e) + (+@f) + ((+@g(@h))(@j)(@k))(@l)', {--[[ +       | | | | | |   | |  ||  | | ||  | | ||| ||   ||  ||   || +       000000000011111111112222222222333333333344444444445555555 +       012345678901234567890123456789012345678901234567890123456 +      ]] +        ast = {{ +          'BinaryPlus:0:31: +', +          children = { +            { +              'BinaryPlus:0:23: +', +              children = { +                { +                  'BinaryPlus:0:14: +', +                  children = { +                    { +                      'BinaryPlus:0:2: +', +                      children = { +                        'Register(name=a):0:0:@a', +                        { +                          'Nested:0:4: (', +                          children = { +                            { +                              'BinaryPlus:0:8: +', +                              children = { +                                'Register(name=b):0:6:@b', +                                'Register(name=c):0:10: @c', +                              }, +                            }, +                          }, +                        }, +                      }, +                    }, +                    { +                      'Call:0:19:(', +                      children = { +                        'Register(name=d):0:16: @d', +                        'Register(name=e):0:20:@e', +                      }, +                    }, +                  }, +                }, +                { +                  'Nested:0:25: (', +                  children = { +                    { +                      'UnaryPlus:0:27:+', +                      children = { +                        'Register(name=f):0:28:@f', +                      }, +                    }, +                  }, +                }, +              }, +            }, +            { +              'Call:0:53:(', +              children = { +                { +                  'Nested:0:33: (', +                  children = { +                    { +                      'Call:0:48:(', +                      children = { +                        { +                          'Call:0:44:(', +                          children = { +                            { +                              'Nested:0:35:(', +                              children = { +                                { +                                  'UnaryPlus:0:36:+', +                                  children = { +                                    { +                                      'Call:0:39:(', +                                      children = { +                                        'Register(name=g):0:37:@g', +                                        'Register(name=h):0:40:@h', +                                      }, +                                    }, +                                  }, +                                }, +                              }, +                            }, +                            'Register(name=j):0:45:@j', +                          }, +                        }, +                        'Register(name=k):0:49:@k', +                      }, +                    }, +                  }, +                }, +                'Register(name=l):0:54:@l', +              }, +            }, +          }, +        }}, +      }, { +        hl('Register', '@a'), +        hl('BinaryPlus', '+', 1), +        hl('NestingParenthesis', '(', 1), +        hl('Register', '@b'), +        hl('BinaryPlus', '+', 1), +        hl('Register', '@c', 1), +        hl('NestingParenthesis', ')'), +        hl('BinaryPlus', '+', 1), +        hl('Register', '@d', 1), +        hl('CallingParenthesis', '('), +        hl('Register', '@e'), +        hl('CallingParenthesis', ')'), +        hl('BinaryPlus', '+', 1), +        hl('NestingParenthesis', '(', 1), +        hl('UnaryPlus', '+'), +        hl('Register', '@f'), +        hl('NestingParenthesis', ')'), +        hl('BinaryPlus', '+', 1), +        hl('NestingParenthesis', '(', 1), +        hl('NestingParenthesis', '('), +        hl('UnaryPlus', '+'), +        hl('Register', '@g'), +        hl('CallingParenthesis', '('), +        hl('Register', '@h'), +        hl('CallingParenthesis', ')'), +        hl('NestingParenthesis', ')'), +        hl('CallingParenthesis', '('), +        hl('Register', '@j'), +        hl('CallingParenthesis', ')'), +        hl('CallingParenthesis', '('), +        hl('Register', '@k'), +        hl('CallingParenthesis', ')'), +        hl('NestingParenthesis', ')'), +        hl('CallingParenthesis', '('), +        hl('Register', '@l'), +        hl('CallingParenthesis', ')'), +      }) +    check_parsing('@a)', { +      --           012 +      ast = { +        { +          'Nested:0:2:', +          children = { +            'Register(name=a):0:0:@a', +          }, +        }, +      }, +      err = { +        arg = ')', +        msg = 'E15: Unexpected closing parenthesis: %.*s', +      }, +    }, { +      hl('Register', '@a'), +      hl('InvalidNestingParenthesis', ')'), +    }) +    check_parsing('(@a', { +      --           012 +      ast = { +        { +          'Nested:0:0:(', +          children = { +            'Register(name=a):0:1:@a', +          }, +        }, +      }, +      err = { +        arg = '(@a', +        msg = 'E110: Missing closing parenthesis for nested expression: %.*s', +      }, +    }, { +      hl('NestingParenthesis', '('), +      hl('Register', '@a'), +    }) +    check_parsing('@a(@b', { +      --           01234 +      ast = { +        { +          'Call:0:2:(', +          children = { +            'Register(name=a):0:0:@a', +            'Register(name=b):0:3:@b', +          }, +        }, +      }, +      err = { +        arg = '(@b', +        msg = 'E116: Missing closing parenthesis for function call: %.*s', +      }, +    }, { +      hl('Register', '@a'), +      hl('CallingParenthesis', '('), +      hl('Register', '@b'), +    }) +    check_parsing('@a(@b, @c, @d, @e)', { +      --           012345678901234567 +      --           0         1 +      ast = { +        { +          'Call:0:2:(', +          children = { +            'Register(name=a):0:0:@a', +            { +              'Comma:0:5:,', +              children = { +                'Register(name=b):0:3:@b', +                { +                  'Comma:0:9:,', +                  children = { +                    'Register(name=c):0:6: @c', +                    { +                      'Comma:0:13:,', +                      children = { +                        'Register(name=d):0:10: @d', +                        'Register(name=e):0:14: @e', +                      }, +                    }, +                  }, +                }, +              }, +            }, +          }, +        }, +      }, +    }, { +      hl('Register', '@a'), +      hl('CallingParenthesis', '('), +      hl('Register', '@b'), +      hl('Comma', ','), +      hl('Register', '@c', 1), +      hl('Comma', ','), +      hl('Register', '@d', 1), +      hl('Comma', ','), +      hl('Register', '@e', 1), +      hl('CallingParenthesis', ')'), +    }) +    check_parsing('@a(@b(@c))', { +      --           01234567890123456789012345678901234567 +      --           0         1         2         3 +      ast = { +        { +          'Call:0:2:(', +          children = { +            'Register(name=a):0:0:@a', +            { +              'Call:0:5:(', +              children = { +                'Register(name=b):0:3:@b', +                'Register(name=c):0:6:@c', +              }, +            }, +          }, +        }, +      }, +    }, { +      hl('Register', '@a'), +      hl('CallingParenthesis', '('), +      hl('Register', '@b'), +      hl('CallingParenthesis', '('), +      hl('Register', '@c'), +      hl('CallingParenthesis', ')'), +      hl('CallingParenthesis', ')'), +    }) +    check_parsing('@a(@b(@c(@d(@e), @f(@g(@h), @i(@j)))))', { +      --           01234567890123456789012345678901234567 +      --           0         1         2         3 +      ast = { +        { +          'Call:0:2:(', +          children = { +            'Register(name=a):0:0:@a', +            { +              'Call:0:5:(', +              children = { +                'Register(name=b):0:3:@b', +                { +                  'Call:0:8:(', +                  children = { +                    'Register(name=c):0:6:@c', +                    { +                      'Comma:0:15:,', +                      children = { +                        { +                          'Call:0:11:(', +                          children = { +                            'Register(name=d):0:9:@d', +                            'Register(name=e):0:12:@e', +                          }, +                        }, +                        { +                          'Call:0:19:(', +                          children = { +                            'Register(name=f):0:16: @f', +                            { +                              'Comma:0:26:,', +                              children = { +                                { +                                  'Call:0:22:(', +                                  children = { +                                    'Register(name=g):0:20:@g', +                                    'Register(name=h):0:23:@h', +                                  }, +                                }, +                                { +                                  'Call:0:30:(', +                                  children = { +                                    'Register(name=i):0:27: @i', +                                    'Register(name=j):0:31:@j', +                                  }, +                                }, +                              }, +                            }, +                          }, +                        }, +                      }, +                    }, +                  }, +                }, +              }, +            }, +          }, +        }, +      }, +    }, { +      hl('Register', '@a'), +      hl('CallingParenthesis', '('), +      hl('Register', '@b'), +      hl('CallingParenthesis', '('), +      hl('Register', '@c'), +      hl('CallingParenthesis', '('), +      hl('Register', '@d'), +      hl('CallingParenthesis', '('), +      hl('Register', '@e'), +      hl('CallingParenthesis', ')'), +      hl('Comma', ','), +      hl('Register', '@f', 1), +      hl('CallingParenthesis', '('), +      hl('Register', '@g'), +      hl('CallingParenthesis', '('), +      hl('Register', '@h'), +      hl('CallingParenthesis', ')'), +      hl('Comma', ','), +      hl('Register', '@i', 1), +      hl('CallingParenthesis', '('), +      hl('Register', '@j'), +      hl('CallingParenthesis', ')'), +      hl('CallingParenthesis', ')'), +      hl('CallingParenthesis', ')'), +      hl('CallingParenthesis', ')'), +      hl('CallingParenthesis', ')'), +    }) +    check_parsing('()()', { +      --           0123 +      ast = { +        { +          'Call:0:2:(', +          children = { +            { +              'Nested:0:0:(', +              children = { +                'Missing:0:1:', +              }, +            }, +          }, +        }, +      }, +      err = { +        arg = ')()', +        msg = 'E15: Expected value, got parenthesis: %.*s', +      }, +    }, { +      hl('NestingParenthesis', '('), +      hl('InvalidNestingParenthesis', ')'), +      hl('CallingParenthesis', '('), +      hl('CallingParenthesis', ')'), +    }) +    check_parsing('(@a)()', { +      --           012345 +      ast = { +        { +          'Call:0:4:(', +          children = { +            { +              'Nested:0:0:(', +              children = { +                'Register(name=a):0:1:@a', +              }, +            }, +          }, +        }, +      }, +    }, { +      hl('NestingParenthesis', '('), +      hl('Register', '@a'), +      hl('NestingParenthesis', ')'), +      hl('CallingParenthesis', '('), +      hl('CallingParenthesis', ')'), +    }) +    check_parsing('(@a)(@b)', { +      --           01234567 +      ast = { +        { +          'Call:0:4:(', +          children = { +            { +              'Nested:0:0:(', +              children = { +                'Register(name=a):0:1:@a', +              }, +            }, +            'Register(name=b):0:5:@b', +          }, +        }, +      }, +    }, { +      hl('NestingParenthesis', '('), +      hl('Register', '@a'), +      hl('NestingParenthesis', ')'), +      hl('CallingParenthesis', '('), +      hl('Register', '@b'), +      hl('CallingParenthesis', ')'), +    }) +    check_parsing('(@a) (@b)', { +      --           012345678 +      ast = { +        { +          'OpMissing:0:4:', +          children = { +            { +              'Nested:0:0:(', +              children = { +                'Register(name=a):0:1:@a', +              }, +            }, +            { +              'Nested:0:4: (', +              children = { +                'Register(name=b):0:6:@b', +              }, +            }, +          }, +        }, +      }, +      err = { +        arg = '(@b)', +        msg = 'E15: Missing operator: %.*s', +      }, +    }, { +      hl('NestingParenthesis', '('), +      hl('Register', '@a'), +      hl('NestingParenthesis', ')'), +      hl('InvalidSpacing', ' '), +      hl('NestingParenthesis', '('), +      hl('Register', '@b'), +      hl('NestingParenthesis', ')'), +    }, { +      [1] = { +        ast = { +          len = 5, +          ast = { +            { +              'Nested:0:0:(', +              children = { +                'Register(name=a):0:1:@a', +                REMOVE_THIS, +              }, +            }, +          }, +          err = REMOVE_THIS, +        }, +        hl_fs = { +          [4] = REMOVE_THIS, +          [5] = REMOVE_THIS, +          [6] = REMOVE_THIS, +          [7] = REMOVE_THIS, +        }, +      }, +    }) +  end) +  itp('works with variable names, including curly braces ones', function() +    check_parsing('var', { +        ast = { +          'PlainIdentifier(scope=0,ident=var):0:0:var', +        }, +    }, { +      hl('IdentifierName', 'var'), +    }) +    check_parsing('g:var', { +        ast = { +          'PlainIdentifier(scope=g,ident=var):0:0:g:var', +        }, +    }, { +      hl('IdentifierScope', 'g'), +      hl('IdentifierScopeDelimiter', ':'), +      hl('IdentifierName', 'var'), +    }) +    check_parsing('g:', { +        ast = { +          'PlainIdentifier(scope=g,ident=):0:0:g:', +        }, +    }, { +      hl('IdentifierScope', 'g'), +      hl('IdentifierScopeDelimiter', ':'), +    }) +    check_parsing('{a}', { +      --           012 +      ast = { +        { +          fmtn('CurlyBracesIdentifier', '-di', ':0:0:{'), +          children = { +            'PlainIdentifier(scope=0,ident=a):0:1:a', +          }, +        }, +      }, +    }, { +      hl('Curly', '{'), +      hl('IdentifierName', 'a'), +      hl('Curly', '}'), +    }) +    check_parsing('{a:b}', { +      --           012 +      ast = { +        { +          fmtn('CurlyBracesIdentifier', '-di', ':0:0:{'), +          children = { +            'PlainIdentifier(scope=a,ident=b):0:1:a:b', +          }, +        }, +      }, +    }, { +      hl('Curly', '{'), +      hl('IdentifierScope', 'a'), +      hl('IdentifierScopeDelimiter', ':'), +      hl('IdentifierName', 'b'), +      hl('Curly', '}'), +    }) +    check_parsing('{a:@b}', { +      --           012345 +      ast = { +        { +          fmtn('CurlyBracesIdentifier', '-di', ':0:0:{'), +          children = { +            { +              'OpMissing:0:3:', +              children={ +                'PlainIdentifier(scope=a,ident=):0:1:a:', +                'Register(name=b):0:3:@b', +              }, +            }, +          }, +        }, +      }, +      err = { +        arg = '@b}', +        msg = 'E15: Missing operator: %.*s', +      }, +    }, { +      hl('Curly', '{'), +      hl('IdentifierScope', 'a'), +      hl('IdentifierScopeDelimiter', ':'), +      hl('InvalidRegister', '@b'), +      hl('Curly', '}'), +    }) +    check_parsing('{@a}', { +      ast = { +        { +          fmtn('CurlyBracesIdentifier', '-di', ':0:0:{'), +          children = { +            'Register(name=a):0:1:@a', +          }, +        }, +      }, +    }, { +      hl('Curly', '{'), +      hl('Register', '@a'), +      hl('Curly', '}'), +    }) +    check_parsing('{@a}{@b}', { +      --           01234567 +      ast = { +        { +          'ComplexIdentifier:0:4:', +          children = { +            { +              fmtn('CurlyBracesIdentifier', '-di', ':0:0:{'), +              children = { +                'Register(name=a):0:1:@a', +              }, +            }, +            { +              fmtn('CurlyBracesIdentifier', '--i', ':0:4:{'), +              children = { +                'Register(name=b):0:5:@b', +              }, +            }, +          }, +        }, +      }, +    }, { +      hl('Curly', '{'), +      hl('Register', '@a'), +      hl('Curly', '}'), +      hl('Curly', '{'), +      hl('Register', '@b'), +      hl('Curly', '}'), +    }) +    check_parsing('g:{@a}', { +      --           01234567 +      ast = { +        { +          'ComplexIdentifier:0:2:', +          children = { +            'PlainIdentifier(scope=g,ident=):0:0:g:', +            { +              fmtn('CurlyBracesIdentifier', '--i', ':0:2:{'), +              children = { +                'Register(name=a):0:3:@a', +              }, +            }, +          }, +        }, +      }, +    }, { +      hl('IdentifierScope', 'g'), +      hl('IdentifierScopeDelimiter', ':'), +      hl('Curly', '{'), +      hl('Register', '@a'), +      hl('Curly', '}'), +    }) +    check_parsing('{@a}_test', { +      --           012345678 +      ast = { +        { +          'ComplexIdentifier:0:4:', +          children = { +            { +              fmtn('CurlyBracesIdentifier', '-di', ':0:0:{'), +              children = { +                'Register(name=a):0:1:@a', +              }, +            }, +            'PlainIdentifier(scope=0,ident=_test):0:4:_test', +          }, +        }, +      }, +    }, { +      hl('Curly', '{'), +      hl('Register', '@a'), +      hl('Curly', '}'), +      hl('IdentifierName', '_test'), +    }) +    check_parsing('g:{@a}_test', { +      --           01234567890 +      ast = { +        { +          'ComplexIdentifier:0:2:', +          children = { +            'PlainIdentifier(scope=g,ident=):0:0:g:', +            { +              'ComplexIdentifier:0:6:', +              children = { +                { +                  fmtn('CurlyBracesIdentifier', '--i', ':0:2:{'), +                  children = { +                    'Register(name=a):0:3:@a', +                  }, +                }, +                'PlainIdentifier(scope=0,ident=_test):0:6:_test', +              }, +            }, +          }, +        }, +      }, +    }, { +      hl('IdentifierScope', 'g'), +      hl('IdentifierScopeDelimiter', ':'), +      hl('Curly', '{'), +      hl('Register', '@a'), +      hl('Curly', '}'), +      hl('IdentifierName', '_test'), +    }) +    check_parsing('g:{@a}_test()', { +      --           0123456789012 +      ast = { +        { +          'Call:0:11:(', +          children = { +            { +              'ComplexIdentifier:0:2:', +              children = { +                'PlainIdentifier(scope=g,ident=):0:0:g:', +                { +                  'ComplexIdentifier:0:6:', +                  children = { +                    { +                      fmtn('CurlyBracesIdentifier', '--i', ':0:2:{'), +                      children = { +                        'Register(name=a):0:3:@a', +                      }, +                    }, +                    'PlainIdentifier(scope=0,ident=_test):0:6:_test', +                  }, +                }, +              }, +            }, +          }, +        }, +      }, +    }, { +      hl('IdentifierScope', 'g'), +      hl('IdentifierScopeDelimiter', ':'), +      hl('Curly', '{'), +      hl('Register', '@a'), +      hl('Curly', '}'), +      hl('IdentifierName', '_test'), +      hl('CallingParenthesis', '('), +      hl('CallingParenthesis', ')'), +    }) +    check_parsing('{@a} ()', { +      --           0123456789012 +      ast = { +        { +          'Call:0:4: (', +          children = { +            { +              fmtn('CurlyBracesIdentifier', '-di', ':0:0:{'), +              children = { +                'Register(name=a):0:1:@a', +              }, +            }, +          }, +        }, +      }, +    }, { +      hl('Curly', '{'), +      hl('Register', '@a'), +      hl('Curly', '}'), +      hl('CallingParenthesis', '(', 1), +      hl('CallingParenthesis', ')'), +    }) +    check_parsing('g:{@a} ()', { +      --           0123456789012 +      ast = { +        { +          'Call:0:6: (', +          children = { +            { +              'ComplexIdentifier:0:2:', +              children = { +                'PlainIdentifier(scope=g,ident=):0:0:g:', +                { +                  fmtn('CurlyBracesIdentifier', '--i', ':0:2:{'), +                  children = { +                    'Register(name=a):0:3:@a', +                  }, +                }, +              }, +            }, +          }, +        }, +      }, +    }, { +      hl('IdentifierScope', 'g'), +      hl('IdentifierScopeDelimiter', ':'), +      hl('Curly', '{'), +      hl('Register', '@a'), +      hl('Curly', '}'), +      hl('CallingParenthesis', '(', 1), +      hl('CallingParenthesis', ')'), +    }) +    check_parsing('{@a', { +      --           012 +      ast = { +        { +          fmtn('UnknownFigure', '-di', ':0:0:{'), +          children = { +            'Register(name=a):0:1:@a', +          }, +        }, +      }, +      err = { +        arg = '{@a', +        msg = 'E15: Missing closing figure brace: %.*s', +      }, +    }, { +      hl('FigureBrace', '{'), +      hl('Register', '@a'), +    }) +    check_parsing('a ()', { +      --           0123 +      ast = { +        { +          'Call:0:1: (', +          children = { +            'PlainIdentifier(scope=0,ident=a):0:0:a', +          }, +        }, +      }, +    }, { +      hl('IdentifierName', 'a'), +      hl('CallingParenthesis', '(', 1), +      hl('CallingParenthesis', ')'), +    }) +  end) +  itp('works with lambdas and dictionaries', function() +    check_parsing('{}', { +      ast = { +        fmtn('DictLiteral', '-di', ':0:0:{'), +      }, +    }, { +      hl('Dict', '{'), +      hl('Dict', '}'), +    }) +    check_parsing('{->@a}', { +      ast = { +        { +          fmtn('Lambda', '\\di', ':0:0:{'), +          children = { +            { +              'Arrow:0:1:->', +              children = { +                'Register(name=a):0:3:@a', +              }, +            }, +          }, +        }, +      }, +    }, { +      hl('Lambda', '{'), +      hl('Arrow', '->'), +      hl('Register', '@a'), +      hl('Lambda', '}'), +    }) +    check_parsing('{->@a+@b}', { +      --           012345678 +      ast = { +        { +          fmtn('Lambda', '\\di', ':0:0:{'), +          children = { +            { +              'Arrow:0:1:->', +              children = { +                { +                  'BinaryPlus:0:5:+', +                  children = { +                    'Register(name=a):0:3:@a', +                    'Register(name=b):0:6:@b', +                  }, +                }, +              }, +            }, +          }, +        }, +      }, +    }, { +      hl('Lambda', '{'), +      hl('Arrow', '->'), +      hl('Register', '@a'), +      hl('BinaryPlus', '+'), +      hl('Register', '@b'), +      hl('Lambda', '}'), +    }) +    check_parsing('{a->@a}', { +      --           012345678 +      ast = { +        { +          fmtn('Lambda', '\\di', ':0:0:{'), +          children = { +            'PlainIdentifier(scope=0,ident=a):0:1:a', +            { +              'Arrow:0:2:->', +              children = { +                'Register(name=a):0:4:@a', +              }, +            }, +          }, +        }, +      }, +    }, { +      hl('Lambda', '{'), +      hl('IdentifierName', 'a'), +      hl('Arrow', '->'), +      hl('Register', '@a'), +      hl('Lambda', '}'), +    }) +    check_parsing('{a,b->@a}', { +      --           012345678 +      ast = { +        { +          fmtn('Lambda', '\\di', ':0:0:{'), +          children = { +            { +              'Comma:0:2:,', +              children = { +                'PlainIdentifier(scope=0,ident=a):0:1:a', +                'PlainIdentifier(scope=0,ident=b):0:3:b', +              }, +            }, +            { +              'Arrow:0:4:->', +              children = { +                'Register(name=a):0:6:@a', +              }, +            }, +          }, +        }, +      }, +    }, { +      hl('Lambda', '{'), +      hl('IdentifierName', 'a'), +      hl('Comma', ','), +      hl('IdentifierName', 'b'), +      hl('Arrow', '->'), +      hl('Register', '@a'), +      hl('Lambda', '}'), +    }) +    check_parsing('{a,b,c->@a}', { +      --           01234567890 +      ast = { +        { +          fmtn('Lambda', '\\di', ':0:0:{'), +          children = { +            { +              'Comma:0:2:,', +              children = { +                'PlainIdentifier(scope=0,ident=a):0:1:a', +                { +                  'Comma:0:4:,', +                  children = { +                    'PlainIdentifier(scope=0,ident=b):0:3:b', +                    'PlainIdentifier(scope=0,ident=c):0:5:c', +                  }, +                }, +              }, +            }, +            { +              'Arrow:0:6:->', +              children = { +                'Register(name=a):0:8:@a', +              }, +            }, +          }, +        }, +      }, +    }, { +      hl('Lambda', '{'), +      hl('IdentifierName', 'a'), +      hl('Comma', ','), +      hl('IdentifierName', 'b'), +      hl('Comma', ','), +      hl('IdentifierName', 'c'), +      hl('Arrow', '->'), +      hl('Register', '@a'), +      hl('Lambda', '}'), +    }) +    check_parsing('{a,b,c,d->@a}', { +      --           0123456789012 +      ast = { +        { +          fmtn('Lambda', '\\di', ':0:0:{'), +          children = { +            { +              'Comma:0:2:,', +              children = { +                'PlainIdentifier(scope=0,ident=a):0:1:a', +                { +                  'Comma:0:4:,', +                  children = { +                    'PlainIdentifier(scope=0,ident=b):0:3:b', +                    { +                      'Comma:0:6:,', +                      children = { +                        'PlainIdentifier(scope=0,ident=c):0:5:c', +                        'PlainIdentifier(scope=0,ident=d):0:7:d', +                      }, +                    }, +                  }, +                }, +              }, +            }, +            { +              'Arrow:0:8:->', +              children = { +                'Register(name=a):0:10:@a', +              }, +            }, +          }, +        }, +      }, +    }, { +      hl('Lambda', '{'), +      hl('IdentifierName', 'a'), +      hl('Comma', ','), +      hl('IdentifierName', 'b'), +      hl('Comma', ','), +      hl('IdentifierName', 'c'), +      hl('Comma', ','), +      hl('IdentifierName', 'd'), +      hl('Arrow', '->'), +      hl('Register', '@a'), +      hl('Lambda', '}'), +    }) +    check_parsing('{a,b,c,d,->@a}', { +      --           01234567890123 +      ast = { +        { +          fmtn('Lambda', '\\di', ':0:0:{'), +          children = { +            { +              'Comma:0:2:,', +              children = { +                'PlainIdentifier(scope=0,ident=a):0:1:a', +                { +                  'Comma:0:4:,', +                  children = { +                    'PlainIdentifier(scope=0,ident=b):0:3:b', +                    { +                      'Comma:0:6:,', +                      children = { +                        'PlainIdentifier(scope=0,ident=c):0:5:c', +                        { +                          'Comma:0:8:,', +                          children = { +                            'PlainIdentifier(scope=0,ident=d):0:7:d', +                          }, +                        }, +                      }, +                    }, +                  }, +                }, +              }, +            }, +            { +              'Arrow:0:9:->', +              children = { +                'Register(name=a):0:11:@a', +              }, +            }, +          }, +        }, +      }, +    }, { +      hl('Lambda', '{'), +      hl('IdentifierName', 'a'), +      hl('Comma', ','), +      hl('IdentifierName', 'b'), +      hl('Comma', ','), +      hl('IdentifierName', 'c'), +      hl('Comma', ','), +      hl('IdentifierName', 'd'), +      hl('Comma', ','), +      hl('Arrow', '->'), +      hl('Register', '@a'), +      hl('Lambda', '}'), +    }) +    check_parsing('{a,b->{c,d->{e,f->@a}}}', { +      --           01234567890123456789012 +      --           0         1         2 +      ast = { +        { +          fmtn('Lambda', '\\di', ':0:0:{'), +          children = { +            { +              'Comma:0:2:,', +              children = { +                'PlainIdentifier(scope=0,ident=a):0:1:a', +                'PlainIdentifier(scope=0,ident=b):0:3:b', +              }, +            }, +            { +              'Arrow:0:4:->', +              children = { +                { +                  fmtn('Lambda', '\\di', ':0:6:{'), +                  children = { +                    { +                      'Comma:0:8:,', +                      children = { +                        'PlainIdentifier(scope=0,ident=c):0:7:c', +                        'PlainIdentifier(scope=0,ident=d):0:9:d', +                      }, +                    }, +                    { +                      'Arrow:0:10:->', +                      children = { +                        { +                          fmtn('Lambda', '\\di', ':0:12:{'), +                          children = { +                            { +                              'Comma:0:14:,', +                              children = { +                                'PlainIdentifier(scope=0,ident=e):0:13:e', +                                'PlainIdentifier(scope=0,ident=f):0:15:f', +                              }, +                            }, +                            { +                              'Arrow:0:16:->', +                              children = { +                                'Register(name=a):0:18:@a', +                              }, +                            }, +                          }, +                        }, +                      }, +                    }, +                  }, +                }, +              }, +            }, +          }, +        }, +      }, +    }, { +      hl('Lambda', '{'), +      hl('IdentifierName', 'a'), +      hl('Comma', ','), +      hl('IdentifierName', 'b'), +      hl('Arrow', '->'), +      hl('Lambda', '{'), +      hl('IdentifierName', 'c'), +      hl('Comma', ','), +      hl('IdentifierName', 'd'), +      hl('Arrow', '->'), +      hl('Lambda', '{'), +      hl('IdentifierName', 'e'), +      hl('Comma', ','), +      hl('IdentifierName', 'f'), +      hl('Arrow', '->'), +      hl('Register', '@a'), +      hl('Lambda', '}'), +      hl('Lambda', '}'), +      hl('Lambda', '}'), +    }) +    check_parsing('{a,b->c,d}', { +      --           0123456789 +      ast = { +        { +          fmtn('Lambda', '\\di', ':0:0:{'), +          children = { +            { +              'Comma:0:2:,', +              children = { +                'PlainIdentifier(scope=0,ident=a):0:1:a', +                'PlainIdentifier(scope=0,ident=b):0:3:b', +              }, +            }, +            { +              'Arrow:0:4:->', +              children = { +                { +                  'Comma:0:7:,', +                  children = { +                    'PlainIdentifier(scope=0,ident=c):0:6:c', +                    'PlainIdentifier(scope=0,ident=d):0:8:d', +                  }, +                }, +              }, +            }, +          }, +        }, +      }, +      err = { +        arg = ',d}', +        msg = 'E15: Comma outside of call, lambda or literal: %.*s', +      }, +    }, { +      hl('Lambda', '{'), +      hl('IdentifierName', 'a'), +      hl('Comma', ','), +      hl('IdentifierName', 'b'), +      hl('Arrow', '->'), +      hl('IdentifierName', 'c'), +      hl('InvalidComma', ','), +      hl('IdentifierName', 'd'), +      hl('Lambda', '}'), +    }) +    check_parsing('a,b,c,d', { +      --           0123456789 +      ast = { +        { +          'Comma:0:1:,', +          children = { +            'PlainIdentifier(scope=0,ident=a):0:0:a', +            { +              'Comma:0:3:,', +              children = { +              'PlainIdentifier(scope=0,ident=b):0:2:b', +                { +                  'Comma:0:5:,', +                  children = { +                    'PlainIdentifier(scope=0,ident=c):0:4:c', +                    'PlainIdentifier(scope=0,ident=d):0:6:d', +                  }, +                }, +              }, +            }, +          }, +        }, +      }, +      err = { +        arg = ',b,c,d', +        msg = 'E15: Comma outside of call, lambda or literal: %.*s', +      }, +    }, { +      hl('IdentifierName', 'a'), +      hl('InvalidComma', ','), +      hl('IdentifierName', 'b'), +      hl('InvalidComma', ','), +      hl('IdentifierName', 'c'), +      hl('InvalidComma', ','), +      hl('IdentifierName', 'd'), +    }) +    check_parsing('a,b,c,d,', { +      --           0123456789 +      ast = { +        { +          'Comma:0:1:,', +          children = { +            'PlainIdentifier(scope=0,ident=a):0:0:a', +            { +              'Comma:0:3:,', +              children = { +              'PlainIdentifier(scope=0,ident=b):0:2:b', +                { +                  'Comma:0:5:,', +                  children = { +                    'PlainIdentifier(scope=0,ident=c):0:4:c', +                    { +                      'Comma:0:7:,', +                      children = { +                        'PlainIdentifier(scope=0,ident=d):0:6:d', +                      }, +                    }, +                  }, +                }, +              }, +            }, +          }, +        }, +      }, +      err = { +        arg = ',b,c,d,', +        msg = 'E15: Comma outside of call, lambda or literal: %.*s', +      }, +    }, { +      hl('IdentifierName', 'a'), +      hl('InvalidComma', ','), +      hl('IdentifierName', 'b'), +      hl('InvalidComma', ','), +      hl('IdentifierName', 'c'), +      hl('InvalidComma', ','), +      hl('IdentifierName', 'd'), +      hl('InvalidComma', ','), +    }) +    check_parsing(',', { +      --           0123456789 +      ast = { +        { +          'Comma:0:0:,', +          children = { +            'Missing:0:0:', +          }, +        }, +      }, +      err = { +        arg = ',', +        msg = 'E15: Expected value, got comma: %.*s', +      }, +    }, { +      hl('InvalidComma', ','), +    }) +    check_parsing('{,a->@a}', { +      --           0123456789 +      ast = { +        { +          fmtn('CurlyBracesIdentifier', '-di', ':0:0:{'), +          children = { +            { +              'Arrow:0:3:->', +              children = { +                { +                  'Comma:0:1:,', +                  children = { +                    'Missing:0:1:', +                    'PlainIdentifier(scope=0,ident=a):0:2:a', +                  }, +                }, +                'Register(name=a):0:5:@a', +              }, +            }, +          }, +        }, +      }, +      err = { +        arg = ',a->@a}', +        msg = 'E15: Expected value, got comma: %.*s', +      }, +    }, { +      hl('Curly', '{'), +      hl('InvalidComma', ','), +      hl('IdentifierName', 'a'), +      hl('InvalidArrow', '->'), +      hl('Register', '@a'), +      hl('Curly', '}'), +    }) +    check_parsing('}', { +      --           0123456789 +      ast = { +        fmtn('UnknownFigure', '---', ':0:0:'), +      }, +      err = { +        arg = '}', +        msg = 'E15: Unexpected closing figure brace: %.*s', +      }, +    }, { +      hl('InvalidFigureBrace', '}'), +    }) +    check_parsing('{->}', { +      --           0123456789 +      ast = { +        { +          fmtn('Lambda', '\\di', ':0:0:{'), +          children = { +            'Arrow:0:1:->', +          }, +        }, +      }, +      err = { +        arg = '}', +        msg = 'E15: Expected value, got closing figure brace: %.*s', +      }, +    }, { +      hl('Lambda', '{'), +      hl('Arrow', '->'), +      hl('InvalidLambda', '}'), +    }) +    check_parsing('{a,b}', { +      --           0123456789 +      ast = { +        { +          fmtn('Lambda', '-di', ':0:0:{'), +          children = { +            { +              'Comma:0:2:,', +              children = { +                'PlainIdentifier(scope=0,ident=a):0:1:a', +                'PlainIdentifier(scope=0,ident=b):0:3:b', +              }, +            }, +          }, +        }, +      }, +      err = { +        arg = '}', +        msg = 'E15: Expected lambda arguments list or arrow: %.*s', +      }, +    }, { +      hl('Lambda', '{'), +      hl('IdentifierName', 'a'), +      hl('Comma', ','), +      hl('IdentifierName', 'b'), +      hl('InvalidLambda', '}'), +    }) +    check_parsing('{a,}', { +      --           0123456789 +      ast = { +        { +          fmtn('Lambda', '-di', ':0:0:{'), +          children = { +            { +              'Comma:0:2:,', +              children = { +                'PlainIdentifier(scope=0,ident=a):0:1:a', +              }, +            }, +          }, +        }, +      }, +      err = { +        arg = '}', +        msg = 'E15: Expected lambda arguments list or arrow: %.*s', +      }, +    }, { +      hl('Lambda', '{'), +      hl('IdentifierName', 'a'), +      hl('Comma', ','), +      hl('InvalidLambda', '}'), +    }) +    check_parsing('{@a:@b}', { +      --           0123456789 +      ast = { +        { +          fmtn('DictLiteral', '-di', ':0:0:{'), +          children = { +            { +              'Colon:0:3::', +              children = { +                'Register(name=a):0:1:@a', +                'Register(name=b):0:4:@b', +              }, +            }, +          }, +        }, +      }, +    }, { +      hl('Dict', '{'), +      hl('Register', '@a'), +      hl('Colon', ':'), +      hl('Register', '@b'), +      hl('Dict', '}'), +    }) +    check_parsing('{@a:@b,@c:@d}', { +      --           0123456789012 +      --           0         1 +      ast = { +        { +          fmtn('DictLiteral', '-di', ':0:0:{'), +          children = { +            { +              'Comma:0:6:,', +              children = { +                { +                  'Colon:0:3::', +                  children = { +                    'Register(name=a):0:1:@a', +                    'Register(name=b):0:4:@b', +                  }, +                }, +                { +                  'Colon:0:9::', +                  children = { +                    'Register(name=c):0:7:@c', +                    'Register(name=d):0:10:@d', +                  }, +                }, +              }, +            }, +          }, +        }, +      }, +    }, { +      hl('Dict', '{'), +      hl('Register', '@a'), +      hl('Colon', ':'), +      hl('Register', '@b'), +      hl('Comma', ','), +      hl('Register', '@c'), +      hl('Colon', ':'), +      hl('Register', '@d'), +      hl('Dict', '}'), +    }) +    check_parsing('{@a:@b,@c:@d,@e:@f,}', { +      --           01234567890123456789 +      --           0         1 +      ast = { +        { +          fmtn('DictLiteral', '-di', ':0:0:{'), +          children = { +            { +              'Comma:0:6:,', +              children = { +                { +                  'Colon:0:3::', +                  children = { +                    'Register(name=a):0:1:@a', +                    'Register(name=b):0:4:@b', +                  }, +                }, +                { +                  'Comma:0:12:,', +                  children = { +                    { +                      'Colon:0:9::', +                      children = { +                        'Register(name=c):0:7:@c', +                        'Register(name=d):0:10:@d', +                      }, +                    }, +                    { +                      'Comma:0:18:,', +                      children = { +                        { +                          'Colon:0:15::', +                          children = { +                            'Register(name=e):0:13:@e', +                            'Register(name=f):0:16:@f', +                          }, +                        }, +                      }, +                    }, +                  }, +                }, +              }, +            }, +          }, +        }, +      }, +    }, { +      hl('Dict', '{'), +      hl('Register', '@a'), +      hl('Colon', ':'), +      hl('Register', '@b'), +      hl('Comma', ','), +      hl('Register', '@c'), +      hl('Colon', ':'), +      hl('Register', '@d'), +      hl('Comma', ','), +      hl('Register', '@e'), +      hl('Colon', ':'), +      hl('Register', '@f'), +      hl('Comma', ','), +      hl('Dict', '}'), +    }) +    check_parsing('{@a:@b,@c:@d,@e:@f,@g:}', { +      --           01234567890123456789012 +      --           0         1         2 +      ast = { +        { +          fmtn('DictLiteral', '-di', ':0:0:{'), +          children = { +            { +              'Comma:0:6:,', +              children = { +                { +                  'Colon:0:3::', +                  children = { +                    'Register(name=a):0:1:@a', +                    'Register(name=b):0:4:@b', +                  }, +                }, +                { +                  'Comma:0:12:,', +                  children = { +                    { +                      'Colon:0:9::', +                      children = { +                        'Register(name=c):0:7:@c', +                        'Register(name=d):0:10:@d', +                      }, +                    }, +                    { +                      'Comma:0:18:,', +                      children = { +                        { +                          'Colon:0:15::', +                          children = { +                            'Register(name=e):0:13:@e', +                            'Register(name=f):0:16:@f', +                          }, +                        }, +                        { +                          'Colon:0:21::', +                          children = { +                            'Register(name=g):0:19:@g', +                          }, +                        }, +                      }, +                    }, +                  }, +                }, +              }, +            }, +          }, +        }, +      }, +      err = { +        arg = '}', +        msg = 'E15: Expected value, got closing figure brace: %.*s', +      }, +    }, { +      hl('Dict', '{'), +      hl('Register', '@a'), +      hl('Colon', ':'), +      hl('Register', '@b'), +      hl('Comma', ','), +      hl('Register', '@c'), +      hl('Colon', ':'), +      hl('Register', '@d'), +      hl('Comma', ','), +      hl('Register', '@e'), +      hl('Colon', ':'), +      hl('Register', '@f'), +      hl('Comma', ','), +      hl('Register', '@g'), +      hl('Colon', ':'), +      hl('InvalidDict', '}'), +    }) +    check_parsing('{@a:@b,}', { +      --           01234567890123 +      --           0         1 +      ast = { +        { +          fmtn('DictLiteral', '-di', ':0:0:{'), +          children = { +            { +              'Comma:0:6:,', +              children = { +                { +                  'Colon:0:3::', +                  children = { +                    'Register(name=a):0:1:@a', +                    'Register(name=b):0:4:@b', +                  }, +                }, +              }, +            }, +          }, +        }, +      }, +    }, { +      hl('Dict', '{'), +      hl('Register', '@a'), +      hl('Colon', ':'), +      hl('Register', '@b'), +      hl('Comma', ','), +      hl('Dict', '}'), +    }) +    check_parsing('{({f -> g})(@h)(@i)}', { +      --           01234567890123456789 +      --           0         1 +      ast = { +        { +          fmtn('CurlyBracesIdentifier', '-di', ':0:0:{'), +          children = { +            { +              'Call:0:15:(', +              children = { +                { +                  'Call:0:11:(', +                  children = { +                    { +                      'Nested:0:1:(', +                      children = { +                        { +                          fmtn('Lambda', '\\di', ':0:2:{'), +                          children = { +                            'PlainIdentifier(scope=0,ident=f):0:3:f', +                            { +                              'Arrow:0:4: ->', +                              children = { +                                'PlainIdentifier(scope=0,ident=g):0:7: g', +                              }, +                            }, +                          }, +                        }, +                      }, +                    }, +                    'Register(name=h):0:12:@h', +                  }, +                }, +                'Register(name=i):0:16:@i', +              }, +            }, +          }, +        }, +      }, +    }, { +      hl('Curly', '{'), +      hl('NestingParenthesis', '('), +      hl('Lambda', '{'), +      hl('IdentifierName', 'f'), +      hl('Arrow', '->', 1), +      hl('IdentifierName', 'g', 1), +      hl('Lambda', '}'), +      hl('NestingParenthesis', ')'), +      hl('CallingParenthesis', '('), +      hl('Register', '@h'), +      hl('CallingParenthesis', ')'), +      hl('CallingParenthesis', '('), +      hl('Register', '@i'), +      hl('CallingParenthesis', ')'), +      hl('Curly', '}'), +    }) +    check_parsing('a:{b()}c', { +      --           01234567 +      ast = { +        { +          'ComplexIdentifier:0:2:', +          children = { +            'PlainIdentifier(scope=a,ident=):0:0:a:', +            { +              'ComplexIdentifier:0:7:', +              children = { +                { +                  fmtn('CurlyBracesIdentifier', '--i', ':0:2:{'), +                  children = { +                    { +                      'Call:0:4:(', +                      children = { +                        'PlainIdentifier(scope=0,ident=b):0:3:b', +                      }, +                    }, +                  }, +                }, +                'PlainIdentifier(scope=0,ident=c):0:7:c', +              }, +            }, +          }, +        }, +      }, +    }, { +      hl('IdentifierScope', 'a'), +      hl('IdentifierScopeDelimiter', ':'), +      hl('Curly', '{'), +      hl('IdentifierName', 'b'), +      hl('CallingParenthesis', '('), +      hl('CallingParenthesis', ')'), +      hl('Curly', '}'), +      hl('IdentifierName', 'c'), +    }) +    check_parsing('a:{{b, c -> @d + @e + ({f -> g})(@h)}(@i)}j', { +      --           01234567890123456789012345678901234567890123456 +      --           0         1         2         3         4 +      ast = { +        { +          'ComplexIdentifier:0:2:', +          children = { +            'PlainIdentifier(scope=a,ident=):0:0:a:', +            { +              'ComplexIdentifier:0:42:', +              children = { +                { +                  fmtn('CurlyBracesIdentifier', '--i', ':0:2:{'), +                  children = { +                    { +                      'Call:0:37:(', +                      children = { +                        { +                          fmtn('Lambda', '\\di', ':0:3:{'), +                          children = { +                            { +                              'Comma:0:5:,', +                              children = { +                                'PlainIdentifier(scope=0,ident=b):0:4:b', +                                'PlainIdentifier(scope=0,ident=c):0:6: c', +                              }, +                            }, +                            { +                              'Arrow:0:8: ->', +                              children = { +                                { +                                  'BinaryPlus:0:19: +', +                                  children = { +                                    { +                                      'BinaryPlus:0:14: +', +                                      children = { +                                        'Register(name=d):0:11: @d', +                                        'Register(name=e):0:16: @e', +                                      }, +                                    }, +                                    { +                                      'Call:0:32:(', +                                      children = { +                                        { +                                          'Nested:0:21: (', +                                          children = { +                                            { +                                              fmtn('Lambda', '\\di', ':0:23:{'), +                                              children = { +                                                'PlainIdentifier(scope=0,ident=f):0:24:f', +                                                { +                                                  'Arrow:0:25: ->', +                                                  children = { +                                                    'PlainIdentifier(scope=0,ident=g):0:28: g', +                                                  }, +                                                }, +                                              }, +                                            }, +                                          }, +                                        }, +                                        'Register(name=h):0:33:@h', +                                      }, +                                    }, +                                  }, +                                }, +                              }, +                            }, +                          }, +                        }, +                        'Register(name=i):0:38:@i', +                      }, +                    }, +                  }, +                }, +                'PlainIdentifier(scope=0,ident=j):0:42:j', +              }, +            }, +          }, +        }, +      }, +    }, { +      hl('IdentifierScope', 'a'), +      hl('IdentifierScopeDelimiter', ':'), +      hl('Curly', '{'), +      hl('Lambda', '{'), +      hl('IdentifierName', 'b'), +      hl('Comma', ','), +      hl('IdentifierName', 'c', 1), +      hl('Arrow', '->', 1), +      hl('Register', '@d', 1), +      hl('BinaryPlus', '+', 1), +      hl('Register', '@e', 1), +      hl('BinaryPlus', '+', 1), +      hl('NestingParenthesis', '(', 1), +      hl('Lambda', '{'), +      hl('IdentifierName', 'f'), +      hl('Arrow', '->', 1), +      hl('IdentifierName', 'g', 1), +      hl('Lambda', '}'), +      hl('NestingParenthesis', ')'), +      hl('CallingParenthesis', '('), +      hl('Register', '@h'), +      hl('CallingParenthesis', ')'), +      hl('Lambda', '}'), +      hl('CallingParenthesis', '('), +      hl('Register', '@i'), +      hl('CallingParenthesis', ')'), +      hl('Curly', '}'), +      hl('IdentifierName', 'j'), +    }) +    check_parsing('{@a + @b : @c + @d, @e + @f : @g + @i}', { +      --           01234567890123456789012345678901234567 +      --           0         1         2         3 +      ast = { +        { +          fmtn('DictLiteral', '-di', ':0:0:{'), +          children = { +            { +              'Comma:0:18:,', +              children = { +                { +                  'Colon:0:8: :', +                  children = { +                    { +                      'BinaryPlus:0:3: +', +                      children = { +                        'Register(name=a):0:1:@a', +                        'Register(name=b):0:5: @b', +                      }, +                    }, +                    { +                      'BinaryPlus:0:13: +', +                      children = { +                        'Register(name=c):0:10: @c', +                        'Register(name=d):0:15: @d', +                      }, +                    }, +                  }, +                }, +                { +                  'Colon:0:27: :', +                  children = { +                    { +                      'BinaryPlus:0:22: +', +                      children = { +                        'Register(name=e):0:19: @e', +                        'Register(name=f):0:24: @f', +                      }, +                    }, +                    { +                      'BinaryPlus:0:32: +', +                      children = { +                        'Register(name=g):0:29: @g', +                        'Register(name=i):0:34: @i', +                      }, +                    }, +                  }, +                }, +              }, +            }, +          }, +        }, +      }, +    }, { +      hl('Dict', '{'), +      hl('Register', '@a'), +      hl('BinaryPlus', '+', 1), +      hl('Register', '@b', 1), +      hl('Colon', ':', 1), +      hl('Register', '@c', 1), +      hl('BinaryPlus', '+', 1), +      hl('Register', '@d', 1), +      hl('Comma', ','), +      hl('Register', '@e', 1), +      hl('BinaryPlus', '+', 1), +      hl('Register', '@f', 1), +      hl('Colon', ':', 1), +      hl('Register', '@g', 1), +      hl('BinaryPlus', '+', 1), +      hl('Register', '@i', 1), +      hl('Dict', '}'), +    }) +    check_parsing('-> -> ->', { +      --           01234567 +      ast = { +        { +          'Arrow:0:0:->', +          children = { +            'Missing:0:0:', +            { +              'Arrow:0:2: ->', +              children = { +                'Missing:0:2:', +                { +                  'Arrow:0:5: ->', +                  children = { +                    'Missing:0:5:', +                  }, +                }, +              }, +            }, +          }, +        }, +      }, +      err = { +        arg = '-> -> ->', +        msg = 'E15: Unexpected arrow: %.*s', +      }, +    }, { +      hl('InvalidArrow', '->'), +      hl('InvalidArrow', '->', 1), +      hl('InvalidArrow', '->', 1), +    }) +    check_parsing('a -> b -> c -> d', { +      --           0123456789012345 +      --           0         1 +      ast = { +        { +          'Arrow:0:1: ->', +          children = { +            'PlainIdentifier(scope=0,ident=a):0:0:a', +            { +              'Arrow:0:6: ->', +              children = { +                'PlainIdentifier(scope=0,ident=b):0:4: b', +                { +                  'Arrow:0:11: ->', +                  children = { +                    'PlainIdentifier(scope=0,ident=c):0:9: c', +                    'PlainIdentifier(scope=0,ident=d):0:14: d', +                  }, +                }, +              }, +            }, +          }, +        }, +      }, +      err = { +        arg = '-> b -> c -> d', +        msg = 'E15: Arrow outside of lambda: %.*s', +      }, +    }, { +      hl('IdentifierName', 'a'), +      hl('InvalidArrow', '->', 1), +      hl('IdentifierName', 'b', 1), +      hl('InvalidArrow', '->', 1), +      hl('IdentifierName', 'c', 1), +      hl('InvalidArrow', '->', 1), +      hl('IdentifierName', 'd', 1), +    }) +    check_parsing('{a -> b -> c}', { +      --           0123456789012 +      --           0         1 +      ast = { +        { +          fmtn('Lambda', '\\di', ':0:0:{'), +          children = { +            'PlainIdentifier(scope=0,ident=a):0:1:a', +            { +              'Arrow:0:2: ->', +              children = { +                { +                  'Arrow:0:7: ->', +                  children = { +                    'PlainIdentifier(scope=0,ident=b):0:5: b', +                    'PlainIdentifier(scope=0,ident=c):0:10: c', +                  }, +                }, +              }, +            }, +          }, +        }, +      }, +      err = { +        arg = '-> c}', +        msg = 'E15: Arrow outside of lambda: %.*s', +      }, +    }, { +      hl('Lambda', '{'), +      hl('IdentifierName', 'a'), +      hl('Arrow', '->', 1), +      hl('IdentifierName', 'b', 1), +      hl('InvalidArrow', '->', 1), +      hl('IdentifierName', 'c', 1), +      hl('Lambda', '}'), +    }) +    check_parsing('{a: -> b}', { +      --           012345678 +      ast = { +        { +          fmtn('CurlyBracesIdentifier', '-di', ':0:0:{'), +          children = { +            { +              'Arrow:0:3: ->', +              children = { +                'PlainIdentifier(scope=a,ident=):0:1:a:', +                'PlainIdentifier(scope=0,ident=b):0:6: b', +              }, +            }, +          }, +        }, +      }, +      err = { +        arg = '-> b}', +        msg = 'E15: Arrow outside of lambda: %.*s', +      }, +    }, { +      hl('Curly', '{'), +      hl('IdentifierScope', 'a'), +      hl('IdentifierScopeDelimiter', ':'), +      hl('InvalidArrow', '->', 1), +      hl('IdentifierName', 'b', 1), +      hl('Curly', '}'), +    }) + +    check_parsing('{a:b -> b}', { +      --           0123456789 +      ast = { +        { +          fmtn('CurlyBracesIdentifier', '-di', ':0:0:{'), +          children = { +            { +              'Arrow:0:4: ->', +              children = { +                'PlainIdentifier(scope=a,ident=b):0:1:a:b', +                'PlainIdentifier(scope=0,ident=b):0:7: b', +              }, +            }, +          }, +        }, +      }, +      err = { +        arg = '-> b}', +        msg = 'E15: Arrow outside of lambda: %.*s', +      }, +    }, { +      hl('Curly', '{'), +      hl('IdentifierScope', 'a'), +      hl('IdentifierScopeDelimiter', ':'), +      hl('IdentifierName', 'b'), +      hl('InvalidArrow', '->', 1), +      hl('IdentifierName', 'b', 1), +      hl('Curly', '}'), +    }) + +    check_parsing('{a#b -> b}', { +      --           0123456789 +      ast = { +        { +          fmtn('CurlyBracesIdentifier', '-di', ':0:0:{'), +          children = { +            { +              'Arrow:0:4: ->', +              children = { +                'PlainIdentifier(scope=0,ident=a#b):0:1:a#b', +                'PlainIdentifier(scope=0,ident=b):0:7: b', +              }, +            }, +          }, +        }, +      }, +      err = { +        arg = '-> b}', +        msg = 'E15: Arrow outside of lambda: %.*s', +      }, +    }, { +      hl('Curly', '{'), +      hl('IdentifierName', 'a#b'), +      hl('InvalidArrow', '->', 1), +      hl('IdentifierName', 'b', 1), +      hl('Curly', '}'), +    }) +    check_parsing('{a : b : c}', { +      --           01234567890 +      --           0         1 +      ast = { +        { +          fmtn('DictLiteral', '-di', ':0:0:{'), +          children = { +            { +              'Colon:0:2: :', +              children = { +                'PlainIdentifier(scope=0,ident=a):0:1:a', +                { +                  'Colon:0:6: :', +                  children = { +                    'PlainIdentifier(scope=0,ident=b):0:4: b', +                    'PlainIdentifier(scope=0,ident=c):0:8: c', +                  }, +                }, +              }, +            }, +          }, +        }, +      }, +      err = { +        arg = ': c}', +        msg = 'E15: Colon outside of dictionary or ternary operator: %.*s', +      }, +    }, { +      hl('Dict', '{'), +      hl('IdentifierName', 'a'), +      hl('Colon', ':', 1), +      hl('IdentifierName', 'b', 1), +      hl('InvalidColon', ':', 1), +      hl('IdentifierName', 'c', 1), +      hl('Dict', '}'), +    }) +    check_parsing('{', { +      --           0 +      ast = { +        fmtn('UnknownFigure', '\\di', ':0:0:{'), +      }, +      err = { +        arg = '{', +        msg = 'E15: Missing closing figure brace: %.*s', +      }, +    }, { +      hl('FigureBrace', '{'), +    }) +    check_parsing('{a', { +      --           01 +      ast = { +        { +          fmtn('UnknownFigure', '\\di', ':0:0:{'), +          children = { +            'PlainIdentifier(scope=0,ident=a):0:1:a', +          }, +        }, +      }, +      err = { +        arg = '{a', +        msg = 'E15: Missing closing figure brace: %.*s', +      }, +    }, { +      hl('FigureBrace', '{'), +      hl('IdentifierName', 'a'), +    }) +    check_parsing('{a,b', { +      --           0123 +      ast = { +        { +          fmtn('Lambda', '\\di', ':0:0:{'), +          children = { +            { +              'Comma:0:2:,', +              children = { +                'PlainIdentifier(scope=0,ident=a):0:1:a', +                'PlainIdentifier(scope=0,ident=b):0:3:b', +              }, +            }, +          }, +        }, +      }, +      err = { +        arg = '{a,b', +        msg = 'E15: Missing closing figure brace for lambda: %.*s', +      }, +    }, { +      hl('Lambda', '{'), +      hl('IdentifierName', 'a'), +      hl('Comma', ','), +      hl('IdentifierName', 'b'), +    }) +    check_parsing('{a,b->', { +      --           012345 +      ast = { +        { +          fmtn('Lambda', '\\di', ':0:0:{'), +          children = { +            { +              'Comma:0:2:,', +              children = { +                'PlainIdentifier(scope=0,ident=a):0:1:a', +                'PlainIdentifier(scope=0,ident=b):0:3:b', +              }, +            }, +            'Arrow:0:4:->', +          }, +        }, +      }, +      err = { +        arg = '', +        msg = 'E15: Expected value, got EOC: %.*s', +      }, +    }, { +      hl('Lambda', '{'), +      hl('IdentifierName', 'a'), +      hl('Comma', ','), +      hl('IdentifierName', 'b'), +      hl('Arrow', '->'), +    }) +    check_parsing('{a,b->c', { +      --           0123456 +      ast = { +        { +          fmtn('Lambda', '\\di', ':0:0:{'), +          children = { +            { +              'Comma:0:2:,', +              children = { +                'PlainIdentifier(scope=0,ident=a):0:1:a', +                'PlainIdentifier(scope=0,ident=b):0:3:b', +              }, +            }, +            { +              'Arrow:0:4:->', +              children = { +                'PlainIdentifier(scope=0,ident=c):0:6:c', +              }, +            }, +          }, +        }, +      }, +      err = { +        arg = '{a,b->c', +        msg = 'E15: Missing closing figure brace for lambda: %.*s', +      }, +    }, { +      hl('Lambda', '{'), +      hl('IdentifierName', 'a'), +      hl('Comma', ','), +      hl('IdentifierName', 'b'), +      hl('Arrow', '->'), +      hl('IdentifierName', 'c'), +    }) +    check_parsing('{a : b', { +      --           012345 +      ast = { +        { +          fmtn('DictLiteral', '-di', ':0:0:{'), +          children = { +            { +              'Colon:0:2: :', +              children = { +                'PlainIdentifier(scope=0,ident=a):0:1:a', +                'PlainIdentifier(scope=0,ident=b):0:4: b', +              }, +            }, +          }, +        }, +      }, +      err = { +        arg = '{a : b', +        msg = 'E723: Missing end of Dictionary \'}\': %.*s', +      }, +    }, { +      hl('Dict', '{'), +      hl('IdentifierName', 'a'), +      hl('Colon', ':', 1), +      hl('IdentifierName', 'b', 1), +    }) +    check_parsing('{a : b,', { +      --           0123456 +      ast = { +        { +          fmtn('DictLiteral', '-di', ':0:0:{'), +          children = { +            { +              'Comma:0:6:,', +              children = { +                { +                  'Colon:0:2: :', +                  children = { +                    'PlainIdentifier(scope=0,ident=a):0:1:a', +                    'PlainIdentifier(scope=0,ident=b):0:4: b', +                  }, +                }, +              }, +            }, +          }, +        }, +      }, +      err = { +        arg = '', +        msg = 'E15: Expected value, got EOC: %.*s', +      }, +    }, { +      hl('Dict', '{'), +      hl('IdentifierName', 'a'), +      hl('Colon', ':', 1), +      hl('IdentifierName', 'b', 1), +      hl('Comma', ','), +    }) +  end) +  itp('works with ternary operator', function() +    check_parsing('a ? b : c', { +      --           012345678 +      ast = { +        { +          'Ternary:0:1: ?', +          children = { +            'PlainIdentifier(scope=0,ident=a):0:0:a', +            { +              'TernaryValue:0:5: :', +              children = { +                'PlainIdentifier(scope=0,ident=b):0:3: b', +                'PlainIdentifier(scope=0,ident=c):0:7: c', +              }, +            }, +          }, +        }, +      }, +    }, { +      hl('IdentifierName', 'a'), +      hl('Ternary', '?', 1), +      hl('IdentifierName', 'b', 1), +      hl('TernaryColon', ':', 1), +      hl('IdentifierName', 'c', 1), +    }) +    check_parsing('@a?@b?@c:@d:@e', { +      --           01234567890123 +      --           0         1 +      ast = { +        { +          'Ternary:0:2:?', +          children = { +            'Register(name=a):0:0:@a', +            { +              'TernaryValue:0:11::', +              children = { +                { +                  'Ternary:0:5:?', +                  children = { +                    'Register(name=b):0:3:@b', +                    { +                      'TernaryValue:0:8::', +                      children = { +                        'Register(name=c):0:6:@c', +                        'Register(name=d):0:9:@d', +                      }, +                    }, +                  }, +                }, +                'Register(name=e):0:12:@e', +              }, +            }, +          }, +        }, +      }, +    }, { +      hl('Register', '@a'), +      hl('Ternary', '?'), +      hl('Register', '@b'), +      hl('Ternary', '?'), +      hl('Register', '@c'), +      hl('TernaryColon', ':'), +      hl('Register', '@d'), +      hl('TernaryColon', ':'), +      hl('Register', '@e'), +    }) +    check_parsing('@a?@b:@c?@d:@e', { +      --           01234567890123 +      --           0         1 +      ast = { +        { +          'Ternary:0:2:?', +          children = { +            'Register(name=a):0:0:@a', +            { +              'TernaryValue:0:5::', +              children = { +                'Register(name=b):0:3:@b', +                { +                  'Ternary:0:8:?', +                  children = { +                    'Register(name=c):0:6:@c', +                    { +                      'TernaryValue:0:11::', +                      children = { +                        'Register(name=d):0:9:@d', +                        'Register(name=e):0:12:@e', +                      }, +                    }, +                  }, +                }, +              }, +            }, +          }, +        }, +      }, +    }, { +      hl('Register', '@a'), +      hl('Ternary', '?'), +      hl('Register', '@b'), +      hl('TernaryColon', ':'), +      hl('Register', '@c'), +      hl('Ternary', '?'), +      hl('Register', '@d'), +      hl('TernaryColon', ':'), +      hl('Register', '@e'), +    }) +    check_parsing('@a?@b?@c?@d:@e?@f:@g:@h?@i:@j:@k', { +      --           01234567890123456789012345678901 +      --           0         1         2         3 +      ast = { +        { +          'Ternary:0:2:?', +          children = { +            'Register(name=a):0:0:@a', +            { +              'TernaryValue:0:29::', +              children = { +                { +                  'Ternary:0:5:?', +                  children = { +                    'Register(name=b):0:3:@b', +                    { +                      'TernaryValue:0:20::', +                      children = { +                        { +                          'Ternary:0:8:?', +                          children = { +                            'Register(name=c):0:6:@c', +                            { +                              'TernaryValue:0:11::', +                              children = { +                                'Register(name=d):0:9:@d', +                                { +                                  'Ternary:0:14:?', +                                  children = { +                                    'Register(name=e):0:12:@e', +                                    { +                                      'TernaryValue:0:17::', +                                      children = { +                                        'Register(name=f):0:15:@f', +                                        'Register(name=g):0:18:@g', +                                      }, +                                    }, +                                  }, +                                }, +                              }, +                            }, +                          }, +                        }, +                        { +                          'Ternary:0:23:?', +                          children = { +                            'Register(name=h):0:21:@h', +                            { +                              'TernaryValue:0:26::', +                              children = { +                                'Register(name=i):0:24:@i', +                                'Register(name=j):0:27:@j', +                              }, +                            }, +                          }, +                        }, +                      }, +                    }, +                  }, +                }, +                'Register(name=k):0:30:@k', +              }, +            }, +          }, +        }, +      }, +    }, { +      hl('Register', '@a'), +      hl('Ternary', '?'), +      hl('Register', '@b'), +      hl('Ternary', '?'), +      hl('Register', '@c'), +      hl('Ternary', '?'), +      hl('Register', '@d'), +      hl('TernaryColon', ':'), +      hl('Register', '@e'), +      hl('Ternary', '?'), +      hl('Register', '@f'), +      hl('TernaryColon', ':'), +      hl('Register', '@g'), +      hl('TernaryColon', ':'), +      hl('Register', '@h'), +      hl('Ternary', '?'), +      hl('Register', '@i'), +      hl('TernaryColon', ':'), +      hl('Register', '@j'), +      hl('TernaryColon', ':'), +      hl('Register', '@k'), +    }) +    check_parsing('?', { +      --           0 +      ast = { +        { +          'Ternary:0:0:?', +          children = { +            'Missing:0:0:', +            'TernaryValue:0:0:?', +          }, +        }, +      }, +      err = { +        arg = '?', +        msg = 'E15: Expected value, got question mark: %.*s', +      }, +    }, { +      hl('InvalidTernary', '?'), +    }) + +    check_parsing('?:', { +      --           01 +      ast = { +        { +          'Ternary:0:0:?', +          children = { +            'Missing:0:0:', +            { +              'TernaryValue:0:1::', +              children = { +                'Missing:0:1:', +              }, +            }, +          }, +        }, +      }, +      err = { +        arg = '?:', +        msg = 'E15: Expected value, got question mark: %.*s', +      }, +    }, { +      hl('InvalidTernary', '?'), +      hl('InvalidTernaryColon', ':'), +    }) + +    check_parsing('?::', { +      --           012 +      ast = { +        { +          'Colon:0:2::', +          children = { +            { +              'Ternary:0:0:?', +              children = { +                'Missing:0:0:', +                { +                  'TernaryValue:0:1::', +                  children = { +                    'Missing:0:1:', +                    'Missing:0:2:', +                  }, +                }, +              }, +            }, +          }, +        }, +      }, +      err = { +        arg = '?::', +        msg = 'E15: Expected value, got question mark: %.*s', +      }, +    }, { +      hl('InvalidTernary', '?'), +      hl('InvalidTernaryColon', ':'), +      hl('InvalidColon', ':'), +    }) + +    check_parsing('a?b', { +      --           012 +      ast = { +        { +          'Ternary:0:1:?', +          children = { +            'PlainIdentifier(scope=0,ident=a):0:0:a', +            { +              'TernaryValue:0:1:?', +              children = { +                'PlainIdentifier(scope=0,ident=b):0:2:b', +              }, +            }, +          }, +        }, +      }, +      err = { +        arg = '?b', +        msg = 'E109: Missing \':\' after \'?\': %.*s', +      }, +    }, { +      hl('IdentifierName', 'a'), +      hl('Ternary', '?'), +      hl('IdentifierName', 'b'), +    }) +    check_parsing('a?b:', { +      --           0123 +      ast = { +        { +          'Ternary:0:1:?', +          children = { +            'PlainIdentifier(scope=0,ident=a):0:0:a', +            { +              'TernaryValue:0:1:?', +              children = { +                'PlainIdentifier(scope=b,ident=):0:2:b:', +              }, +            }, +          }, +        }, +      }, +      err = { +        arg = '?b:', +        msg = 'E109: Missing \':\' after \'?\': %.*s', +      }, +    }, { +      hl('IdentifierName', 'a'), +      hl('Ternary', '?'), +      hl('IdentifierScope', 'b'), +      hl('IdentifierScopeDelimiter', ':'), +    }) + +    check_parsing('a?b::c', { +      --           012345 +      ast = { +        { +          'Ternary:0:1:?', +          children = { +            'PlainIdentifier(scope=0,ident=a):0:0:a', +            { +              'TernaryValue:0:4::', +              children = { +                'PlainIdentifier(scope=b,ident=):0:2:b:', +                'PlainIdentifier(scope=0,ident=c):0:5:c', +              }, +            }, +          }, +        }, +      }, +    }, { +      hl('IdentifierName', 'a'), +      hl('Ternary', '?'), +      hl('IdentifierScope', 'b'), +      hl('IdentifierScopeDelimiter', ':'), +      hl('TernaryColon', ':'), +      hl('IdentifierName', 'c'), +    }) + +    check_parsing('a?b :', { +      --           01234 +      ast = { +        { +          'Ternary:0:1:?', +          children = { +            'PlainIdentifier(scope=0,ident=a):0:0:a', +            { +              'TernaryValue:0:3: :', +              children = { +                'PlainIdentifier(scope=0,ident=b):0:2:b', +              }, +            }, +          }, +        }, +      }, +      err = { +        arg = '', +        msg = 'E15: Expected value, got EOC: %.*s', +      }, +    }, { +      hl('IdentifierName', 'a'), +      hl('Ternary', '?'), +      hl('IdentifierName', 'b'), +      hl('TernaryColon', ':', 1), +    }) + +    check_parsing('(@a?@b:@c)?@d:@e', { +      --           0123456789012345 +      --           0         1 +      ast = { +        { +          'Ternary:0:10:?', +          children = { +            { +              'Nested:0:0:(', +              children = { +                { +                  'Ternary:0:3:?', +                  children = { +                    'Register(name=a):0:1:@a', +                    { +                      'TernaryValue:0:6::', +                      children = { +                        'Register(name=b):0:4:@b', +                        'Register(name=c):0:7:@c', +                      }, +                    }, +                  }, +                }, +              }, +            }, +            { +              'TernaryValue:0:13::', +              children = { +                'Register(name=d):0:11:@d', +                'Register(name=e):0:14:@e', +              }, +            }, +          }, +        }, +      }, +    }, { +      hl('NestingParenthesis', '('), +      hl('Register', '@a'), +      hl('Ternary', '?'), +      hl('Register', '@b'), +      hl('TernaryColon', ':'), +      hl('Register', '@c'), +      hl('NestingParenthesis', ')'), +      hl('Ternary', '?'), +      hl('Register', '@d'), +      hl('TernaryColon', ':'), +      hl('Register', '@e'), +    }) + +    check_parsing('(@a?@b:@c)?(@d?@e:@f):(@g?@h:@i)', { +      --           01234567890123456789012345678901 +      --           0         1         2         3 +      ast = { +        { +          'Ternary:0:10:?', +          children = { +            { +              'Nested:0:0:(', +              children = { +                { +                  'Ternary:0:3:?', +                  children = { +                    'Register(name=a):0:1:@a', +                    { +                      'TernaryValue:0:6::', +                      children = { +                        'Register(name=b):0:4:@b', +                        'Register(name=c):0:7:@c', +                      }, +                    }, +                  }, +                }, +              }, +            }, +            { +              'TernaryValue:0:21::', +              children = { +                { +                  'Nested:0:11:(', +                  children = { +                    { +                      'Ternary:0:14:?', +                      children = { +                        'Register(name=d):0:12:@d', +                        { +                          'TernaryValue:0:17::', +                          children = { +                            'Register(name=e):0:15:@e', +                            'Register(name=f):0:18:@f', +                          }, +                        }, +                      }, +                    }, +                  }, +                }, +                { +                  'Nested:0:22:(', +                  children = { +                    { +                      'Ternary:0:25:?', +                      children = { +                        'Register(name=g):0:23:@g', +                        { +                          'TernaryValue:0:28::', +                          children = { +                            'Register(name=h):0:26:@h', +                            'Register(name=i):0:29:@i', +                          }, +                        }, +                      }, +                    }, +                  }, +                }, +              }, +            }, +          }, +        }, +      }, +    }, { +      hl('NestingParenthesis', '('), +      hl('Register', '@a'), +      hl('Ternary', '?'), +      hl('Register', '@b'), +      hl('TernaryColon', ':'), +      hl('Register', '@c'), +      hl('NestingParenthesis', ')'), +      hl('Ternary', '?'), +      hl('NestingParenthesis', '('), +      hl('Register', '@d'), +      hl('Ternary', '?'), +      hl('Register', '@e'), +      hl('TernaryColon', ':'), +      hl('Register', '@f'), +      hl('NestingParenthesis', ')'), +      hl('TernaryColon', ':'), +      hl('NestingParenthesis', '('), +      hl('Register', '@g'), +      hl('Ternary', '?'), +      hl('Register', '@h'), +      hl('TernaryColon', ':'), +      hl('Register', '@i'), +      hl('NestingParenthesis', ')'), +    }) + +    check_parsing('(@a?@b:@c)?@d?@e:@f:@g?@h:@i', { +      --           0123456789012345678901234567 +      --           0         1         2 +      ast = { +        { +          'Ternary:0:10:?', +          children = { +            { +              'Nested:0:0:(', +              children = { +                { +                  'Ternary:0:3:?', +                  children = { +                    'Register(name=a):0:1:@a', +                    { +                      'TernaryValue:0:6::', +                      children = { +                        'Register(name=b):0:4:@b', +                        'Register(name=c):0:7:@c', +                      }, +                    }, +                  }, +                }, +              }, +            }, +            { +              'TernaryValue:0:19::', +              children = { +                { +                  'Ternary:0:13:?', +                  children = { +                    'Register(name=d):0:11:@d', +                    { +                      'TernaryValue:0:16::', +                      children = { +                        'Register(name=e):0:14:@e', +                        'Register(name=f):0:17:@f', +                      }, +                    }, +                  }, +                }, +                { +                  'Ternary:0:22:?', +                  children = { +                    'Register(name=g):0:20:@g', +                    { +                      'TernaryValue:0:25::', +                      children = { +                        'Register(name=h):0:23:@h', +                        'Register(name=i):0:26:@i', +                      }, +                    }, +                  }, +                }, +              }, +            }, +          }, +        }, +      }, +    }, { +      hl('NestingParenthesis', '('), +      hl('Register', '@a'), +      hl('Ternary', '?'), +      hl('Register', '@b'), +      hl('TernaryColon', ':'), +      hl('Register', '@c'), +      hl('NestingParenthesis', ')'), +      hl('Ternary', '?'), +      hl('Register', '@d'), +      hl('Ternary', '?'), +      hl('Register', '@e'), +      hl('TernaryColon', ':'), +      hl('Register', '@f'), +      hl('TernaryColon', ':'), +      hl('Register', '@g'), +      hl('Ternary', '?'), +      hl('Register', '@h'), +      hl('TernaryColon', ':'), +      hl('Register', '@i'), +    }) +    check_parsing('a?b{cdef}g:h', { +      --           012345678901 +      --           0         1 +      ast = { +        { +          'Ternary:0:1:?', +          children = { +            'PlainIdentifier(scope=0,ident=a):0:0:a', +            { +              'TernaryValue:0:10::', +              children = { +                { +                  'ComplexIdentifier:0:3:', +                  children = { +                    'PlainIdentifier(scope=0,ident=b):0:2:b', +                    { +                      'ComplexIdentifier:0:9:', +                      children = { +                        { +                          fmtn('CurlyBracesIdentifier', '--i', ':0:3:{'), +                          children = { +                            'PlainIdentifier(scope=0,ident=cdef):0:4:cdef', +                          }, +                        }, +                        'PlainIdentifier(scope=0,ident=g):0:9:g', +                      }, +                    }, +                  }, +                }, +                'PlainIdentifier(scope=0,ident=h):0:11:h', +              }, +            }, +          }, +        }, +      }, +    }, { +      hl('IdentifierName', 'a'), +      hl('Ternary', '?'), +      hl('IdentifierName', 'b'), +      hl('Curly', '{'), +      hl('IdentifierName', 'cdef'), +      hl('Curly', '}'), +      hl('IdentifierName', 'g'), +      hl('TernaryColon', ':'), +      hl('IdentifierName', 'h'), +    }) +    check_parsing('a ? b : c : d', { +      --           0123456789012 +      --           0         1 +      ast = { +        { +          'Colon:0:9: :', +          children = { +            { +              'Ternary:0:1: ?', +              children = { +                'PlainIdentifier(scope=0,ident=a):0:0:a', +                { +                  'TernaryValue:0:5: :', +                  children = { +                    'PlainIdentifier(scope=0,ident=b):0:3: b', +                    'PlainIdentifier(scope=0,ident=c):0:7: c', +                  }, +                }, +              }, +            }, +            'PlainIdentifier(scope=0,ident=d):0:11: d', +          }, +        }, +      }, +      err = { +        arg = ': d', +        msg = 'E15: Colon outside of dictionary or ternary operator: %.*s', +      }, +    }, { +      hl('IdentifierName', 'a'), +      hl('Ternary', '?', 1), +      hl('IdentifierName', 'b', 1), +      hl('TernaryColon', ':', 1), +      hl('IdentifierName', 'c', 1), +      hl('InvalidColon', ':', 1), +      hl('IdentifierName', 'd', 1), +    }) +  end) +  itp('works with comparison operators', function() +    check_parsing('a == b', { +      --           012345 +      ast = { +        { +          'Comparison(type=Equal,inv=0,ccs=UseOption):0:1: ==', +          children = { +            'PlainIdentifier(scope=0,ident=a):0:0:a', +            'PlainIdentifier(scope=0,ident=b):0:4: b', +          }, +        }, +      }, +    }, { +      hl('IdentifierName', 'a'), +      hl('Comparison', '==', 1), +      hl('IdentifierName', 'b', 1), +    }) + +    check_parsing('a ==? b', { +      --           0123456 +      ast = { +        { +          'Comparison(type=Equal,inv=0,ccs=IgnoreCase):0:1: ==?', +          children = { +            'PlainIdentifier(scope=0,ident=a):0:0:a', +            'PlainIdentifier(scope=0,ident=b):0:5: b', +          }, +        }, +      }, +    }, { +      hl('IdentifierName', 'a'), +      hl('Comparison', '==', 1), +      hl('ComparisonModifier', '?'), +      hl('IdentifierName', 'b', 1), +    }) + +    check_parsing('a ==# b', { +      --           0123456 +      ast = { +        { +          'Comparison(type=Equal,inv=0,ccs=MatchCase):0:1: ==#', +          children = { +            'PlainIdentifier(scope=0,ident=a):0:0:a', +            'PlainIdentifier(scope=0,ident=b):0:5: b', +          }, +        }, +      }, +    }, { +      hl('IdentifierName', 'a'), +      hl('Comparison', '==', 1), +      hl('ComparisonModifier', '#'), +      hl('IdentifierName', 'b', 1), +    }) + +    check_parsing('a !=# b', { +      --           0123456 +      ast = { +        { +          'Comparison(type=Equal,inv=1,ccs=MatchCase):0:1: !=#', +          children = { +            'PlainIdentifier(scope=0,ident=a):0:0:a', +            'PlainIdentifier(scope=0,ident=b):0:5: b', +          }, +        }, +      }, +    }, { +      hl('IdentifierName', 'a'), +      hl('Comparison', '!=', 1), +      hl('ComparisonModifier', '#'), +      hl('IdentifierName', 'b', 1), +    }) + +    check_parsing('a <=# b', { +      --           0123456 +      ast = { +        { +          'Comparison(type=Greater,inv=1,ccs=MatchCase):0:1: <=#', +          children = { +            'PlainIdentifier(scope=0,ident=a):0:0:a', +            'PlainIdentifier(scope=0,ident=b):0:5: b', +          }, +        }, +      }, +    }, { +      hl('IdentifierName', 'a'), +      hl('Comparison', '<=', 1), +      hl('ComparisonModifier', '#'), +      hl('IdentifierName', 'b', 1), +    }) + +    check_parsing('a >=# b', { +      --           0123456 +      ast = { +        { +          'Comparison(type=GreaterOrEqual,inv=0,ccs=MatchCase):0:1: >=#', +          children = { +            'PlainIdentifier(scope=0,ident=a):0:0:a', +            'PlainIdentifier(scope=0,ident=b):0:5: b', +          }, +        }, +      }, +    }, { +      hl('IdentifierName', 'a'), +      hl('Comparison', '>=', 1), +      hl('ComparisonModifier', '#'), +      hl('IdentifierName', 'b', 1), +    }) + +    check_parsing('a ># b', { +      --           012345 +      ast = { +        { +          'Comparison(type=Greater,inv=0,ccs=MatchCase):0:1: >#', +          children = { +            'PlainIdentifier(scope=0,ident=a):0:0:a', +            'PlainIdentifier(scope=0,ident=b):0:4: b', +          }, +        }, +      }, +    }, { +      hl('IdentifierName', 'a'), +      hl('Comparison', '>', 1), +      hl('ComparisonModifier', '#'), +      hl('IdentifierName', 'b', 1), +    }) + +    check_parsing('a <# b', { +      --           012345 +      ast = { +        { +          'Comparison(type=GreaterOrEqual,inv=1,ccs=MatchCase):0:1: <#', +          children = { +            'PlainIdentifier(scope=0,ident=a):0:0:a', +            'PlainIdentifier(scope=0,ident=b):0:4: b', +          }, +        }, +      }, +    }, { +      hl('IdentifierName', 'a'), +      hl('Comparison', '<', 1), +      hl('ComparisonModifier', '#'), +      hl('IdentifierName', 'b', 1), +    }) + +    check_parsing('a is#b', { +      --           012345 +      ast = { +        { +          'Comparison(type=Identical,inv=0,ccs=MatchCase):0:1: is#', +          children = { +            'PlainIdentifier(scope=0,ident=a):0:0:a', +            'PlainIdentifier(scope=0,ident=b):0:5:b', +          }, +        }, +      }, +    }, { +      hl('IdentifierName', 'a'), +      hl('Comparison', 'is', 1), +      hl('ComparisonModifier', '#'), +      hl('IdentifierName', 'b'), +    }) + +    check_parsing('a is?b', { +      --           012345 +      ast = { +        { +          'Comparison(type=Identical,inv=0,ccs=IgnoreCase):0:1: is?', +          children = { +            'PlainIdentifier(scope=0,ident=a):0:0:a', +            'PlainIdentifier(scope=0,ident=b):0:5:b', +          }, +        }, +      }, +    }, { +      hl('IdentifierName', 'a'), +      hl('Comparison', 'is', 1), +      hl('ComparisonModifier', '?'), +      hl('IdentifierName', 'b'), +    }) + +    check_parsing('a isnot b', { +      --           012345678 +      ast = { +        { +          'Comparison(type=Identical,inv=1,ccs=UseOption):0:1: isnot', +          children = { +            'PlainIdentifier(scope=0,ident=a):0:0:a', +            'PlainIdentifier(scope=0,ident=b):0:7: b', +          }, +        }, +      }, +    }, { +      hl('IdentifierName', 'a'), +      hl('Comparison', 'isnot', 1), +      hl('IdentifierName', 'b', 1), +    }) + +    check_parsing('a < b < c', { +      --           012345678 +      ast = { +        { +          'Comparison(type=GreaterOrEqual,inv=1,ccs=UseOption):0:1: <', +          children = { +            'PlainIdentifier(scope=0,ident=a):0:0:a', +            { +              'Comparison(type=GreaterOrEqual,inv=1,ccs=UseOption):0:5: <', +              children = { +                'PlainIdentifier(scope=0,ident=b):0:3: b', +                'PlainIdentifier(scope=0,ident=c):0:7: c', +              }, +            }, +          }, +        }, +      }, +      err = { +        arg = ' < c', +        msg = 'E15: Operator is not associative: %.*s', +      }, +    }, { +      hl('IdentifierName', 'a'), +      hl('Comparison', '<', 1), +      hl('IdentifierName', 'b', 1), +      hl('InvalidComparison', '<', 1), +      hl('IdentifierName', 'c', 1), +    }) + +    check_parsing('a < b <# c', { +      --           012345678 +      ast = { +        { +          'Comparison(type=GreaterOrEqual,inv=1,ccs=UseOption):0:1: <', +          children = { +            'PlainIdentifier(scope=0,ident=a):0:0:a', +            { +              'Comparison(type=GreaterOrEqual,inv=1,ccs=MatchCase):0:5: <#', +              children = { +                'PlainIdentifier(scope=0,ident=b):0:3: b', +                'PlainIdentifier(scope=0,ident=c):0:8: c', +              }, +            }, +          }, +        }, +      }, +      err = { +        arg = ' <# c', +        msg = 'E15: Operator is not associative: %.*s', +      }, +    }, { +      hl('IdentifierName', 'a'), +      hl('Comparison', '<', 1), +      hl('IdentifierName', 'b', 1), +      hl('InvalidComparison', '<', 1), +      hl('InvalidComparisonModifier', '#'), +      hl('IdentifierName', 'c', 1), +    }) + +    check_parsing('a += b', { +      --           012345 +      ast = { +        { +          'Assignment(Add):0:1: +=', +          children = { +            'PlainIdentifier(scope=0,ident=a):0:0:a', +            'PlainIdentifier(scope=0,ident=b):0:4: b', +          }, +        }, +      }, +      err = { +        arg = '+= b', +        msg = 'E15: Misplaced assignment: %.*s', +      }, +    }, { +      hl('IdentifierName', 'a'), +      hl('InvalidAssignmentWithAddition', '+=', 1), +      hl('IdentifierName', 'b', 1), +    }) +    check_parsing('a + b == c + d', { +      --           01234567890123 +      --           0         1 +      ast = { +        { +          'Comparison(type=Equal,inv=0,ccs=UseOption):0:5: ==', +          children = { +            { +              'BinaryPlus:0:1: +', +              children = { +                'PlainIdentifier(scope=0,ident=a):0:0:a', +                'PlainIdentifier(scope=0,ident=b):0:3: b', +              }, +            }, +            { +              'BinaryPlus:0:10: +', +              children = { +                'PlainIdentifier(scope=0,ident=c):0:8: c', +                'PlainIdentifier(scope=0,ident=d):0:12: d', +              }, +            }, +          }, +        }, +      }, +    }, { +      hl('IdentifierName', 'a'), +      hl('BinaryPlus', '+', 1), +      hl('IdentifierName', 'b', 1), +      hl('Comparison', '==', 1), +      hl('IdentifierName', 'c', 1), +      hl('BinaryPlus', '+', 1), +      hl('IdentifierName', 'd', 1), +    }) +    check_parsing('+ a == + b', { +      --           0123456789 +      ast = { +        { +          'Comparison(type=Equal,inv=0,ccs=UseOption):0:3: ==', +          children = { +            { +              'UnaryPlus:0:0:+', +              children = { +                'PlainIdentifier(scope=0,ident=a):0:1: a', +              }, +            }, +            { +              'UnaryPlus:0:6: +', +              children = { +                'PlainIdentifier(scope=0,ident=b):0:8: b', +              }, +            }, +          }, +        }, +      }, +    }, { +      hl('UnaryPlus', '+'), +      hl('IdentifierName', 'a', 1), +      hl('Comparison', '==', 1), +      hl('UnaryPlus', '+', 1), +      hl('IdentifierName', 'b', 1), +    }) +  end) +  itp('works with concat/subscript', function() +    check_parsing('.', { +      --           0 +      ast = { +        { +          'ConcatOrSubscript:0:0:.', +          children = { +            'Missing:0:0:', +          }, +        }, +      }, +      err = { +        arg = '.', +        msg = 'E15: Unexpected dot: %.*s', +      }, +    }, { +      hl('InvalidConcatOrSubscript', '.'), +    }) + +    check_parsing('a.', { +      --           01 +      ast = { +        { +          'ConcatOrSubscript:0:1:.', +          children = { +            'PlainIdentifier(scope=0,ident=a):0:0:a', +          }, +        }, +      }, +      err = { +        arg = '', +        msg = 'E15: Expected value, got EOC: %.*s', +      }, +    }, { +      hl('IdentifierName', 'a'), +      hl('ConcatOrSubscript', '.'), +    }) + +    check_parsing('a.b', { +      --           012 +      ast = { +        { +          'ConcatOrSubscript:0:1:.', +          children = { +            'PlainIdentifier(scope=0,ident=a):0:0:a', +            'PlainKey(key=b):0:2:b', +          }, +        }, +      }, +    }, { +      hl('IdentifierName', 'a'), +      hl('ConcatOrSubscript', '.'), +      hl('IdentifierKey', 'b'), +    }) + +    check_parsing('1.2', { +      --           012 +      ast = { +        'Float(val=1.200000e+00):0:0:1.2', +      }, +    }, { +      hl('Float', '1.2'), +    }) + +    check_parsing('1.2 + 1.3e-5', { +      --           012345678901 +      --           0         1 +      ast = { +        { +          'BinaryPlus:0:3: +', +          children = { +            'Float(val=1.200000e+00):0:0:1.2', +            'Float(val=1.300000e-05):0:5: 1.3e-5', +          }, +        }, +      }, +    }, { +      hl('Float', '1.2'), +      hl('BinaryPlus', '+', 1), +      hl('Float', '1.3e-5', 1), +    }) + +    check_parsing('a . 1.2 + 1.3e-5', { +      --           0123456789012345 +      --           0         1 +      ast = { +        { +          'BinaryPlus:0:7: +', +          children = { +            { +              'Concat:0:1: .', +              children = { +                'PlainIdentifier(scope=0,ident=a):0:0:a', +                { +                  'ConcatOrSubscript:0:5:.', +                  children = { +                    'Integer(val=1):0:3: 1', +                    'PlainKey(key=2):0:6:2', +                  }, +                }, +              }, +            }, +            'Float(val=1.300000e-05):0:9: 1.3e-5', +          }, +        }, +      }, +    }, { +      hl('IdentifierName', 'a'), +      hl('Concat', '.', 1), +      hl('Number', '1', 1), +      hl('ConcatOrSubscript', '.'), +      hl('IdentifierKey', '2'), +      hl('BinaryPlus', '+', 1), +      hl('Float', '1.3e-5', 1), +    }) + +    check_parsing('1.3e-5 + 1.2 . a', { +      --           0123456789012345 +      --           0         1 +      ast = { +        { +          'Concat:0:12: .', +          children = { +            { +              'BinaryPlus:0:6: +', +              children = { +                'Float(val=1.300000e-05):0:0:1.3e-5', +                'Float(val=1.200000e+00):0:8: 1.2', +              }, +            }, +            'PlainIdentifier(scope=0,ident=a):0:14: a', +          }, +        }, +      }, +    }, { +      hl('Float', '1.3e-5'), +      hl('BinaryPlus', '+', 1), +      hl('Float', '1.2', 1), +      hl('Concat', '.', 1), +      hl('IdentifierName', 'a', 1), +    }) + +    check_parsing('1.3e-5 + a . 1.2', { +      --           0123456789012345 +      --           0         1 +      ast = { +        { +          'Concat:0:10: .', +          children = { +            { +              'BinaryPlus:0:6: +', +              children = { +                'Float(val=1.300000e-05):0:0:1.3e-5', +                'PlainIdentifier(scope=0,ident=a):0:8: a', +              }, +            }, +            { +              'ConcatOrSubscript:0:14:.', +              children = { +                'Integer(val=1):0:12: 1', +                'PlainKey(key=2):0:15:2', +              }, +            }, +          }, +        }, +      }, +    }, { +      hl('Float', '1.3e-5'), +      hl('BinaryPlus', '+', 1), +      hl('IdentifierName', 'a', 1), +      hl('Concat', '.', 1), +      hl('Number', '1', 1), +      hl('ConcatOrSubscript', '.'), +      hl('IdentifierKey', '2'), +    }) + +    check_parsing('1.2.3', { +      --           01234 +      ast = { +        { +          'ConcatOrSubscript:0:3:.', +          children = { +            { +              'ConcatOrSubscript:0:1:.', +              children = { +                'Integer(val=1):0:0:1', +                'PlainKey(key=2):0:2:2', +              }, +            }, +            'PlainKey(key=3):0:4:3', +          }, +        }, +      }, +    }, { +      hl('Number', '1'), +      hl('ConcatOrSubscript', '.'), +      hl('IdentifierKey', '2'), +      hl('ConcatOrSubscript', '.'), +      hl('IdentifierKey', '3'), +    }) + +    check_parsing('a.1.2', { +      --           01234 +      ast = { +        { +          'ConcatOrSubscript:0:3:.', +          children = { +            { +              'ConcatOrSubscript:0:1:.', +              children = { +                'PlainIdentifier(scope=0,ident=a):0:0:a', +                'PlainKey(key=1):0:2:1', +              }, +            }, +            'PlainKey(key=2):0:4:2', +          }, +        }, +      }, +    }, { +      hl('IdentifierName', 'a'), +      hl('ConcatOrSubscript', '.'), +      hl('IdentifierKey', '1'), +      hl('ConcatOrSubscript', '.'), +      hl('IdentifierKey', '2'), +    }) + +    check_parsing('a . 1.2', { +      --           0123456 +      ast = { +        { +          'Concat:0:1: .', +          children = { +            'PlainIdentifier(scope=0,ident=a):0:0:a', +            { +              'ConcatOrSubscript:0:5:.', +              children = { +                'Integer(val=1):0:3: 1', +                'PlainKey(key=2):0:6:2', +              }, +            }, +          }, +        }, +      }, +    }, { +      hl('IdentifierName', 'a'), +      hl('Concat', '.', 1), +      hl('Number', '1', 1), +      hl('ConcatOrSubscript', '.'), +      hl('IdentifierKey', '2'), +    }) + +    check_parsing('+a . +b', { +      --           0123456 +      ast = { +        { +          'Concat:0:2: .', +          children = { +            { +              'UnaryPlus:0:0:+', +              children = { +                'PlainIdentifier(scope=0,ident=a):0:1:a', +              }, +            }, +            { +              'UnaryPlus:0:4: +', +              children = { +                'PlainIdentifier(scope=0,ident=b):0:6:b', +              }, +            }, +          }, +        }, +      }, +    }, { +      hl('UnaryPlus', '+'), +      hl('IdentifierName', 'a'), +      hl('Concat', '.', 1), +      hl('UnaryPlus', '+', 1), +      hl('IdentifierName', 'b'), +    }) + +    check_parsing('a. b', { +      --           0123 +      ast = { +        { +          'Concat:0:1:.', +          children = { +            'PlainIdentifier(scope=0,ident=a):0:0:a', +            'PlainIdentifier(scope=0,ident=b):0:2: b', +          }, +        }, +      }, +    }, { +      hl('IdentifierName', 'a'), +      hl('ConcatOrSubscript', '.'), +      hl('IdentifierName', 'b', 1), +    }) + +    check_parsing('a. 1', { +      --           0123 +      ast = { +        { +          'Concat:0:1:.', +          children = { +            'PlainIdentifier(scope=0,ident=a):0:0:a', +            'Integer(val=1):0:2: 1', +          }, +        }, +      }, +    }, { +      hl('IdentifierName', 'a'), +      hl('ConcatOrSubscript', '.'), +      hl('Number', '1', 1), +    }) +  end) +  itp('works with bracket subscripts', function() +    check_parsing(':', { +      --           0 +      ast = { +        { +          'Colon:0:0::', +          children = { +            'Missing:0:0:', +          }, +        }, +      }, +      err = { +        arg = ':', +        msg = 'E15: Colon outside of dictionary or ternary operator: %.*s', +      }, +    }, { +      hl('InvalidColon', ':'), +    }) +    check_parsing('a[]', { +      --           012 +      ast = { +        { +          'Subscript:0:1:[', +          children = { +            'PlainIdentifier(scope=0,ident=a):0:0:a', +          }, +        }, +      }, +      err = { +        arg = ']', +        msg = 'E15: Expected value, got closing bracket: %.*s', +      }, +    }, { +      hl('IdentifierName', 'a'), +      hl('SubscriptBracket', '['), +      hl('InvalidSubscriptBracket', ']'), +    }) +    check_parsing('a[b:]', { +      --           01234 +      ast = { +        { +          'Subscript:0:1:[', +          children = { +            'PlainIdentifier(scope=0,ident=a):0:0:a', +            'PlainIdentifier(scope=b,ident=):0:2:b:', +          }, +        }, +      }, +    }, { +      hl('IdentifierName', 'a'), +      hl('SubscriptBracket', '['), +      hl('IdentifierScope', 'b'), +      hl('IdentifierScopeDelimiter', ':'), +      hl('SubscriptBracket', ']'), +    }) + +    check_parsing('a[b:c]', { +      --           012345 +      ast = { +        { +          'Subscript:0:1:[', +          children = { +            'PlainIdentifier(scope=0,ident=a):0:0:a', +            'PlainIdentifier(scope=b,ident=c):0:2:b:c', +          }, +        }, +      }, +    }, { +      hl('IdentifierName', 'a'), +      hl('SubscriptBracket', '['), +      hl('IdentifierScope', 'b'), +      hl('IdentifierScopeDelimiter', ':'), +      hl('IdentifierName', 'c'), +      hl('SubscriptBracket', ']'), +    }) +    check_parsing('a[b : c]', { +      --           01234567 +      ast = { +        { +          'Subscript:0:1:[', +          children = { +            'PlainIdentifier(scope=0,ident=a):0:0:a', +            { +              'Colon:0:3: :', +              children = { +                'PlainIdentifier(scope=0,ident=b):0:2:b', +                'PlainIdentifier(scope=0,ident=c):0:5: c', +              }, +            }, +          }, +        }, +      }, +    }, { +      hl('IdentifierName', 'a'), +      hl('SubscriptBracket', '['), +      hl('IdentifierName', 'b'), +      hl('SubscriptColon', ':', 1), +      hl('IdentifierName', 'c', 1), +      hl('SubscriptBracket', ']'), +    }) + +    check_parsing('a[: b]', { +      --           012345 +      ast = { +        { +          'Subscript:0:1:[', +          children = { +            'PlainIdentifier(scope=0,ident=a):0:0:a', +            { +              'Colon:0:2::', +              children = { +                'Missing:0:2:', +                'PlainIdentifier(scope=0,ident=b):0:3: b', +              }, +            }, +          }, +        }, +      }, +    }, { +      hl('IdentifierName', 'a'), +      hl('SubscriptBracket', '['), +      hl('SubscriptColon', ':'), +      hl('IdentifierName', 'b', 1), +      hl('SubscriptBracket', ']'), +    }) + +    check_parsing('a[b :]', { +      --           012345 +      ast = { +        { +          'Subscript:0:1:[', +          children = { +            'PlainIdentifier(scope=0,ident=a):0:0:a', +            { +              'Colon:0:3: :', +              children = { +                'PlainIdentifier(scope=0,ident=b):0:2:b', +              }, +            }, +          }, +        }, +      }, +    }, { +      hl('IdentifierName', 'a'), +      hl('SubscriptBracket', '['), +      hl('IdentifierName', 'b'), +      hl('SubscriptColon', ':', 1), +      hl('SubscriptBracket', ']'), +    }) +    check_parsing('a[b][c][d](e)(f)(g)', { +      --           0123456789012345678 +      --           0         1 +      ast = { +        { +          'Call:0:16:(', +          children = { +            { +              'Call:0:13:(', +              children = { +                { +                  'Call:0:10:(', +                  children = { +                    { +                      'Subscript:0:7:[', +                      children = { +                        { +                          'Subscript:0:4:[', +                          children = { +                            { +                              'Subscript:0:1:[', +                              children = { +                                'PlainIdentifier(scope=0,ident=a):0:0:a', +                                'PlainIdentifier(scope=0,ident=b):0:2:b', +                              }, +                            }, +                            'PlainIdentifier(scope=0,ident=c):0:5:c', +                          }, +                        }, +                        'PlainIdentifier(scope=0,ident=d):0:8:d', +                      }, +                    }, +                    'PlainIdentifier(scope=0,ident=e):0:11:e', +                  }, +                }, +                'PlainIdentifier(scope=0,ident=f):0:14:f', +              }, +            }, +            'PlainIdentifier(scope=0,ident=g):0:17:g', +          }, +        }, +      }, +    }, { +      hl('IdentifierName', 'a'), +      hl('SubscriptBracket', '['), +      hl('IdentifierName', 'b'), +      hl('SubscriptBracket', ']'), +      hl('SubscriptBracket', '['), +      hl('IdentifierName', 'c'), +      hl('SubscriptBracket', ']'), +      hl('SubscriptBracket', '['), +      hl('IdentifierName', 'd'), +      hl('SubscriptBracket', ']'), +      hl('CallingParenthesis', '('), +      hl('IdentifierName', 'e'), +      hl('CallingParenthesis', ')'), +      hl('CallingParenthesis', '('), +      hl('IdentifierName', 'f'), +      hl('CallingParenthesis', ')'), +      hl('CallingParenthesis', '('), +      hl('IdentifierName', 'g'), +      hl('CallingParenthesis', ')'), +    }) +    check_parsing('{a}{b}{c}[d][e][f]', { +      --           012345678901234567 +      --           0         1 +      ast = { +        { +          'Subscript:0:15:[', +          children = { +            { +              'Subscript:0:12:[', +              children = { +                { +                  'Subscript:0:9:[', +                  children = { +                    { +                      'ComplexIdentifier:0:3:', +                      children = { +                        { +                          fmtn('CurlyBracesIdentifier', '-di', ':0:0:{'), +                          children = { +                            'PlainIdentifier(scope=0,ident=a):0:1:a', +                          }, +                        }, +                        { +                          'ComplexIdentifier:0:6:', +                          children = { +                            { +                              fmtn('CurlyBracesIdentifier', '--i', ':0:3:{'), +                              children = { +                                'PlainIdentifier(scope=0,ident=b):0:4:b', +                              }, +                            }, +                            { +                              fmtn('CurlyBracesIdentifier', '--i', ':0:6:{'), +                              children = { +                                'PlainIdentifier(scope=0,ident=c):0:7:c', +                              }, +                            }, +                          }, +                        }, +                      }, +                    }, +                    'PlainIdentifier(scope=0,ident=d):0:10:d', +                  }, +                }, +                'PlainIdentifier(scope=0,ident=e):0:13:e', +              }, +            }, +            'PlainIdentifier(scope=0,ident=f):0:16:f', +          }, +        }, +      }, +    }, { +      hl('Curly', '{'), +      hl('IdentifierName', 'a'), +      hl('Curly', '}'), +      hl('Curly', '{'), +      hl('IdentifierName', 'b'), +      hl('Curly', '}'), +      hl('Curly', '{'), +      hl('IdentifierName', 'c'), +      hl('Curly', '}'), +      hl('SubscriptBracket', '['), +      hl('IdentifierName', 'd'), +      hl('SubscriptBracket', ']'), +      hl('SubscriptBracket', '['), +      hl('IdentifierName', 'e'), +      hl('SubscriptBracket', ']'), +      hl('SubscriptBracket', '['), +      hl('IdentifierName', 'f'), +      hl('SubscriptBracket', ']'), +    }) +  end) +  itp('supports list literals', function() +    check_parsing('[]', { +      --           01 +      ast = { +        'ListLiteral:0:0:[', +      }, +    }, { +      hl('List', '['), +      hl('List', ']'), +    }) + +    check_parsing('[a]', { +      --           012 +      ast = { +        { +          'ListLiteral:0:0:[', +          children = { +            'PlainIdentifier(scope=0,ident=a):0:1:a', +          }, +        }, +      }, +    }, { +      hl('List', '['), +      hl('IdentifierName', 'a'), +      hl('List', ']'), +    }) + +    check_parsing('[a, b]', { +      --           012345 +      ast = { +        { +          'ListLiteral:0:0:[', +          children = { +            { +              'Comma:0:2:,', +              children = { +                'PlainIdentifier(scope=0,ident=a):0:1:a', +                'PlainIdentifier(scope=0,ident=b):0:3: b', +              }, +            }, +          }, +        }, +      }, +    }, { +      hl('List', '['), +      hl('IdentifierName', 'a'), +      hl('Comma', ','), +      hl('IdentifierName', 'b', 1), +      hl('List', ']'), +    }) + +    check_parsing('[a, b, c]', { +      --           012345678 +      ast = { +        { +          'ListLiteral:0:0:[', +          children = { +            { +              'Comma:0:2:,', +              children = { +                'PlainIdentifier(scope=0,ident=a):0:1:a', +                { +                  'Comma:0:5:,', +                  children = { +                    'PlainIdentifier(scope=0,ident=b):0:3: b', +                    'PlainIdentifier(scope=0,ident=c):0:6: c', +                  }, +                }, +              }, +            }, +          }, +        }, +      }, +    }, { +      hl('List', '['), +      hl('IdentifierName', 'a'), +      hl('Comma', ','), +      hl('IdentifierName', 'b', 1), +      hl('Comma', ','), +      hl('IdentifierName', 'c', 1), +      hl('List', ']'), +    }) + +    check_parsing('[a, b, c, ]', { +      --           01234567890 +      --           0         1 +      ast = { +        { +          'ListLiteral:0:0:[', +          children = { +            { +              'Comma:0:2:,', +              children = { +                'PlainIdentifier(scope=0,ident=a):0:1:a', +                { +                  'Comma:0:5:,', +                  children = { +                    'PlainIdentifier(scope=0,ident=b):0:3: b', +                    { +                      'Comma:0:8:,', +                      children = { +                        'PlainIdentifier(scope=0,ident=c):0:6: c', +                      }, +                    }, +                  }, +                }, +              }, +            }, +          }, +        }, +      }, +    }, { +      hl('List', '['), +      hl('IdentifierName', 'a'), +      hl('Comma', ','), +      hl('IdentifierName', 'b', 1), +      hl('Comma', ','), +      hl('IdentifierName', 'c', 1), +      hl('Comma', ','), +      hl('List', ']', 1), +    }) + +    check_parsing('[a : b, c : d]', { +      --           01234567890123 +      --           0         1 +      ast = { +        { +          'ListLiteral:0:0:[', +          children = { +            { +              'Comma:0:6:,', +              children = { +                { +                  'Colon:0:2: :', +                  children = { +                    'PlainIdentifier(scope=0,ident=a):0:1:a', +                    'PlainIdentifier(scope=0,ident=b):0:4: b', +                  }, +                }, +                { +                  'Colon:0:9: :', +                  children = { +                    'PlainIdentifier(scope=0,ident=c):0:7: c', +                    'PlainIdentifier(scope=0,ident=d):0:11: d', +                  }, +                }, +              }, +            }, +          }, +        }, +      }, +      err = { +        arg = ': b, c : d]', +        msg = 'E15: Colon outside of dictionary or ternary operator: %.*s', +      }, +    }, { +      hl('List', '['), +      hl('IdentifierName', 'a'), +      hl('InvalidColon', ':', 1), +      hl('IdentifierName', 'b', 1), +      hl('Comma', ','), +      hl('IdentifierName', 'c', 1), +      hl('InvalidColon', ':', 1), +      hl('IdentifierName', 'd', 1), +      hl('List', ']'), +    }) + +    check_parsing(']', { +      --           0 +      ast = { +        'ListLiteral:0:0:', +      }, +      err = { +        arg = ']', +        msg = 'E15: Unexpected closing figure brace: %.*s', +      }, +    }, { +      hl('InvalidList', ']'), +    }) + +    check_parsing('a]', { +      --           01 +      ast = { +        { +          'ListLiteral:0:1:', +          children = { +            'PlainIdentifier(scope=0,ident=a):0:0:a', +          }, +        }, +      }, +      err = { +        arg = ']', +        msg = 'E15: Unexpected closing figure brace: %.*s', +      }, +    }, { +      hl('IdentifierName', 'a'), +      hl('InvalidList', ']'), +    }) + +    check_parsing('[] []', { +      --           01234 +      ast = { +        { +          'OpMissing:0:2:', +          children = { +            'ListLiteral:0:0:[', +            'ListLiteral:0:2: [', +          }, +        }, +      }, +      err = { +        arg = '[]', +        msg = 'E15: Missing operator: %.*s', +      }, +    }, { +      hl('List', '['), +      hl('List', ']'), +      hl('InvalidSpacing', ' '), +      hl('List', '['), +      hl('List', ']'), +    }, { +      [1] = { +        ast = { +          len = 3, +          err = REMOVE_THIS, +          ast = { +            'ListLiteral:0:0:[', +          }, +        }, +        hl_fs = { +          [3] = REMOVE_THIS, +          [4] = REMOVE_THIS, +          [5] = REMOVE_THIS, +        }, +      }, +    }) + +    check_parsing('[][]', { +      --           0123 +      ast = { +        { +          'Subscript:0:2:[', +          children = { +            'ListLiteral:0:0:[', +          }, +        }, +      }, +      err = { +        arg = ']', +        msg = 'E15: Expected value, got closing bracket: %.*s', +      }, +    }, { +      hl('List', '['), +      hl('List', ']'), +      hl('SubscriptBracket', '['), +      hl('InvalidSubscriptBracket', ']'), +    }) + +    check_parsing('[', { +      --           0 +      ast = { +        'ListLiteral:0:0:[', +      }, +      err = { +        arg = '', +        msg = 'E15: Expected value, got EOC: %.*s', +      }, +    }, { +      hl('List', '['), +    }) + +    check_parsing('[1', { +      --           01 +      ast = { +        { +          'ListLiteral:0:0:[', +          children = { +            'Integer(val=1):0:1:1', +          }, +        }, +      }, +      err = { +        arg = '[1', +        msg = 'E697: Missing end of List \']\': %.*s', +      }, +    }, { +      hl('List', '['), +      hl('Number', '1'), +    }) +  end) +  itp('works with strings', function() +    check_parsing('\'abc\'', { +      --           01234 +      ast = { +        fmtn('SingleQuotedString', 'val="abc"', ':0:0:\'abc\''), +      }, +    }, { +      hl('SingleQuote', '\''), +      hl('SingleQuotedBody', 'abc'), +      hl('SingleQuote', '\''), +    }) +    check_parsing('"abc"', { +      --           01234 +      ast = { +        fmtn('DoubleQuotedString', 'val="abc"', ':0:0:"abc"'), +      }, +    }, { +      hl('DoubleQuote', '"'), +      hl('DoubleQuotedBody', 'abc'), +      hl('DoubleQuote', '"'), +    }) +    check_parsing('\'\'', { +      --           01 +      ast = { +        fmtn('SingleQuotedString', 'val=NULL', ':0:0:\'\''), +      }, +    }, { +      hl('SingleQuote', '\''), +      hl('SingleQuote', '\''), +    }) +    check_parsing('""', { +      --           01 +      ast = { +        fmtn('DoubleQuotedString', 'val=NULL', ':0:0:""'), +      }, +    }, { +      hl('DoubleQuote', '"'), +      hl('DoubleQuote', '"'), +    }) +    check_parsing('"', { +      --           0 +      ast = { +        fmtn('DoubleQuotedString', 'val=NULL', ':0:0:"'), +      }, +      err = { +        arg = '"', +        msg = 'E114: Missing double quote: %.*s', +      }, +    }, { +      hl('InvalidDoubleQuote', '"'), +    }) +    check_parsing('\'', { +      --           0 +      ast = { +        fmtn('SingleQuotedString', 'val=NULL', ':0:0:\''), +      }, +      err = { +        arg = '\'', +        msg = 'E115: Missing single quote: %.*s', +      }, +    }, { +      hl('InvalidSingleQuote', '\''), +    }) +    check_parsing('"a', { +      --           01 +      ast = { +        fmtn('DoubleQuotedString', 'val="a"', ':0:0:"a'), +      }, +      err = { +        arg = '"a', +        msg = 'E114: Missing double quote: %.*s', +      }, +    }, { +      hl('InvalidDoubleQuote', '"'), +      hl('InvalidDoubleQuotedBody', 'a'), +    }) +    check_parsing('\'a', { +      --           01 +      ast = { +        fmtn('SingleQuotedString', 'val="a"', ':0:0:\'a'), +      }, +      err = { +        arg = '\'a', +        msg = 'E115: Missing single quote: %.*s', +      }, +    }, { +      hl('InvalidSingleQuote', '\''), +      hl('InvalidSingleQuotedBody', 'a'), +    }) +    check_parsing('\'abc\'\'def\'', { +      --           0123456789 +      ast = { +        fmtn('SingleQuotedString', 'val="abc\'def"', ':0:0:\'abc\'\'def\''), +      }, +    }, { +      hl('SingleQuote', '\''), +      hl('SingleQuotedBody', 'abc'), +      hl('SingleQuotedQuote', '\'\''), +      hl('SingleQuotedBody', 'def'), +      hl('SingleQuote', '\''), +    }) +    check_parsing('\'abc\'\'', { +      --           012345 +      ast = { +        fmtn('SingleQuotedString', 'val="abc\'"', ':0:0:\'abc\'\''), +      }, +      err = { +        arg = '\'abc\'\'', +        msg = 'E115: Missing single quote: %.*s', +      }, +    }, { +      hl('InvalidSingleQuote', '\''), +      hl('InvalidSingleQuotedBody', 'abc'), +      hl('InvalidSingleQuotedQuote', '\'\''), +    }) +    check_parsing('\'\'\'\'\'\'\'\'', { +      --           01234567 +      ast = { +        fmtn('SingleQuotedString', 'val="\'\'\'"', ':0:0:\'\'\'\'\'\'\'\''), +      }, +    }, { +      hl('SingleQuote', '\''), +      hl('SingleQuotedQuote', '\'\''), +      hl('SingleQuotedQuote', '\'\''), +      hl('SingleQuotedQuote', '\'\''), +      hl('SingleQuote', '\''), +    }) +    check_parsing('\'\'\'a\'\'\'\'bc\'', { +      --           01234567890 +      --           0         1 +      ast = { +        fmtn('SingleQuotedString', 'val="\'a\'\'bc"', ':0:0:\'\'\'a\'\'\'\'bc\''), +      }, +    }, { +      hl('SingleQuote', '\''), +      hl('SingleQuotedQuote', '\'\''), +      hl('SingleQuotedBody', 'a'), +      hl('SingleQuotedQuote', '\'\''), +      hl('SingleQuotedQuote', '\'\''), +      hl('SingleQuotedBody', 'bc'), +      hl('SingleQuote', '\''), +    }) +    check_parsing('"\\"\\"\\"\\""', { +      --           0123456789 +      ast = { +        fmtn('DoubleQuotedString', 'val="\\"\\"\\"\\""', ':0:0:"\\"\\"\\"\\""'), +      }, +    }, { +      hl('DoubleQuote', '"'), +      hl('DoubleQuotedEscape', '\\"'), +      hl('DoubleQuotedEscape', '\\"'), +      hl('DoubleQuotedEscape', '\\"'), +      hl('DoubleQuotedEscape', '\\"'), +      hl('DoubleQuote', '"'), +    }) +    check_parsing('"abc\\"def\\"ghi\\"jkl\\"mno"', { +      --           0123456789012345678901234 +      --           0         1         2 +      ast = { +        fmtn('DoubleQuotedString', 'val="abc\\"def\\"ghi\\"jkl\\"mno"', ':0:0:"abc\\"def\\"ghi\\"jkl\\"mno"'), +      }, +    }, { +      hl('DoubleQuote', '"'), +      hl('DoubleQuotedBody', 'abc'), +      hl('DoubleQuotedEscape', '\\"'), +      hl('DoubleQuotedBody', 'def'), +      hl('DoubleQuotedEscape', '\\"'), +      hl('DoubleQuotedBody', 'ghi'), +      hl('DoubleQuotedEscape', '\\"'), +      hl('DoubleQuotedBody', 'jkl'), +      hl('DoubleQuotedEscape', '\\"'), +      hl('DoubleQuotedBody', 'mno'), +      hl('DoubleQuote', '"'), +    }) +    check_parsing('"\\b\\e\\f\\r\\t\\\\"', { +      --           0123456789012345 +      --           0         1 +      ast = { +        [[DoubleQuotedString(val="\8\27\12\13\9\\"):0:0:"\b\e\f\r\t\\"]], +      }, +    }, { +      hl('DoubleQuote', '"'), +      hl('DoubleQuotedEscape', '\\b'), +      hl('DoubleQuotedEscape', '\\e'), +      hl('DoubleQuotedEscape', '\\f'), +      hl('DoubleQuotedEscape', '\\r'), +      hl('DoubleQuotedEscape', '\\t'), +      hl('DoubleQuotedEscape', '\\\\'), +      hl('DoubleQuote', '"'), +    }) +    check_parsing('"\\n\n"', { +      --           01234 +      ast = { +        fmtn('DoubleQuotedString', 'val="\\\n\\\n"', ':0:0:"\\n\n"'), +      }, +    }, { +      hl('DoubleQuote', '"'), +      hl('DoubleQuotedEscape', '\\n'), +      hl('DoubleQuotedBody', '\n'), +      hl('DoubleQuote', '"'), +    }) +    check_parsing('"\\x00"', { +      --           012345 +      ast = { +        fmtn('DoubleQuotedString', 'val="\\0"', ':0:0:"\\x00"'), +      }, +    }, { +      hl('DoubleQuote', '"'), +      hl('DoubleQuotedEscape', '\\x00'), +      hl('DoubleQuote', '"'), +    }) +    check_parsing('"\\xFF"', { +      --           012345 +      ast = { +        fmtn('DoubleQuotedString', 'val="\255"', ':0:0:"\\xFF"'), +      }, +    }, { +      hl('DoubleQuote', '"'), +      hl('DoubleQuotedEscape', '\\xFF'), +      hl('DoubleQuote', '"'), +    }) +    check_parsing('"\\xF"', { +      --           012345 +      ast = { +        fmtn('DoubleQuotedString', 'val="\\15"', ':0:0:"\\xF"'), +      }, +    }, { +      hl('DoubleQuote', '"'), +      hl('DoubleQuotedEscape', '\\xF'), +      hl('DoubleQuote', '"'), +    }) +    check_parsing('"\\u00AB"', { +      --           01234567 +      ast = { +        fmtn('DoubleQuotedString', 'val="«"', ':0:0:"\\u00AB"'), +      }, +    }, { +      hl('DoubleQuote', '"'), +      hl('DoubleQuotedEscape', '\\u00AB'), +      hl('DoubleQuote', '"'), +    }) +    check_parsing('"\\U000000AB"', { +      --           01234567 +      ast = { +        fmtn('DoubleQuotedString', 'val="«"', ':0:0:"\\U000000AB"'), +      }, +    }, { +      hl('DoubleQuote', '"'), +      hl('DoubleQuotedEscape', '\\U000000AB'), +      hl('DoubleQuote', '"'), +    }) +    check_parsing('"\\x"', { +      --           0123 +      ast = { +        fmtn('DoubleQuotedString', 'val="x"', ':0:0:"\\x"'), +      }, +    }, { +      hl('DoubleQuote', '"'), +      hl('DoubleQuotedUnknownEscape', '\\x'), +      hl('DoubleQuote', '"'), +    }) + +    check_parsing('"\\x', { +      --           012 +      ast = { +        fmtn('DoubleQuotedString', 'val="x"', ':0:0:"\\x'), +      }, +      err = { +        arg = '"\\x', +        msg = 'E114: Missing double quote: %.*s', +      }, +    }, { +      hl('InvalidDoubleQuote', '"'), +      hl('InvalidDoubleQuotedUnknownEscape', '\\x'), +    }) + +    check_parsing('"\\xF', { +      --           0123 +      ast = { +        fmtn('DoubleQuotedString', 'val="\\15"', ':0:0:"\\xF'), +      }, +      err = { +        arg = '"\\xF', +        msg = 'E114: Missing double quote: %.*s', +      }, +    }, { +      hl('InvalidDoubleQuote', '"'), +      hl('InvalidDoubleQuotedEscape', '\\xF'), +    }) + +    check_parsing('"\\u"', { +      --           0123 +      ast = { +        fmtn('DoubleQuotedString', 'val="u"', ':0:0:"\\u"'), +      }, +    }, { +      hl('DoubleQuote', '"'), +      hl('DoubleQuotedUnknownEscape', '\\u'), +      hl('DoubleQuote', '"'), +    }) + +    check_parsing('"\\u', { +      --           012 +      ast = { +        fmtn('DoubleQuotedString', 'val="u"', ':0:0:"\\u'), +      }, +      err = { +        arg = '"\\u', +        msg = 'E114: Missing double quote: %.*s', +      }, +    }, { +      hl('InvalidDoubleQuote', '"'), +      hl('InvalidDoubleQuotedUnknownEscape', '\\u'), +    }) + +    check_parsing('"\\U', { +      --           012 +      ast = { +        fmtn('DoubleQuotedString', 'val="U"', ':0:0:"\\U'), +      }, +      err = { +        arg = '"\\U', +        msg = 'E114: Missing double quote: %.*s', +      }, +    }, { +      hl('InvalidDoubleQuote', '"'), +      hl('InvalidDoubleQuotedUnknownEscape', '\\U'), +    }) + +    check_parsing('"\\U"', { +      --           0123 +      ast = { +        fmtn('DoubleQuotedString', 'val="U"', ':0:0:"\\U"'), +      }, +    }, { +      hl('DoubleQuote', '"'), +      hl('DoubleQuotedUnknownEscape', '\\U'), +      hl('DoubleQuote', '"'), +    }) + +    check_parsing('"\\xFX"', { +      --           012345 +      ast = { +        fmtn('DoubleQuotedString', 'val="\\15X"', ':0:0:"\\xFX"'), +      }, +    }, { +      hl('DoubleQuote', '"'), +      hl('DoubleQuotedEscape', '\\xF'), +      hl('DoubleQuotedBody', 'X'), +      hl('DoubleQuote', '"'), +    }) + +    check_parsing('"\\XFX"', { +      --           012345 +      ast = { +        fmtn('DoubleQuotedString', 'val="\\15X"', ':0:0:"\\XFX"'), +      }, +    }, { +      hl('DoubleQuote', '"'), +      hl('DoubleQuotedEscape', '\\XF'), +      hl('DoubleQuotedBody', 'X'), +      hl('DoubleQuote', '"'), +    }) + +    check_parsing('"\\xX"', { +      --           01234 +      ast = { +        fmtn('DoubleQuotedString', 'val="xX"', ':0:0:"\\xX"'), +      }, +    }, { +      hl('DoubleQuote', '"'), +      hl('DoubleQuotedUnknownEscape', '\\x'), +      hl('DoubleQuotedBody', 'X'), +      hl('DoubleQuote', '"'), +    }) + +    check_parsing('"\\XX"', { +      --           01234 +      ast = { +        fmtn('DoubleQuotedString', 'val="XX"', ':0:0:"\\XX"'), +      }, +    }, { +      hl('DoubleQuote', '"'), +      hl('DoubleQuotedUnknownEscape', '\\X'), +      hl('DoubleQuotedBody', 'X'), +      hl('DoubleQuote', '"'), +    }) + +    check_parsing('"\\uX"', { +      --           01234 +      ast = { +        fmtn('DoubleQuotedString', 'val="uX"', ':0:0:"\\uX"'), +      }, +    }, { +      hl('DoubleQuote', '"'), +      hl('DoubleQuotedUnknownEscape', '\\u'), +      hl('DoubleQuotedBody', 'X'), +      hl('DoubleQuote', '"'), +    }) + +    check_parsing('"\\UX"', { +      --           01234 +      ast = { +        fmtn('DoubleQuotedString', 'val="UX"', ':0:0:"\\UX"'), +      }, +    }, { +      hl('DoubleQuote', '"'), +      hl('DoubleQuotedUnknownEscape', '\\U'), +      hl('DoubleQuotedBody', 'X'), +      hl('DoubleQuote', '"'), +    }) + +    check_parsing('"\\x0X"', { +      --           012345 +      ast = { +        fmtn('DoubleQuotedString', 'val="\\0X"', ':0:0:"\\x0X"'), +      }, +    }, { +      hl('DoubleQuote', '"'), +      hl('DoubleQuotedEscape', '\\x0'), +      hl('DoubleQuotedBody', 'X'), +      hl('DoubleQuote', '"'), +    }) + +    check_parsing('"\\X0X"', { +      --           012345 +      ast = { +        fmtn('DoubleQuotedString', 'val="\\0X"', ':0:0:"\\X0X"'), +      }, +    }, { +      hl('DoubleQuote', '"'), +      hl('DoubleQuotedEscape', '\\X0'), +      hl('DoubleQuotedBody', 'X'), +      hl('DoubleQuote', '"'), +    }) + +    check_parsing('"\\u0X"', { +      --           012345 +      ast = { +        fmtn('DoubleQuotedString', 'val="\\0X"', ':0:0:"\\u0X"'), +      }, +    }, { +      hl('DoubleQuote', '"'), +      hl('DoubleQuotedEscape', '\\u0'), +      hl('DoubleQuotedBody', 'X'), +      hl('DoubleQuote', '"'), +    }) + +    check_parsing('"\\U0X"', { +      --           012345 +      ast = { +        fmtn('DoubleQuotedString', 'val="\\0X"', ':0:0:"\\U0X"'), +      }, +    }, { +      hl('DoubleQuote', '"'), +      hl('DoubleQuotedEscape', '\\U0'), +      hl('DoubleQuotedBody', 'X'), +      hl('DoubleQuote', '"'), +    }) + +    check_parsing('"\\x00X"', { +      --           0123456 +      ast = { +        fmtn('DoubleQuotedString', 'val="\\0X"', ':0:0:"\\x00X"'), +      }, +    }, { +      hl('DoubleQuote', '"'), +      hl('DoubleQuotedEscape', '\\x00'), +      hl('DoubleQuotedBody', 'X'), +      hl('DoubleQuote', '"'), +    }) + +    check_parsing('"\\X00X"', { +      --           0123456 +      ast = { +        fmtn('DoubleQuotedString', 'val="\\0X"', ':0:0:"\\X00X"'), +      }, +    }, { +      hl('DoubleQuote', '"'), +      hl('DoubleQuotedEscape', '\\X00'), +      hl('DoubleQuotedBody', 'X'), +      hl('DoubleQuote', '"'), +    }) + +    check_parsing('"\\u00X"', { +      --           0123456 +      ast = { +        fmtn('DoubleQuotedString', 'val="\\0X"', ':0:0:"\\u00X"'), +      }, +    }, { +      hl('DoubleQuote', '"'), +      hl('DoubleQuotedEscape', '\\u00'), +      hl('DoubleQuotedBody', 'X'), +      hl('DoubleQuote', '"'), +    }) + +    check_parsing('"\\U00X"', { +      --           0123456 +      ast = { +        fmtn('DoubleQuotedString', 'val="\\0X"', ':0:0:"\\U00X"'), +      }, +    }, { +      hl('DoubleQuote', '"'), +      hl('DoubleQuotedEscape', '\\U00'), +      hl('DoubleQuotedBody', 'X'), +      hl('DoubleQuote', '"'), +    }) + +    check_parsing('"\\u000X"', { +      --           01234567 +      ast = { +        fmtn('DoubleQuotedString', 'val="\\0X"', ':0:0:"\\u000X"'), +      }, +    }, { +      hl('DoubleQuote', '"'), +      hl('DoubleQuotedEscape', '\\u000'), +      hl('DoubleQuotedBody', 'X'), +      hl('DoubleQuote', '"'), +    }) + +    check_parsing('"\\U000X"', { +      --           01234567 +      ast = { +        fmtn('DoubleQuotedString', 'val="\\0X"', ':0:0:"\\U000X"'), +      }, +    }, { +      hl('DoubleQuote', '"'), +      hl('DoubleQuotedEscape', '\\U000'), +      hl('DoubleQuotedBody', 'X'), +      hl('DoubleQuote', '"'), +    }) + +    check_parsing('"\\u0000X"', { +      --           012345678 +      ast = { +        fmtn('DoubleQuotedString', 'val="\\0X"', ':0:0:"\\u0000X"'), +      }, +    }, { +      hl('DoubleQuote', '"'), +      hl('DoubleQuotedEscape', '\\u0000'), +      hl('DoubleQuotedBody', 'X'), +      hl('DoubleQuote', '"'), +    }) + +    check_parsing('"\\U0000X"', { +      --           012345678 +      ast = { +        fmtn('DoubleQuotedString', 'val="\\0X"', ':0:0:"\\U0000X"'), +      }, +    }, { +      hl('DoubleQuote', '"'), +      hl('DoubleQuotedEscape', '\\U0000'), +      hl('DoubleQuotedBody', 'X'), +      hl('DoubleQuote', '"'), +    }) + +    check_parsing('"\\U00000X"', { +      --           0123456789 +      ast = { +        fmtn('DoubleQuotedString', 'val="\\0X"', ':0:0:"\\U00000X"'), +      }, +    }, { +      hl('DoubleQuote', '"'), +      hl('DoubleQuotedEscape', '\\U00000'), +      hl('DoubleQuotedBody', 'X'), +      hl('DoubleQuote', '"'), +    }) + +    check_parsing('"\\U000000X"', { +      --           01234567890 +      --           0         1 +      ast = { +        fmtn('DoubleQuotedString', 'val="\\0X"', ':0:0:"\\U000000X"'), +      }, +    }, { +      hl('DoubleQuote', '"'), +      hl('DoubleQuotedEscape', '\\U000000'), +      hl('DoubleQuotedBody', 'X'), +      hl('DoubleQuote', '"'), +    }) + +    check_parsing('"\\U0000000X"', { +      --           012345678901 +      --           0         1 +      ast = { +        fmtn('DoubleQuotedString', 'val="\\0X"', ':0:0:"\\U0000000X"'), +      }, +    }, { +      hl('DoubleQuote', '"'), +      hl('DoubleQuotedEscape', '\\U0000000'), +      hl('DoubleQuotedBody', 'X'), +      hl('DoubleQuote', '"'), +    }) + +    check_parsing('"\\U00000000X"', { +      --           0123456789012 +      --           0         1 +      ast = { +        fmtn('DoubleQuotedString', 'val="\\0X"', ':0:0:"\\U00000000X"'), +      }, +    }, { +      hl('DoubleQuote', '"'), +      hl('DoubleQuotedEscape', '\\U00000000'), +      hl('DoubleQuotedBody', 'X'), +      hl('DoubleQuote', '"'), +    }) + +    check_parsing('"\\x000X"', { +      --           01234567 +      ast = { +        fmtn('DoubleQuotedString', 'val="\\0000X"', ':0:0:"\\x000X"'), +      }, +    }, { +      hl('DoubleQuote', '"'), +      hl('DoubleQuotedEscape', '\\x00'), +      hl('DoubleQuotedBody', '0X'), +      hl('DoubleQuote', '"'), +    }) + +    check_parsing('"\\X000X"', { +      --           01234567 +      ast = { +        fmtn('DoubleQuotedString', 'val="\\0000X"', ':0:0:"\\X000X"'), +      }, +    }, { +      hl('DoubleQuote', '"'), +      hl('DoubleQuotedEscape', '\\X00'), +      hl('DoubleQuotedBody', '0X'), +      hl('DoubleQuote', '"'), +    }) + +    check_parsing('"\\u00000X"', { +      --           0123456789 +      ast = { +        fmtn('DoubleQuotedString', 'val="\\0000X"', ':0:0:"\\u00000X"'), +      }, +    }, { +      hl('DoubleQuote', '"'), +      hl('DoubleQuotedEscape', '\\u0000'), +      hl('DoubleQuotedBody', '0X'), +      hl('DoubleQuote', '"'), +    }) + +    check_parsing('"\\U000000000X"', { +      --           01234567890123 +      --           0         1 +      ast = { +        fmtn('DoubleQuotedString', 'val="\\0000X"', ':0:0:"\\U000000000X"'), +      }, +    }, { +      hl('DoubleQuote', '"'), +      hl('DoubleQuotedEscape', '\\U00000000'), +      hl('DoubleQuotedBody', '0X'), +      hl('DoubleQuote', '"'), +    }) + +    check_parsing('"\\0"', { +      --           0123 +      ast = { +        fmtn('DoubleQuotedString', 'val="\\0"', ':0:0:"\\0"'), +      }, +    }, { +      hl('DoubleQuote', '"'), +      hl('DoubleQuotedEscape', '\\0'), +      hl('DoubleQuote', '"'), +    }) + +    check_parsing('"\\00"', { +      --           01234 +      ast = { +        fmtn('DoubleQuotedString', 'val="\\0"', ':0:0:"\\00"'), +      }, +    }, { +      hl('DoubleQuote', '"'), +      hl('DoubleQuotedEscape', '\\00'), +      hl('DoubleQuote', '"'), +    }) + +    check_parsing('"\\000"', { +      --           012345 +      ast = { +        fmtn('DoubleQuotedString', 'val="\\0"', ':0:0:"\\000"'), +      }, +    }, { +      hl('DoubleQuote', '"'), +      hl('DoubleQuotedEscape', '\\000'), +      hl('DoubleQuote', '"'), +    }) + +    check_parsing('"\\0000"', { +      --           0123456 +      ast = { +        fmtn('DoubleQuotedString', 'val="\\0000"', ':0:0:"\\0000"'), +      }, +    }, { +      hl('DoubleQuote', '"'), +      hl('DoubleQuotedEscape', '\\000'), +      hl('DoubleQuotedBody', '0'), +      hl('DoubleQuote', '"'), +    }) + +    check_parsing('"\\8"', { +      --           0123 +      ast = { +        fmtn('DoubleQuotedString', 'val="8"', ':0:0:"\\8"'), +      }, +    }, { +      hl('DoubleQuote', '"'), +      hl('DoubleQuotedUnknownEscape', '\\8'), +      hl('DoubleQuote', '"'), +    }) + +    check_parsing('"\\08"', { +      --           01234 +      ast = { +        fmtn('DoubleQuotedString', 'val="\\0008"', ':0:0:"\\08"'), +      }, +    }, { +      hl('DoubleQuote', '"'), +      hl('DoubleQuotedEscape', '\\0'), +      hl('DoubleQuotedBody', '8'), +      hl('DoubleQuote', '"'), +    }) + +    check_parsing('"\\008"', { +      --           012345 +      ast = { +        fmtn('DoubleQuotedString', 'val="\\0008"', ':0:0:"\\008"'), +      }, +    }, { +      hl('DoubleQuote', '"'), +      hl('DoubleQuotedEscape', '\\00'), +      hl('DoubleQuotedBody', '8'), +      hl('DoubleQuote', '"'), +    }) + +    check_parsing('"\\0008"', { +      --           0123456 +      ast = { +        fmtn('DoubleQuotedString', 'val="\\0008"', ':0:0:"\\0008"'), +      }, +    }, { +      hl('DoubleQuote', '"'), +      hl('DoubleQuotedEscape', '\\000'), +      hl('DoubleQuotedBody', '8'), +      hl('DoubleQuote', '"'), +    }) + +    check_parsing('"\\777"', { +      --           012345 +      ast = { +        fmtn('DoubleQuotedString', 'val="\255"', ':0:0:"\\777"'), +      }, +    }, { +      hl('DoubleQuote', '"'), +      hl('DoubleQuotedEscape', '\\777'), +      hl('DoubleQuote', '"'), +    }) + +    check_parsing('"\\050"', { +      --           012345 +      ast = { +        fmtn('DoubleQuotedString', 'val="\40"', ':0:0:"\\050"'), +      }, +    }, { +      hl('DoubleQuote', '"'), +      hl('DoubleQuotedEscape', '\\050'), +      hl('DoubleQuote', '"'), +    }) + +    check_parsing('"\\<C-u>"', { +      --           012345 +      ast = { +        fmtn('DoubleQuotedString', 'val="\\21"', ':0:0:"\\<C-u>"'), +      }, +    }, { +      hl('DoubleQuote', '"'), +      hl('DoubleQuotedEscape', '\\<C-u>'), +      hl('DoubleQuote', '"'), +    }) + +    check_parsing('"\\<', { +      --           012 +      ast = { +        fmtn('DoubleQuotedString', 'val="<"', ':0:0:"\\<'), +      }, +      err = { +        arg = '"\\<', +        msg = 'E114: Missing double quote: %.*s', +      }, +    }, { +      hl('InvalidDoubleQuote', '"'), +      hl('InvalidDoubleQuotedUnknownEscape', '\\<'), +    }) + +    check_parsing('"\\<"', { +      --           0123 +      ast = { +        fmtn('DoubleQuotedString', 'val="<"', ':0:0:"\\<"'), +      }, +    }, { +      hl('DoubleQuote', '"'), +      hl('DoubleQuotedUnknownEscape', '\\<'), +      hl('DoubleQuote', '"'), +    }) + +    check_parsing('"\\<C-u"', { +      --           0123456 +      ast = { +        fmtn('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 = { +          len = 4, +          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 = { +          len = 1, +          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', { +      len = 0, +      ast = nil, +      err = { +        arg = '\0002&A:\000', +        msg = 'E15: Expected value, got EOC: %.*s', +      }, +    }, { +    }, { +      [2] = { +        ast = { +          len = REMOVE_THIS, +          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 = { +          len = 2, +          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}, { +      len = 1, +      ast = { +        'Integer(val=0):0:0:0', +      }, +    }, { +      hl('Number', '0'), +    }) +    check_parsing({data='001', size=2}, { +      len = 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 = { +        fmtn('DoubleQuotedString', 'val="U"', ':0:0:"\\U'), +      }, +      err = { +        arg = '"\\U', +        msg = 'E114: Missing double quote: %.*s', +      }, +    }, { +      hl('InvalidDoubleQuote', '"'), +      hl('InvalidDoubleQuotedUnknownEscape', '\\U'), +    }) +    check_parsing('|"\\U\\', { +      --           01234 +      len = 0, +      err = { +        arg = '|"\\U\\', +        msg = 'E15: Expected value, got EOC: %.*s', +      }, +    }, { +    }, { +      [2] = { +        ast = { +          len = REMOVE_THIS, +          ast = { +            { +              'Or:0:0:|', +              children = { +                'Missing:0:0:', +                fmtn('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 +      len = 0, +      err = { +        arg = '|"\\e"', +        msg = 'E15: Expected value, got EOC: %.*s', +      }, +    }, { +    }, { +      [2] = { +        ast = { +          len = REMOVE_THIS, +          ast = { +            { +              'Or:0:0:|', +              children = { +                'Missing:0:0:', +                fmtn('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 +      len = 0, +      err = { +        arg = '|\029', +        msg = 'E15: Expected value, got EOC: %.*s', +      }, +    }, { +    }, { +      [2] = { +        ast = { +          len = REMOVE_THIS, +          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 = { +        fmtn('DoubleQuotedString', 'val="<"', ':0:0:"\\<'), +      }, +      err = { +        arg = '"\\<', +        msg = 'E114: Missing double quote: %.*s', +      }, +    }, { +      hl('InvalidDoubleQuote', '"'), +      hl('InvalidDoubleQuotedUnknownEscape', '\\<'), +    }) +    check_parsing('"\\1', { +      --           012 +      ast = { +        fmtn('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 = { +            fmtn('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 = { +          len = 1, +          ast = { +            fmtn('UnknownFigure', '---', ':0:0:'), +          }, +        }, +        hl_fs = { +          [2] = REMOVE_THIS, +        }, +      }, +    }) +    check_parsing(':?\000\000\000\000\000\000\000', { +      len = 2, +      ast = { +        { +          'Colon:0:0::', +          children = { +            'Missing:0:0:', +            { +              'Ternary:0:1:?', +              children = { +                'Missing:0:1:', +                'TernaryValue:0:1:?', +              }, +            }, +          }, +        }, +      }, +      err = { +        arg = ':?\000\000\000\000\000\000\000', +        msg = 'E15: Colon outside of dictionary or ternary operator: %.*s', +      }, +    }, { +      hl('InvalidColon', ':'), +      hl('InvalidTernary', '?'), +    }, { +      [2] = { +        ast = { +          len = REMOVE_THIS, +        }, +        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) +  itp('works with assignments', function() +    check_asgn_parsing('a=b', { +      --                012 +      ast = { +        { +          'Assignment(Plain):0:1:=', +          children = { +            'PlainIdentifier(scope=0,ident=a):0:0:a', +            'PlainIdentifier(scope=0,ident=b):0:2:b', +          }, +        }, +      }, +    }, { +      hl('IdentifierName', 'a'), +      hl('PlainAssignment', '='), +      hl('IdentifierName', 'b'), +    }) + +    check_asgn_parsing('a+=b', { +      --                0123 +      ast = { +        { +          'Assignment(Add):0:1:+=', +          children = { +            'PlainIdentifier(scope=0,ident=a):0:0:a', +            'PlainIdentifier(scope=0,ident=b):0:3:b', +          }, +        }, +      }, +    }, { +      hl('IdentifierName', 'a'), +      hl('AssignmentWithAddition', '+='), +      hl('IdentifierName', 'b'), +    }) + +    check_asgn_parsing('a-=b', { +      --                0123 +      ast = { +        { +          'Assignment(Subtract):0:1:-=', +          children = { +            'PlainIdentifier(scope=0,ident=a):0:0:a', +            'PlainIdentifier(scope=0,ident=b):0:3:b', +          }, +        }, +      }, +    }, { +      hl('IdentifierName', 'a'), +      hl('AssignmentWithSubtraction', '-='), +      hl('IdentifierName', 'b'), +    }) + +    check_asgn_parsing('a.=b', { +      --                0123 +      ast = { +        { +          'Assignment(Concat):0:1:.=', +          children = { +            'PlainIdentifier(scope=0,ident=a):0:0:a', +            'PlainIdentifier(scope=0,ident=b):0:3:b', +          }, +        }, +      }, +    }, { +      hl('IdentifierName', 'a'), +      hl('AssignmentWithConcatenation', '.='), +      hl('IdentifierName', 'b'), +    }) + +    check_asgn_parsing('a', { +      --                0 +      ast = { +        'PlainIdentifier(scope=0,ident=a):0:0:a', +      }, +    }, { +      hl('IdentifierName', 'a'), +    }) + +    check_asgn_parsing('a b', { +      --                012 +      ast = { +        { +          'OpMissing:0:1:', +          children = { +            'PlainIdentifier(scope=0,ident=a):0:0:a', +            'PlainIdentifier(scope=0,ident=b):0:1: b', +          }, +        }, +      }, +      err = { +        arg = 'b', +        msg = 'E15: Expected assignment operator or subscript: %.*s', +      }, +    }, { +      hl('IdentifierName', 'a'), +      hl('InvalidSpacing', ' '), +      hl('IdentifierName', 'b'), +    }, { +      [5] = { +        ast = { +          len = 2, +          ast = { +            'PlainIdentifier(scope=0,ident=a):0:0:a', +          }, +          err = REMOVE_THIS, +        }, +        hl_fs = { +          [2] = REMOVE_THIS, +          [3] = REMOVE_THIS, +        } +      }, +    }) + +    check_asgn_parsing('[a, b, c]', { +      --                012345678 +      ast = { +        { +          'ListLiteral:0:0:[', +          children = { +            { +              'Comma:0:2:,', +              children = { +                'PlainIdentifier(scope=0,ident=a):0:1:a', +                { +                  'Comma:0:5:,', +                  children = { +                    'PlainIdentifier(scope=0,ident=b):0:3: b', +                    'PlainIdentifier(scope=0,ident=c):0:6: c', +                  }, +                }, +              }, +            }, +          }, +        }, +      }, +    }, { +      hl('List', '['), +      hl('IdentifierName', 'a'), +      hl('Comma', ','), +      hl('IdentifierName', 'b', 1), +      hl('Comma', ','), +      hl('IdentifierName', 'c', 1), +      hl('List', ']'), +    }) + +    check_asgn_parsing('[a, b]', { +      --                012345 +      ast = { +        { +          'ListLiteral:0:0:[', +          children = { +            { +              'Comma:0:2:,', +              children = { +                'PlainIdentifier(scope=0,ident=a):0:1:a', +                'PlainIdentifier(scope=0,ident=b):0:3: b', +              }, +            }, +          }, +        }, +      }, +    }, { +      hl('List', '['), +      hl('IdentifierName', 'a'), +      hl('Comma', ','), +      hl('IdentifierName', 'b', 1), +      hl('List', ']'), +    }) + +    check_asgn_parsing('[a]', { +      --                012 +      ast = { +        { +          'ListLiteral:0:0:[', +          children = { +            'PlainIdentifier(scope=0,ident=a):0:1:a', +          }, +        }, +      }, +    }, { +      hl('List', '['), +      hl('IdentifierName', 'a'), +      hl('List', ']'), +    }) + +    check_asgn_parsing('[]', { +      --                01 +      ast = { +        'ListLiteral:0:0:[', +      }, +      err = { +        arg = ']', +        msg = 'E475: Unable to assign to empty list: %.*s', +      }, +    }, { +      hl('List', '['), +      hl('InvalidList', ']'), +    }) + +    check_asgn_parsing('a[1] += 3', { +      --                012345678 +      ast = { +        { +          'Assignment(Add):0:4: +=', +          children = { +            { +              'Subscript:0:1:[', +              children = { +                'PlainIdentifier(scope=0,ident=a):0:0:a', +                'Integer(val=1):0:2:1', +              }, +            }, +            'Integer(val=3):0:7: 3', +          }, +        }, +      }, +    }, { +      hl('IdentifierName', 'a'), +      hl('SubscriptBracket', '['), +      hl('Number', '1'), +      hl('SubscriptBracket', ']'), +      hl('AssignmentWithAddition', '+=', 1), +      hl('Number', '3', 1), +    }) + +    check_asgn_parsing('a[1 + 2] += 3', { +      --                0123456789012 +      --                0         1 +      ast = { +        { +          'Assignment(Add):0:8: +=', +          children = { +            { +              'Subscript:0:1:[', +              children = { +                'PlainIdentifier(scope=0,ident=a):0:0:a', +                { +                  'BinaryPlus:0:3: +', +                  children = { +                    'Integer(val=1):0:2:1', +                    'Integer(val=2):0:5: 2', +                  }, +                }, +              }, +            }, +            'Integer(val=3):0:11: 3', +          }, +        }, +      }, +    }, { +      hl('IdentifierName', 'a'), +      hl('SubscriptBracket', '['), +      hl('Number', '1'), +      hl('BinaryPlus', '+', 1), +      hl('Number', '2', 1), +      hl('SubscriptBracket', ']'), +      hl('AssignmentWithAddition', '+=', 1), +      hl('Number', '3', 1), +    }) + +    check_asgn_parsing('a[{-> {b{3}: 4}[5]}()] += 6', { +      --                012345678901234567890123456 +      --                0         1         2 +      ast = { +        { +          'Assignment(Add):0:22: +=', +          children = { +            { +              'Subscript:0:1:[', +              children = { +                'PlainIdentifier(scope=0,ident=a):0:0:a', +                { +                  'Call:0:19:(', +                  children = { +                    { +                      fmtn('Lambda', '\\di', ':0:2:{'), +                      children = { +                        { +                          'Arrow:0:3:->', +                          children = { +                            { +                              'Subscript:0:15:[', +                              children = { +                                { +                                  fmtn('DictLiteral', '-di', ':0:5: {'), +                                  children = { +                                    { +                                      'Colon:0:11::', +                                      children = { +                                        { +                                          'ComplexIdentifier:0:8:', +                                          children = { +                                            'PlainIdentifier(scope=0,ident=b):0:7:b', +                                            { +                                              fmtn('CurlyBracesIdentifier', '--i', ':0:8:{'), +                                              children = { +                                                'Integer(val=3):0:9:3', +                                              }, +                                            }, +                                          }, +                                        }, +                                        'Integer(val=4):0:12: 4', +                                      }, +                                    }, +                                  }, +                                }, +                                'Integer(val=5):0:16:5', +                              }, +                            }, +                          }, +                        }, +                      }, +                    }, +                  }, +                }, +              }, +            }, +            'Integer(val=6):0:25: 6', +          }, +        }, +      }, +    }, { +      hl('IdentifierName', 'a'), +      hl('SubscriptBracket', '['), +      hl('Lambda', '{'), +      hl('Arrow', '->'), +      hl('Dict', '{', 1), +      hl('IdentifierName', 'b'), +      hl('Curly', '{'), +      hl('Number', '3'), +      hl('Curly', '}'), +      hl('Colon', ':'), +      hl('Number', '4', 1), +      hl('Dict', '}'), +      hl('SubscriptBracket', '['), +      hl('Number', '5'), +      hl('SubscriptBracket', ']'), +      hl('Lambda', '}'), +      hl('CallingParenthesis', '('), +      hl('CallingParenthesis', ')'), +      hl('SubscriptBracket', ']'), +      hl('AssignmentWithAddition', '+=', 1), +      hl('Number', '6', 1), +    }) + +    check_asgn_parsing('a{1}.2[{-> {b{3}: 4}[5]}()]', { +      --                012345678901234567890123456 +      --                0         1         2 +      ast = { +        { +          'Subscript:0:6:[', +          children = { +            { +              'ConcatOrSubscript:0:4:.', +              children = { +                { +                  'ComplexIdentifier:0:1:', +                  children = { +                    'PlainIdentifier(scope=0,ident=a):0:0:a', +                    { +                      fmtn('CurlyBracesIdentifier', '--i', ':0:1:{'), +                      children = { +                        'Integer(val=1):0:2:1', +                      }, +                    }, +                  }, +                }, +                'PlainKey(key=2):0:5:2', +              }, +            }, +            { +              'Call:0:24:(', +              children = { +                { +                  fmtn('Lambda', '\\di', ':0:7:{'), +                  children = { +                    { +                      'Arrow:0:8:->', +                      children = { +                        { +                          'Subscript:0:20:[', +                          children = { +                            { +                              fmtn('DictLiteral', '-di', ':0:10: {'), +                              children = { +                                { +                                  'Colon:0:16::', +                                  children = { +                                    { +                                      'ComplexIdentifier:0:13:', +                                      children = { +                                        'PlainIdentifier(scope=0,ident=b):0:12:b', +                                        { +                                          fmtn('CurlyBracesIdentifier', '--i', ':0:13:{'), +                                          children = { +                                            'Integer(val=3):0:14:3', +                                          }, +                                        }, +                                      }, +                                    }, +                                    'Integer(val=4):0:17: 4', +                                  }, +                                }, +                              }, +                            }, +                            'Integer(val=5):0:21:5', +                          }, +                        }, +                      }, +                    }, +                  }, +                }, +              }, +            }, +          }, +        }, +      }, +    }, { +      hl('IdentifierName', 'a'), +      hl('Curly', '{'), +      hl('Number', '1'), +      hl('Curly', '}'), +      hl('ConcatOrSubscript', '.'), +      hl('IdentifierKey', '2'), +      hl('SubscriptBracket', '['), +      hl('Lambda', '{'), +      hl('Arrow', '->'), +      hl('Dict', '{', 1), +      hl('IdentifierName', 'b'), +      hl('Curly', '{'), +      hl('Number', '3'), +      hl('Curly', '}'), +      hl('Colon', ':'), +      hl('Number', '4', 1), +      hl('Dict', '}'), +      hl('SubscriptBracket', '['), +      hl('Number', '5'), +      hl('SubscriptBracket', ']'), +      hl('Lambda', '}'), +      hl('CallingParenthesis', '('), +      hl('CallingParenthesis', ')'), +      hl('SubscriptBracket', ']'), +    }) + +    check_asgn_parsing('a', { +      --                0 +      ast = { +        'PlainIdentifier(scope=0,ident=a):0:0:a', +      }, +    }, { +      hl('IdentifierName', 'a'), +    }) + +    check_asgn_parsing('{a}', { +      --                012 +      ast = { +        { +          fmtn('CurlyBracesIdentifier', '--i', ':0:0:{'), +          children = { +            'PlainIdentifier(scope=0,ident=a):0:1:a', +          }, +        }, +      }, +    }, { +      hl('FigureBrace', '{'), +      hl('IdentifierName', 'a'), +      hl('Curly', '}'), +    }) + +    check_asgn_parsing('{a}b', { +      --                0123 +      ast = { +        { +          'ComplexIdentifier:0:3:', +          children = { +            { +              fmtn('CurlyBracesIdentifier', '--i', ':0:0:{'), +              children = { +                'PlainIdentifier(scope=0,ident=a):0:1:a', +              }, +            }, +            'PlainIdentifier(scope=0,ident=b):0:3:b', +          }, +        }, +      }, +    }, { +      hl('FigureBrace', '{'), +      hl('IdentifierName', 'a'), +      hl('Curly', '}'), +      hl('IdentifierName', 'b'), +    }) + +    check_asgn_parsing('a{b}c', { +      --                01234 +      ast = { +        { +          'ComplexIdentifier:0:1:', +          children = { +            'PlainIdentifier(scope=0,ident=a):0:0:a', +            { +              'ComplexIdentifier:0:4:', +              children = { +                { +                  fmtn('CurlyBracesIdentifier', '--i', ':0:1:{'), +                  children = { +                    'PlainIdentifier(scope=0,ident=b):0:2:b', +                  }, +                }, +                'PlainIdentifier(scope=0,ident=c):0:4:c', +              }, +            }, +          }, +        }, +      }, +    }, { +      hl('IdentifierName', 'a'), +      hl('Curly', '{'), +      hl('IdentifierName', 'b'), +      hl('Curly', '}'), +      hl('IdentifierName', 'c'), +    }) + +    check_asgn_parsing('a{b}c[0]', { +      --                01234567 +      ast = { +        { +          'Subscript:0:5:[', +          children = { +            { +              'ComplexIdentifier:0:1:', +              children = { +                'PlainIdentifier(scope=0,ident=a):0:0:a', +                { +                  'ComplexIdentifier:0:4:', +                  children = { +                    { +                      fmtn('CurlyBracesIdentifier', '--i', ':0:1:{'), +                      children = { +                        'PlainIdentifier(scope=0,ident=b):0:2:b', +                      }, +                    }, +                    'PlainIdentifier(scope=0,ident=c):0:4:c', +                  }, +                }, +              }, +            }, +            'Integer(val=0):0:6:0', +          }, +        }, +      }, +    }, { +      hl('IdentifierName', 'a'), +      hl('Curly', '{'), +      hl('IdentifierName', 'b'), +      hl('Curly', '}'), +      hl('IdentifierName', 'c'), +      hl('SubscriptBracket', '['), +      hl('Number', '0'), +      hl('SubscriptBracket', ']'), +    }) + +    check_asgn_parsing('a{b}c.0', { +      --                0123456 +      ast = { +        { +          'ConcatOrSubscript:0:5:.', +          children = { +            { +              'ComplexIdentifier:0:1:', +              children = { +                'PlainIdentifier(scope=0,ident=a):0:0:a', +                { +                  'ComplexIdentifier:0:4:', +                  children = { +                    { +                      fmtn('CurlyBracesIdentifier', '--i', ':0:1:{'), +                      children = { +                        'PlainIdentifier(scope=0,ident=b):0:2:b', +                      }, +                    }, +                    'PlainIdentifier(scope=0,ident=c):0:4:c', +                  }, +                }, +              }, +            }, +            'PlainKey(key=0):0:6:0', +          }, +        }, +      }, +    }, { +      hl('IdentifierName', 'a'), +      hl('Curly', '{'), +      hl('IdentifierName', 'b'), +      hl('Curly', '}'), +      hl('IdentifierName', 'c'), +      hl('ConcatOrSubscript', '.'), +      hl('IdentifierKey', '0'), +    }) + +    check_asgn_parsing('[a{b}c[0].0]', { +      --                012345678901 +      --                0         1 +      ast = { +        { +          'ListLiteral:0:0:[', +          children = { +            { +              'ConcatOrSubscript:0:9:.', +              children = { +                { +                  'Subscript:0:6:[', +                  children = { +                    { +                      'ComplexIdentifier:0:2:', +                      children = { +                        'PlainIdentifier(scope=0,ident=a):0:1:a', +                        { +                          'ComplexIdentifier:0:5:', +                          children = { +                            { +                              fmtn('CurlyBracesIdentifier', '--i', ':0:2:{'), +                              children = { +                                'PlainIdentifier(scope=0,ident=b):0:3:b', +                              }, +                            }, +                            'PlainIdentifier(scope=0,ident=c):0:5:c', +                          }, +                        }, +                      }, +                    }, +                    'Integer(val=0):0:7:0', +                  }, +                }, +                'PlainKey(key=0):0:10:0', +              }, +            }, +          }, +        }, +      }, +    }, { +      hl('List', '['), +      hl('IdentifierName', 'a'), +      hl('Curly', '{'), +      hl('IdentifierName', 'b'), +      hl('Curly', '}'), +      hl('IdentifierName', 'c'), +      hl('SubscriptBracket', '['), +      hl('Number', '0'), +      hl('SubscriptBracket', ']'), +      hl('ConcatOrSubscript', '.'), +      hl('IdentifierKey', '0'), +      hl('List', ']'), +    }) + +    check_asgn_parsing('{a}{b}', { +      --                012345 +      ast = { +        { +          'ComplexIdentifier:0:3:', +          children = { +            { +              fmtn('CurlyBracesIdentifier', '--i', ':0:0:{'), +              children = { +                'PlainIdentifier(scope=0,ident=a):0:1:a', +              }, +            }, +            { +              fmtn('CurlyBracesIdentifier', '--i', ':0:3:{'), +              children = { +                'PlainIdentifier(scope=0,ident=b):0:4:b', +              }, +            }, +          }, +        }, +      }, +    }, { +      hl('FigureBrace', '{'), +      hl('IdentifierName', 'a'), +      hl('Curly', '}'), +      hl('Curly', '{'), +      hl('IdentifierName', 'b'), +      hl('Curly', '}'), +    }) + +    check_asgn_parsing('a.b{c}{d}', { +      --                012345678 +      ast = { +        { +          'OpMissing:0:3:', +          children = { +            { +              'ConcatOrSubscript:0:1:.', +              children = { +                'PlainIdentifier(scope=0,ident=a):0:0:a', +                'PlainKey(key=b):0:2:b', +              }, +            }, +            { +              'ComplexIdentifier:0:6:', +              children = { +                { +                  fmtn('CurlyBracesIdentifier', '--i', ':0:3:{'), +                  children = { +                    'PlainIdentifier(scope=0,ident=c):0:4:c', +                  }, +                }, +                { +                  fmtn('CurlyBracesIdentifier', '--i', ':0:6:{'), +                  children = { +                    'PlainIdentifier(scope=0,ident=d):0:7:d', +                  }, +                }, +              }, +            }, +          }, +        }, +      }, +      err = { +        arg = '{c}{d}', +        msg = 'E15: Missing operator: %.*s', +      }, +    }, { +      hl('IdentifierName', 'a'), +      hl('ConcatOrSubscript', '.'), +      hl('IdentifierKey', 'b'), +      hl('InvalidFigureBrace', '{'), +      hl('IdentifierName', 'c'), +      hl('Curly', '}'), +      hl('Curly', '{'), +      hl('IdentifierName', 'd'), +      hl('Curly', '}'), +    }) + +    check_asgn_parsing('[a] = 1', { +      --                0123456 +      ast = { +        { +          'Assignment(Plain):0:3: =', +          children = { +            { +              'ListLiteral:0:0:[', +              children = { +                'PlainIdentifier(scope=0,ident=a):0:1:a', +              }, +            }, +            'Integer(val=1):0:5: 1', +          }, +        }, +      }, +    }, { +      hl('List', '['), +      hl('IdentifierName', 'a'), +      hl('List', ']'), +      hl('PlainAssignment', '=', 1), +      hl('Number', '1', 1), +    }) + +    check_asgn_parsing('[a[b], [c, [d, [e]]]] = 1', { +      --                0123456789012345678901234 +      --                0         1         2 +      ast = { +        { +          'Assignment(Plain):0:21: =', +          children = { +            { +              'ListLiteral:0:0:[', +              children = { +                { +                  'Comma:0:5:,', +                  children = { +                    { +                      'Subscript:0:2:[', +                      children = { +                        'PlainIdentifier(scope=0,ident=a):0:1:a', +                        'PlainIdentifier(scope=0,ident=b):0:3:b', +                      }, +                    }, +                    { +                      'ListLiteral:0:6: [', +                      children = { +                        { +                          'Comma:0:9:,', +                          children = { +                            'PlainIdentifier(scope=0,ident=c):0:8:c', +                            { +                              'ListLiteral:0:10: [', +                              children = { +                                { +                                  'Comma:0:13:,', +                                  children = { +                                    'PlainIdentifier(scope=0,ident=d):0:12:d', +                                    { +                                      'ListLiteral:0:14: [', +                                      children = { +                                        'PlainIdentifier(scope=0,ident=e):0:16:e', +                                      }, +                                    }, +                                  }, +                                }, +                              }, +                            }, +                          }, +                        }, +                      }, +                    }, +                  }, +                }, +              }, +            }, +            'Integer(val=1):0:23: 1', +          }, +        }, +      }, +      err = { +        arg = '[c, [d, [e]]]] = 1', +        msg = 'E475: Nested lists not allowed when assigning: %.*s', +      }, +    }, { +      hl('List', '['), +      hl('IdentifierName', 'a'), +      hl('SubscriptBracket', '['), +      hl('IdentifierName', 'b'), +      hl('SubscriptBracket', ']'), +      hl('Comma', ','), +      hl('InvalidList', '[', 1), +      hl('IdentifierName', 'c'), +      hl('Comma', ','), +      hl('InvalidList', '[', 1), +      hl('IdentifierName', 'd'), +      hl('Comma', ','), +      hl('InvalidList', '[', 1), +      hl('IdentifierName', 'e'), +      hl('List', ']'), +      hl('List', ']'), +      hl('List', ']'), +      hl('List', ']'), +      hl('PlainAssignment', '=', 1), +      hl('Number', '1', 1), +    }) + +    check_asgn_parsing('$X += 1', { +      --                0123456 +      ast = { +        { +          'Assignment(Add):0:2: +=', +          children = { +            'Environment(ident=X):0:0:$X', +            'Integer(val=1):0:5: 1', +          }, +        }, +      }, +    }, { +      hl('EnvironmentSigil', '$'), +      hl('EnvironmentName', 'X'), +      hl('AssignmentWithAddition', '+=', 1), +      hl('Number', '1', 1), +    }) + +    check_asgn_parsing('@a .= 1', { +      --                0123456 +      ast = { +        { +          'Assignment(Concat):0:2: .=', +          children = { +            'Register(name=a):0:0:@a', +            'Integer(val=1):0:5: 1', +          }, +        }, +      }, +    }, { +      hl('Register', '@a'), +      hl('AssignmentWithConcatenation', '.=', 1), +      hl('Number', '1', 1), +    }) + +    check_asgn_parsing('&option -= 1', { +      --                012345678901 +      --                0         1 +      ast = { +        { +          'Assignment(Subtract):0:7: -=', +          children = { +            'Option(scope=0,ident=option):0:0:&option', +            'Integer(val=1):0:10: 1', +          }, +        }, +      }, +    }, { +      hl('OptionSigil', '&'), +      hl('OptionName', 'option'), +      hl('AssignmentWithSubtraction', '-=', 1), +      hl('Number', '1', 1), +    }) + +    check_asgn_parsing('[$X, @a, &l:option] = [1, 2, 3]', { +      --                0123456789012345678901234567890 +      --                0         1         2         3 +      ast = { +        { +          'Assignment(Plain):0:19: =', +          children = { +            { +              'ListLiteral:0:0:[', +              children = { +                { +                  'Comma:0:3:,', +                  children = { +                    'Environment(ident=X):0:1:$X', +                    { +                      'Comma:0:7:,', +                      children = { +                        'Register(name=a):0:4: @a', +                        'Option(scope=l,ident=option):0:8: &l:option', +                      }, +                    }, +                  }, +                }, +              }, +            }, +            { +              'ListLiteral:0:21: [', +              children = { +                { +                  'Comma:0:24:,', +                  children = { +                    'Integer(val=1):0:23:1', +                    { +                      'Comma:0:27:,', +                      children = { +                        'Integer(val=2):0:25: 2', +                        'Integer(val=3):0:28: 3', +                      }, +                    }, +                  }, +                }, +              }, +            }, +          }, +        }, +      }, +    }, { +      hl('List', '['), +      hl('EnvironmentSigil', '$'), +      hl('EnvironmentName', 'X'), +      hl('Comma', ','), +      hl('Register', '@a', 1), +      hl('Comma', ','), +      hl('OptionSigil', '&', 1), +      hl('OptionScope', 'l'), +      hl('OptionScopeDelimiter', ':'), +      hl('OptionName', 'option'), +      hl('List', ']'), +      hl('PlainAssignment', '=', 1), +      hl('List', '[', 1), +      hl('Number', '1'), +      hl('Comma', ','), +      hl('Number', '2', 1), +      hl('Comma', ','), +      hl('Number', '3', 1), +      hl('List', ']'), +    }) +  end) +end diff --git a/test/unit/viml/helpers.lua b/test/unit/viml/helpers.lua new file mode 100644 index 0000000000..9d8102e023 --- /dev/null +++ b/test/unit/viml/helpers.lua @@ -0,0 +1,130 @@ +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 + +local expr_asgn_type_tab +make_enum_conv_tab(lib, { +  'kExprAsgnPlain', +  'kExprAsgnAdd', +  'kExprAsgnSubtract', +  'kExprAsgnConcat', +}, 'kExprAsgn', function(ret) expr_asgn_type_tab = ret end) + +local function conv_expr_asgn_type(expr_asgn_type) +  return conv_enum(expr_asgn_type_tab, expr_asgn_type) +end + +return { +  conv_ccs = conv_ccs, +  pline2lua = pline2lua, +  pstate_str = pstate_str, +  new_pstate = new_pstate, +  conv_cmp_type = conv_cmp_type, +  pstate_set_str = pstate_set_str, +  conv_expr_asgn_type = conv_expr_asgn_type, +}  | 
