Vim регулярное выражение, чтобы разделить строку, но сохранить разделители - PullRequest
1 голос
/ 16 января 2020

Насколько я понимаю, шаблон ниже должен работать (ожидается ['bar', 'FOO', 'bar']), но найден только первый вариант (совпадения нулевой ширины после FOO, но не раньше).

echo split('barFOObar', '\v(FOO\zs|\zeFOO)')  " --> ['barFOO', 'bar']

Netiher Могу ли я решить эту проблему с помощью lookahead / lookbehind.

echo split('barFOObar', '\v((FOO)\@<=|(FOO)\@=)')  " --> ['bar', 'bar']

Сравните это, например, с Python:

echo py3eval("re.split('(?=FOO)|(?<=FOO)', 'barFOObar')") " --> ['bar', 'FOO', 'bar']

(Примечание: в Python, в скобках '(FOO)' также подойдет для этого.)

Почему приведенные выше примеры в регулярном выражении Vim не работают так, как я думал? (А также, есть ли более или менее простой способ сделать это в чистом Vimscript?)

Ответы [ 2 ]

2 голосов
/ 16 января 2020

Кажется, что нет способа достичь sh такого прямого результата, используя один split(). Фактически, документы для split() упоминают эту конкретную ситуацию сохранения разделителя с:

Если вы хотите сохранить разделитель, вы также можете использовать \zs в конце шаблона:

:echo split('abc:def:ghi', ':\zs')
['abc:', 'def:', 'ghi']

Сказав это, использование как взглядов вперед, так и взглядов назад действительно действительно работает. В вашем примере у вас есть синтаксическая ошибка. Так как вы используете режим verymagi c, вам не следует экранировать @, поскольку он уже особенный. (Спасибо @ user938271 за указание на это!)

Это работает:

:echo split('barFOObar', '\v((FOO)@<=|(FOO)@=)')
" --> ['bar', 'FOO', 'bar']

Относительно использования маркеров для \zs и \ze:

:echo split('barFOObar', '\v(FOO\zs|\zeFOO)')
" --> ['barFOO', 'bar']

Итак, первая проблема, с которой вы столкнулись, состоит в том, что оба выражения на каждой стороне | соответствуют одному и тому же тексту "FOO", поэтому, поскольку они идентичны, первые выигрыши и вы получите это на левой стороне.

Измените порядок, и вы получите его на правой стороне:

:echo split('barFOObar', '\v(\zeFOO|FOO\zs)')
" --> ['bar', 'FOObar']

Теперь возникает вопрос, почему второй токен "FOObar" не разделяется, так как он повторное сопоставление (случай подсвечивания разделяет этот, верно?)

Ну, ответ в том, что он на самом деле снова разделяется, но он совпадает в первом случае \zeFOO еще раз и производит разделение с пустая строка. Вы можете увидеть это, передав ключевой аргумент:

:echo split('barFOObar', '\v(\zeFOO|FOO\zs)', 1)
" --> ['bar', '', 'FOObar']

Один вопрос, который до сих пор остается без ответа, заключается в том, почему lookahead / lookbehind работает , а \zs и \ze нет. Я думаю, что я как-то учел это в этом ответе , чтобы использовать регулярное выражение в группах синтаксиса.

Это не будет работать, потому что Vim не будет сканировать один и тот же текст дважды, пытаясь сопоставить другое регулярное выражение.

Несмотря на то, что в \zs полученное совпадение включает в себя только bar, Vim необходимо использовать FOO, чтобы иметь возможность сопоставить это регулярное выражение, и не будет, если оно уже сопоставило его с другая половина шаблона.

Вид сзади с \@<= отличается. Причина, по которой он работает, заключается в том, что Vim сначала ищет bar (или любой другой текст, который он рассматривает), а затем оглядывается назад, чтобы увидеть, совпадает ли FOO. Таким образом, шаблон привязывается к bar, а не FOO и не страдает от проблемы с попыткой начать сопоставление в области, которая уже соответствует другому выражению.

Вы можете легко визуализировать эту разницу, выполняя поиск с Vim. Попробуйте это:

/\v(\zeFOO|FOO\zs)

и сравните его с этим:

/\v((FOO)@<=|(FOO)@=)

Вы заметите, что последний будет соответствовать как до , так и после FOO, в то время как первый не будет.


Сравните это, например, с Python [re.split] ... в Python, '(FOO)' в скобках также будет работать для this.

Двигатели регулярных выражений Vim и Python - разные звери ...

Многие ограничения в движке Vim связаны с его наследством от vi. Одним конкретным ограничением являются группы захвата, где вы ограничены 9 из них, и нет никакого способа обойти это.

Учитывая это ограничение, вы обнаружите, что группы захвата обычно используются реже (и, когда используются они менее мощные), чем в Python.

Один из вариантов, который следует учитывать, - это использовать Python в Vim вместо Vimscript. Хотя обычно это влияет на переносимость, поэтому лично я бы не стал переключаться только на эту функцию.


Есть ли более или менее простой способ сделать это в чистом Vimscript, тогда?

Один из вариантов - переопределить версию split(), которая сохраняет разделители, используя matchstrpos(). Например:

function! SplitDelim(expr, pat)
    let result = []
    let expr = a:expr
    while 1
        let [w, s, e] = matchstrpos(expr, a:pat)
        if s == -1
            break
        endif
        call add(result, s ? expr[:s-1] : '')
        call add(result, w)
        let expr = expr[e:]
    endwhile
    call add(result, expr)
    return result
endfunction
1 голос
/ 16 января 2020

Вы можете сначала заменить FOO на -FOO-, а затем разбить строку. Например:

:echo split(substitute('barFOObarFOObaz', 'FOO','-&-','g'),'-')
['bar', 'FOO', 'bar', 'FOO', 'baz']
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...