Приключения с открытым исходным кодом: Эпизод 79: Исследование Crystal Regular Expression API

В предыдущем эпизоде мы рассмотрели Ruby Regular Expression API. Я хочу попробовать еще несколько языков, и наиболее очевидным для начала является Crystal.

Многие решения работают точно так же, как и в Ruby, но некоторые различия интересны.

Тестовый пример

В Crystal нет %W, которая является одной из моих любимых функций Ruby, но в данном случае подойдет ее неинтерполирующий и гораздо менее потрясающий родственник %w.

Вот тестовый пример:

%w[
  2015-05-25
  2016/06/26
  27/07/2017
].each do |s|
  p parse_date(s)
end
Войти в полноэкранный режим Выйти из полноэкранного режима

И ожидаемый результат:

[2015, 5, 25]
[2016, 6, 26]
[2017, 7, 27]
Вход в полноэкранный режим Выход из полноэкранного режима

Решение 1

def parse_date(s)
  case s
  when %r[(dddd)-(dd)-(dd)]
    [$1.to_i, $2.to_i, $3.to_i]
  when %r[(dddd)/(dd)/(dd)]
    [$1.to_i, $2.to_i, $3.to_i]
  when %r[(dd)/(dd)/(dddd)]
    [$3.to_i, $2.to_i, $1.to_i]
  end
end
Войти в полноэкранный режим Выйти из полноэкранного режима

Самое простое решение работает точно так же, как и в Ruby, без каких-либо изменений.

Решение 2

#!/usr/bin/env crystal

def parse_date(s)
  case s
  when %r[(dddd)-(dd)-(dd)], %r[(dddd)/(dd)/(dd)]
    [$1.to_i, $2.to_i, $3.to_i]
  when %r[(dd)/(dd)/(dddd)]
    [$3.to_i, $2.to_i, $1.to_i]
  end
end
Войти в полноэкранный режим Выйти из полноэкранного режима

Группировка опций when работает так же, как и в Ruby.

Решение 3

def parse_date(s)
  case s
  when %r[(dddd)([/-])(dd)2(dd)]
    [$1.to_i, $3.to_i, $4.to_i]
  when %r[(dd)/(dd)/(dddd)]
    [$3.to_i, $2.to_i, $1.to_i]
  end
end
Войти в полноэкранный режим Выйти из полноэкранного режима

Обратные ссылки также работают как в Ruby.

Решение 4

Теперь это не работает:

def parse_date(s)
  case s
  when %r[(dddd)-(dd)-(dd)|(dddd)/(dd)/(dd)]
    [($1 || $4).to_i, ($2 || $5).to_i, ($3 || $6).to_i]
  when %r[(dd)/(dd)/(dddd)]
    [$3.to_i, $2.to_i, $1.to_i]
  end
end
Войти в полноэкранный режим Выйти из полноэкранного режима

Причина в том, что в Ruby $1 может быть либо String, либо nil. В Crystal $1 является String, поэтому если он не совпал, то обращение к нему будет ошибкой.

В Crystal также есть эквиваленты nil $1?, $2? и т.д. Обратите внимание, что для того, чтобы все выражение не было nilable, мы не ставим ? на последнем:

def parse_date(s)
  case s
  when %r[(dddd)-(dd)-(dd)|(dddd)/(dd)/(dd)]
    [($1? || $4).to_i, ($2? || $5).to_i, ($3? || $6).to_i]
  when %r[(dd)/(dd)/(dddd)]
    [$3.to_i, $2.to_i, $1.to_i]
  end
end
Войти в полноэкранный режим Выйти из полноэкранного режима

Решение 5

def parse_date(s)
  case s
  when %r[(dddd)-(dd)-(dd)|(dddd)/(dd)/(dd)|(dd)/(dd)/(dddd)]
    [($1? || $4? || $9).to_i, ($2? || $5? || $8).to_i, ($3? || $6? || $7).to_i]
  end
end
Войти в полноэкранный режим Выйти из полноэкранного режима

Зная то, что мы знаем, мы можем использовать тот же трюк, переписав ($1 || $4 || $9) в ($1? || $4? || $9) и так далее.

Решение 6

def parse_date(s)
  case s
  when
    %r[(?<year>dddd)-(?<month>dd)-(?<day>dd)],
    %r[(?<year>dddd)/(?<month>dd)/(?<day>dd)],
    %r[(?<day>dd)/(?<month>dd)/(?<year>dddd)]
    [$~["year"].to_i, $~["month"].to_i, $~["day"].to_i]
  end
end
Войдите в полноэкранный режим Выход из полноэкранного режима

Использование именованных захватов работает так же, как и в версии для Ruby.

Решение 7

def parse_date(s)
  case s
  when %r[(?<year>dddd)-(?<month>dd)-(?<day>dd)|(?<year>dddd)/(?<month>dd)/(?<day>dd)|(?<day>dd)/(?<month>dd)/(?<year>dddd)]
    [$~["year"].to_i, $~["month"].to_i, $~["day"].to_i]
  end
end
Войти в полноэкранный режим Выйти из полноэкранного режима

Наличие групп захватов с одинаковыми именами работает так же, как и в Ruby, без изменений.

Решение 8

def parse_date(s)
  case s
  when %r[
      (?<year>dddd)-(?<month>dd)-(?<day>dd) |
      (?<year>dddd)/(?<month>dd)/(?<day>dd) |
      (?<day>dd)/(?<month>dd)/(?<year>dddd)
    ]x
    [$~["year"].to_i, $~["month"].to_i, $~["day"].to_i]
  end
end
Войти в полноэкранный режим Выйти из полноэкранного режима

Так же как и флаг //x — все просто работает.

Решение 9

def parse_date(s)
  if %r[
      (?<year>dddd)-(?<month>dd)-(?<day>dd) |
      (?<year>dddd)/(?<month>dd)/(?<day>dd) |
      (?<day>dd)/(?<month>dd)/(?<year>dddd)
    ]x =~ s
    [year.to_i, month.to_i, day.to_i]
  end
end
Войти в полноэкранный режим Выйти из полноэкранного режима

С другой стороны, это совершенно не поддерживается — единственным побочным эффектом совпадения регулярных выражений является переопределение переменной $~ ($1 — это просто псевдоним для $[1]? и т.д.). Соответствие регулярному выражению не может переопределять другие локальные переменные.

Мне не очень нравится эта особенность Ruby, поэтому неудивительно, что она не нашла своего применения здесь.

История на данный момент

Все работало без изменений или с минимальными изменениями. Это мой обычный опыт работы с Crystal. В большинстве случаев все просто работает.

Весь код находится на GitHub.

Следующий эпизод

В следующем эпизоде мы посмотрим, как другие языки справляются с этой проблемой.

Оцените статью
devanswers.ru
Добавить комментарий