2009-06-12 1 views
8

Я побежал Perl :: Критик на одном из моих сценариев, и получил это сообщение:Есть ли лучший способ писать регулярные выражения Perl с/x, поэтому код все еще легко читается?

Regular expression without "/x" flag at line 21, column 26. See page 236 of PBP. 

я посмотрел информацию политики here, и я понимаю, что писать регулярные выражения в расширенном режиме поможет всем, кто ищет в коде.

Тем не менее, я зациклился на том, как конвертировать мой код, чтобы использовать флаг/x.

CPAN Пример:

# Match a single-quoted string efficiently... 

m{'[^\\']*(?:\\.[^\\']*)*'}; #Huh? 

# Same thing with extended format... 

m{ 
    '   # an opening single quote 
    [^\\']  # any non-special chars (i.e. not backslash or single quote) 
    (?:   # then all of... 
     \\ . # any explicitly backslashed char 
     [^\\']* # followed by an non-special chars 
    )*   # ...repeated zero or more times 
    '   # a closing single quote 
}x; 

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

Мой код:

if ($line =~ /^\s*package\s+(\S+);/) { 

Я точно не знаю, как использовать расширенный регулярное выражение внутри, если заявление. Я могу написать вот так:

if (
     $line =~/
     ^\s* # starting with zero or more spaces 
     package 
     \s+  # at least one space 
     (\S+) # capture any non-space characters 
     ;  # ending in a semi-colon 
     /x 
    ) 
    { 

И это работает, но я думаю, что это почти труднее читать, чем оригинал. Есть ли лучший способ (или лучший способ) написать это? Я думаю, я мог бы создать переменную с помощью qr //.

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

Я знаю Perl :: Критик - всего лишь ориентир, но было бы неплохо следовать за ним.

Заранее благодарен!

EDIT: Таким образом, после получения несколько ответов, то мне стало ясно, что создание регулярных выражений многострочного с комментариями не всегда необходимо. Люди, которые понимают основное регулярное выражение, должны уметь понимать, что делал мой пример, - комментарии, которые я добавил, были немного лишними и подробными. Мне нравится идея использования расширенного флага регулярных выражений, но все же вложение пространств в регулярное выражение, чтобы сделать каждую часть регулярного выражения немного более ясной. Спасибо за все входные данные!

ответ

11

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

if ($line =~ m{ 
     \A \s* 
     package 
     \s+ 
     (\S+) 
     \s* ; 
    }x 
) { 

ИМХО, следующая версия прекрасно:

if ($line =~ m{ \A \s* package \s+ (\S+) \s* ; }x ) { 

в условия получения выгоды от m//x.

Замечания совершенно не нужны в этом случае, потому что вы не делаете ничего сложного. Я добавил \s* перед точкой с запятой, потому что иногда люди устанавливают полуколону отдельно от имени пакета и не должны сбрасывать ваш матч.

+0

У меня было перейдите на страницу http://www.perl.com/doc/manual/html/pod/perlre.html, чтобы узнать, что означало «\ A». Это предпочтительный способ вместо «^»? – BrianH

+2

Я предполагаю, что раньше я не думал добавлять пробелы в однострочном регулярном выражении. Я всегда думаю о флаге «/ x» как о многострочном флаге, но мне очень нравится ваш пример выше. – BrianH

+2

@BrianH: нет, не совсем. Это только имеет значение, если вы используете/m, а когда вы используете/m, вы обычно хотите ^, а не \ A. $, с другой стороны, часто используется там, где люди действительно имели в виду \ z. – ysth

8

Это в значительной степени ваш призыв к добавленной стоимости дополнительной информацией.

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

Фактически, это «вызов» относительно дополнительной информации о дополнительной информации может быть довольно сложным.

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

Редактировать: В некотором роде пример CPAN на самом деле не так полезен. При использовании флага x для добавления комментариев для описания сложного regexp я склонен описывать компоненты, которые регулярное выражение пытается сопоставить, а не просто описывать сами «биты» регулярного выражения. Например, я бы написать такие вещи, как:

  • первого компонента (область и район) в почтовый индекс Великобритании или
  • международный код города для Великобритании, или
  • любой Великобритании номер мобильного телефона ,

, который говорит мне больше, чем

  • одну или две буквы, а затем номер, необязательно с последующим письмом или
  • два четыре цифры вместе, или
  • ноль, а затем на четыре десятичные цифры, тире и шесть десятичных цифр.

Мое чувство было бы оставить комментарии регулярного выражения в этом случае. Ваше чувство кишки правильное!

+1

Очень хорошее редактирование об описании регулярного выражения. Я попадаю в ловушку описания того, что делает регулярное выражение (например, «захватывать любые непространственные символы»), когда может быть что-то вроде «захватить имя пакета». Я бы добавил +1 ваш пост, если бы мог! – BrianH

+0

Спасибо @BrianH. Очень больно найти код, который снабжен комментариями, такими как «# добавить 1 к i» над линией C «I ++»; ;-) –

1

Похоже, что это скорее вопрос о том, как последовательно отступать многострочное условие, при котором существует много ответов. Что действительно важно, так это последовательность. Если вы используете perltidy или какой-либо другой форматтер, согласитесь с тем, что он придумал (с вашей конфигурацией). Тем не менее, я бы отложил содержимое регулярного выражения на один уровень от разделителей.

Ваше сообщение показывает один серьезный недостаток в запуске существующего кода с помощью чего-то вроде Perl :: Critic - you Пример CPAN не указан * из исходного регулярного выражения. Если вы делаете много «очистки», вы можете ожидать появления ошибок, поэтому я надеюсь, что у вас есть хороший набор тестов.

+0

Где я не упоминал «*»? У меня есть небольшой набор тестов для этого скрипта, да. Сценарий - это просто поиск моей системы для установленных модулей Perl, поэтому она не слишком критична, если она ломается - но дело касается очистки существующего кода. – BrianH

+0

О, вы говорили о примере CPAN с отсутствующим «*». Я взял это прямо из http://search.cpan.org/~elliotjs/Perl-Critic-1.098/lib/Perl/Critic/Policy/RegularExpressions/RequireExtendedFormatting.pm - это не мой код. Но это иллюстрирует вашу мысль. – BrianH

+0

@BrianH: спасибо, исправлено – ysth

11

Никогда не пишите комментарий, в котором говорится, что говорит код. Комментарии должны сообщать вам, почему код говорит, что он говорит. Посмотрите на это уродство, без комментариев это очень трудно понять, что происходит, но комментарии дают понять , что пытается быть согласованы:

require 5.010; 
my $sep   = qr{ [/.-] }x;    #allowed separators  
my $any_century = qr/ 1[6-9] | [2-9][0-9] /x; #match the century 
my $any_decade = qr/ [0-9]{2} /x;   #match any decade or 2 digit year 
my $any_year = qr/ $any_century? $any_decade /x; #match a 2 or 4 digit year 

#match the 1st through 28th for any month of any year 
my $start_of_month = qr/ 
    (?:       #match 
     0?[1-9] |    #Jan - Sep or 
     1[0-2]     #Oct - Dec 
    ) 
    ($sep)      #the separator 
    (?: 
     0?[1-9] |    # 1st - 9th or 
     1[0-9] |    #10th - 19th or 
     2[0-8]     #20th - 28th 
    ) 
    \g{-1}      #and the separator again 
/x; 

#match 28th - 31st for any month but Feb for any year 
my $end_of_month = qr/ 
    (?: 
     (?: 0?[13578] | 1[02]) #match Jan, Mar, May, Jul, Aug, Oct, Dec 
     ($sep)     #the separator 
     31      #the 31st 
     \g{-1}     #and the separator again 
     |      #or 
     (?: 0?[13-9] | 1[0-2]) #match all months but Feb 
     ($sep)     #the separator 
     (?:29|30)    #the 29th or the 30th 
     \g{-1}     #and the separator again 
    ) 
/x; 

#match any non-leap year date and the first part of Feb in leap years 
my $non_leap_year = qr/ (?: $start_of_month | $end_of_month) $any_year/x; 

#match 29th of Feb in leap years 
#BUG: 00 is treated as a non leap year 
#even though 2000, 2400, etc are leap years 
my $feb_in_leap = qr/ 
    0?2       #match Feb 
    ($sep)      #the separtor 
    29       #the 29th 
    \g{-1}      #the separator again 
    (?: 
     $any_century?   #any century 
     (?:      #and decades divisible by 4 but not 100 
      0[48]  | 
      [2468][048] | 
      [13579][26] 
     ) 
     | 
     (?:      #or match centuries that are divisible by 4 
      16   | 
      [2468][048] | 
      [3579][26] 
     ) 
     00      
    ) 
/x; 

my $any_date = qr/$non_leap_year|$feb_in_leap/; 
my $only_date = qr/^$any_date$/; 
6

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

Я переработал регулярное выражение, подтверждающее дату выдачи Час Оуэнс, в новую декларативную форму, доступную в Perl-5.10, которая имеет множество преимуществ.

  • токенов в регулярном выражении могут быть использованы повторно
  • Любой печать регулярного выражения позже будет еще увидеть всю логику дерева.

Это может быть не всякий чайник с рыбой, но для чрезвычайно сложных вещей, таких как дата проверки, это может быть удобно (ps: в реальном мире, пожалуйста, используйте модуль для материала даты, не делайте DIY, это это всего лишь пример, чтобы узнать из)

#!/usr/bin/perl 
use strict; 
use warnings; 
require 5.010; 

#match the 1st through 28th for any month of any year 
my $date_syntax = qr{ 
    (?(DEFINE) 
     (?<century> 
      (1[6-9] | [2-9][0-9]) 
     ) 
     (?<decade> 
      [0-9]{2} (?!\d) 
     ) 
     (?<year> 
      (?&century)? (?&decade)(?!\d) 
     ) 
     (?<leapdecade> (
      0[48]  | 
      [2468][048] | 
      [13579][26] 
      )(?!\d) 
     ) 
     (?<leapcentury> (
      16   | 
      [2468][048] | 
      [3579][26] 
      ) 
     ) 
     (?<leapyear> 
      (?&century)?(?&leapdecade)(?!\d) 
      | 
      (?&leapcentury)00(?!\d) 
     ) 
     (?<monthnumber>  (0?[1-9] | 1[0-2])(?!\d)     ) 
     (?<shortmonthnumber> (0?[469] | 11 )(?!\d)     ) 
     (?<longmonthnumber> (0?[13578] | 1[02])(?!\d)    ) 
     (?<nonfebmonth>  (0?[13-9] | 1[0-2])(?!\d)    ) 
     (?<febmonth>   (0?2)(?!\d)        ) 
     (?<twentyeightdays> (0?[1-9] | 1[0-9] | 2[0-8])(?!\d)  ) 
     (?<twentyninedays> ((?&twentyeightdays) | 29)(?!\d)   ) 
     (?<thirtydays>  ((?&twentyeightdays) | 29 | 30)(?!\d) ) 
     (?<thirtyonedays> ((?&twentyeightdays) | 29 | 30 | 31)(?!\d)) 
     (?<separator>  [/.-]        )    #/ markdown syntax highlighter fix 
     (?<ymd> 
      (?&leapyear) (?&separator) (?&febmonth) (?&separator) (?&twentyninedays) (?!\d) 
      | 
      (?&year) (?&separator) (?&longmonthnumber) (?&separator) (?&thirtyonedays) (?!\d) 
      | 
      (?&year) (?&separator) (?&shortmonthnumber) (?&separator) (?&thirtydays) (?!\d) 
      | 
      (?&year) (?&separator) (?&febmonth) (?&separator) (?&twentyeightdays) (?!\d) 
     ) 
     (?<mdy> 
      (?&febmonth) (?&separator) (?&twentyninedays) (?&separator) (?&leapyear) (?!\d) 
      | 
      (?&longmonthnumber) (?&separator) (?&thirtyonedays) (?&separator) (?&year) (?!\d) 
      | 
      (?&shortmonthnumber) (?&separator) (?&thirtydays) (?&separator) (?&year) (?!\d) 
      | 
      (?&febmonth) (?&separator) (?&twentyeightdays) (?&separator) (?&year) (?!\d) 
     ) 
     (?<dmy> 
      (?&twentyninedays) (?&separator) (?&febmonth) (?&separator) (?&leapyear) (?!\d) 
      | 
      (?&thirtyonedays) (?&separator) (?&longmonthnumber) (?&separator)(?&year) (?!\d) 
      | 
      (?&thirtydays) (?&separator) (?&shortmonthnumber) (?&separator) (?&year) (?!\d) 
      | 
      (?&twentyeightdays) (?&separator) (?&febmonth) (?&separator) (?&year) (?!\d) 
     ) 
     (?<date> 
      (?&ymd) | (?&mdy) | (?&dmy) 
     ) 
     (?<exact_date> 
      ^(?&date)$ 
     ) 
    ) 
}x; 

my @test = ("2009-02-29", "2009-02-28", "2004-02-28", "2004-02-29", "2005-03-31", "2005-04-31", "2005-05-31", 
    "28-02-2009","02-28-2009",   
); 

for (@test) { 
    if ($_ =~ m/(?&exact_date) $date_syntax/x) { 
    print "$_ is valid\n"; 
    } 
    else { 
    print "$_ is not valid\n"; 
    } 

    if ($_ =~ m/^(?&ymd) $date_syntax/x) { 
    print "$_ is valid ymd\n"; 
    } 
    else { 
    print "$_ is not valid ymd\n"; 
    } 


    if ($_ =~ m/^(?&leapyear) $date_syntax/x) { 
    print "$_ is leap (start)\n"; 
    } 
    else { 
    print "$_ is not leap (start)\n"; 
    } 

    print "\n"; 
} 

Примечание добавление (?!\d) фрагментов, которые добавляются таким образом, что

«45» обыкновение соответствовать ~= m{(?&twentyeightdays) $syntax} из-за «4» соответствие 0? [4]

+0

Это заставляет меня с нетерпением ждать Perl6. –