Многосимвольный символ мешает символу конца строки в регулярном выражении?

С помощью этого регулярного выражения:

regex1 = /\z/

соответствуют следующие строки:

"hello" =~ regex1 # => 5
"こんにちは" =~ regex1 # => 5

но с этими регулярными выражениями:

regex2 = /#$/?\z/
regex3 = /\n?\z/

они показывают разницу:

"hello" =~ regex2 # => 5
"hello" =~ regex3 # => 5
"こんにちは" =~ regex2 # => nil
"こんにちは" =~ regex3 # => nil

Что мешает? Строковая кодировка - UTF-8, а ОС - Linux (т.е. $/ - "\n"). Являются ли мультибайтные символы мешающими $/? Как?

Ответы

Ответ 1

В Ruby trunk проблема теперь была принята как ошибка. Надеюсь, это будет исправлено.

Обновление: в Ruby trunk размещены два патча.

Ответ 2

Проблема, о которой вы сообщили, определенно является ошибкой Regexp of RUBY_VERSION #=> "2.0.0", но уже существующей в предыдущих версиях 1.9, когда кодировка разрешает многобайтовые символы, такие как __ENCODING__ #=> #<Encoding:UTF-8>

Не зависит от Linux, возможно воспроизвести одно и то же поведение в OSX и Windows.

В то время как ошибка 8210 будет исправлена, мы можем помочь, изолируя и понимая случаи, в которых возникает проблема. Это также может быть полезно для любого способа обхода, если применимо к конкретным случаям.

Я понимаю, что проблема возникает, когда:

  • поиск чего-то до конца строки \z.
  • и последний символ строки многобайтовый.
  • и перед поиском используется ноль или один шаблон ?
  • но число ноль или один char искалось меньше, чем число байтов последнего символа.

Ошибка может быть вызвана недоразумениями между количеством байтов и числом символов, которое фактически проверяется механизмом регулярных выражений.

Несколько примеров могут помочь:

ИСПЫТАНИЕ 1: где последний символ: "は" - 3 байта:

s = "んにちは"

тестирование нуля или одного из [3 байта] до конца строки:

s =~ /ん?\z/u   #=> 4"       # OK it works 3 == 3

, когда мы пытаемся использовать ç [2 байта]

s =~ /ç?\z/u   #=> nil       # KO: BUG when 3 > 2
s =~ /x?ç?\z/u #=> 4         # OK it works 3 == ( 1+2 )

при тестировании нуля или одного из \n [1 байт]

s =~ /\n?\z/u #=> nil"      # KO: BUG when 3 > 1
s =~ /\n?\n?\z/u #=> nil"   # KO: BUG when 3 > 2
s =~ /\n?\n?\n?\z/u #=> 4"  # OK it works 3 == ( 1+1+1)

По результатам TEST1 мы можем утверждать: если последний многобайтовый символ строки равен 3 байтам, то тест "zero или one before" работает только тогда, когда мы тестируем как минимум 3 байта (не 3 символа) до.

ТЕСТ 2: где последний символ "ç" равен 2 байтам

s = "in French there is the ç" 

проверить нуль или один из ん [3 байта] "

s =~ /ん?\z/u #=> 24        # OK 2 <= 3

проверить нуль или один из é [2 байта]

s =~ /é?\z/u #=> 24         # OK 2 == 2
s =~ /x?é?\z/u #=> 24       # OK 2 < (2+1)

тест для нуля или один из \n [1 байт]

s =~ /\n?\z/u    #=> nil    # KO 2 > 1  ( the BUG occurs )
s =~ /\n?\n?\z/u #=> 24     # OK 2 == (1+1)
s =~ /\n?\n?\n?\z/u #=> 24  # OK 2 < (1+1+1)

По результатам TEST2 мы можем утверждать: если последний многобайтовый символ строки равен 2 байтам, то тест "нуль или один до" работает только при проверке не менее 2 байтов (не 2 символа) до.

Если многобайтовый символ не находится в конце строки, я нашел, что он работает правильно.

public gist с моим тестовым кодом, доступным здесь