Как передать Regexp.last_match в блок в Ruby - PullRequest
0 голосов
/ 17 сентября 2018

Есть ли способ передать последнее совпадение (практически Regexp.last_match) в блок (итератор) в Ruby?

Вот пример метода как своего рода оболочкиSrring#sub, чтобы продемонстрировать проблему.Он принимает как стандартные аргументы, так и блок:

def newsub(str, *rest, &bloc)
  str.sub(*rest, &bloc)
end

Он работает в стандартном случае только для аргументов и может принимать блок;однако позиционные специальные переменные, такие как $ 1, $ 2 и т. д., не могут использоваться внутри блока.Вот несколько примеров:

newsub("abcd", /ab(c)/, '\1')        # => "cd"
newsub("abcd", /ab(c)/){|m| $1}      # => "d"  ($1 == nil)
newsub("abcd", /ab(c)/){$1.upcase}   # => NoMethodError

Причина, по которой блок работает не так, как String#sub(/..(.)/){$1}, заключается в том, что я предполагаю, что что-то связано с областью действия;специальные переменные $ 1, $ 2 и т. д. являются локальными переменными (как и Regexp.last_match).

Есть ли способ решить эту проблему?Я хотел бы, чтобы метод newsub работал так же, как String#sub, в том смысле, что $ 1, $ 2 и т. Д. Можно использовать в поставляемом блоке.

РЕДАКТИРОВАТЬ: Согласно некоторые предыдущие ответы , не может быть способа достичь этого ...

1 Ответ

0 голосов
/ 18 сентября 2018

Вот способ согласно вопросу (Ruby 2).Это не красиво, и не совсем на 100% идеально во всех аспектах, но делает работу.

def newsub(str, *rest, &bloc)
  str =~ rest[0]  # => ArgumentError if rest[0].nil?
  bloc.binding.tap do |b|
    b.local_variable_set(:_, $~)
    b.eval("$~=_")
  end if bloc
  str.sub(*rest, &bloc)
end

При этом, результат будет следующим:

_ = (/(xyz)/ =~ 'xyz')
p $1  # => "xyz"
p _   # => 0

p newsub("abcd", /ab(c)/, '\1')        # => "cd"
p $1  # => "xyz"
p _   # => 0

p newsub("abcd", /ab(c)/){|m| $1}      # => "cd"
p $1  # => "c"
p _                 # => #<MatchData "abc" 1:"c">

v, _ = $1, newsub("efg", /ef(g)/){$1.upcase}
p [v, _]  # => ["c", "G"]
p $1  # => "g"
p Regexp.last_match # => #<MatchData "efg" 1:"g">

In-анализ глубины

В определенном выше методе newsub, когда задан блок, локальные переменные $ 1 и т. д. в потоке вызывающей стороны (пере) устанавливаются после выполнения блока, что согласуется с String#sub.Однако, когда блок не задан, локальные переменные $ 1 и т. Д. не сбрасываются, тогда как в String#sub, $ 1 и т. Д. Всегда сбрасываются независимо от того, задан блок или нет.

Кроме того, локальная переменная вызывающего _ сбрасывается в этом алгоритме.В соглашении Руби локальная переменная _ используется в качестве фиктивной переменной, и ее значение не следует читать или ссылаться.Поэтому это не должно вызывать каких-либо практических проблем.Если бы оператор local_variable_set(:$~, $~) был действительным, временные локальные переменные не понадобились бы.Тем не менее, это не так, в Ruby (по крайней мере, начиная с версии 2.5.1).См. Комментарий (на японском языке) Казухиро НИШИЯМА в [ruby-list: 50708] .

Общий фон (спецификация Руби), объясненный

Вот простой примервыделите спецификацию Ruby, относящуюся к этой проблеме:

s = "abcd"
/b(c)/ =~ s
p $1     # => "c"
1.times do |i|
  p s    # => "abcd"
  p $1   # => "c"
end

Специальные переменные $&, $1, $2 и т. д. (связанные, $~ (Regexp.last_match), $'и так) работают в местном масштабе.В Ruby локальная область наследует переменные с одинаковыми именами в родительской области.В приведенном выше примере переменная s является унаследованной , как и $1.Блок do имеет выход -ed на 1.times, а метод 1.times не контролирует переменные внутри блока, за исключением параметров блока (i в примере выше; nb , хотя Integer#times не предоставляет никаких параметров блока, попытка получить один (ие) в блоке будет игнорироваться).

Это означает, что метод дает -sa блок не контролирует $1, $2 и т. Д. В блоке, которые являются локальными переменными (даже если они могут выглядеть как глобальные переменные).

Case of String # sub

Теперь давайте проанализируем, как String#sub с блоком работает:

'abc'.sub(/.(.)./){ |m| $1 }

Здесь метод sub сначала выполняет совпадение с регулярным выражением, и, следовательно, локальные переменные, такие как $1автоматически устанавливаются.Затем они (переменные типа $1) наследуются в блоке , поскольку этот блок находится в той же области, что и метод "sub" .Они не передаются из sub в блок и отличаются от параметра блока m (который соответствует строке или эквивалентен $&).

Для этогопричина, если метод sub определен в области действия , отличной от блока, метод sub не контролирует локальные переменные внутри блока, включая $1. другая область действия означает случай, когда метод sub написан и определен с помощью кода Ruby, или на практике все методы Ruby, кроме некоторых, написаны не на Ruby, а на том же языке, что и использованный.написать интерпретатор Ruby.

Официальный документ Ruby's (версия 2.5.1) объясняется в разделе String#sub:

В блочной форметекущая строка совпадения передается как параметр, и переменные, такие как $ 1, $ 2, $ `, $ & и $ ', будут установлены соответствующим образом.

Правильно.На практике методы, которые могут устанавливать и устанавливают специальные переменные, связанные с регулярным выражением, такие как $ 1, $ 2 и т. Д., Ограничены некоторыми встроенными методами, включая Regexp#match, Regexp#=~, Regexp#===, String#=~, String#sub, String#gsub, String#scan, Enumerable#all? и Enumerable#grep.
Совет 1: String#split, кажется, сбрасывает $~ ноль всегда.
Совет 2: Regexp#match? иString#match? не обновлять $~ и, следовательно, гораздо быстрее.

Вот небольшой фрагмент кода, чтобы подчеркнуть, как работает область:

def sample(str, *rest, &bloc)
  str.sub(*rest, &bloc)
  $1    # non-nil if matches
end

sample('abc', /(c)/){}  # => "c"
p $1    # => nil

Здесь $1 в методе sample () устанавливается в str.sub в той же области видимости.Это означает, что метод sample() не сможет (просто) сослаться на $1 в переданном ему блоке.

Я отмечу утверждение в разделе Регулярного выражения официального документа Ruby (Ver.2.5.1)

Используя оператор =~ со строкой и регулярным выражением, глобальная переменная $~ устанавливается после успешного совпадения.

вводит в заблуждение, потому что

  1. $~ является предопределенной локальной областью действия ( не глобальная переменная) и
  2. $~ устанавливается (возможно, ноль) независимо от того, успешна ли последняя попытка совпадения.

Тот факт, что переменные типа $~ и $1 не являются глобальными переменными, могутбыть немного запутанным.Но эй, это полезные записи, не так ли?

...