更好的正则表达式语法思想

时间:2011-02-06 17:04:15

标签: java regex fluent

我需要一些帮助才能完成关于正则表达式的想法。

简介

SE上有正则表达式question about better syntax,但我认为我不会使用流利的语法。 这对新手来说肯定不错,但是如果复杂的正则表达式, 你用一整页稍微好一点的胡言乱语来代替一条乱码。 我喜欢approach by Martin Fowler,其中正则表达式由较小的部分组成。 他的解决方案是可读的,但手工制作;他提出了一种聪明的方法来构建复杂的正则表达式而不是支持它的类。

我正在尝试使用类似的东西(首先参见他的例子)

final MyPattern pattern = MyPattern.builder()
.caseInsensitive()
.define("numberOfPoints", "\\d+")
.define("numberOfNights", "\\d+")
.define("hotelName", ".*")
.define(' ', "\\s+")
.build("score `numberOfPoints` for `numberOfNights` nights? at `hotelName`");

MyMatcher m = pattern.matcher("Score 400 FOR 2 nights at Minas Tirith Airport");
System.out.println(m.group("numberOfPoints")); // prints 400

其中fluent语法用于组合扩展的regex,如下所示:

  • 定义命名模式并通过封闭反引号来使用它们
    • `name`创建一个命名组
      • 助记符:shell捕获反引号中包含的命令的结果
    • `:name`创建一个非捕获组
      • 助记符:类似于(?: ... )
    • `-name`创建了一个反向引用
      • 助记符:短划线将其连接到上一次出现
  • 重新定义个别角色并在任何地方使用它,除非引用
    • 此处仅允许一些字符(例如~ @#%“)
      • 重新定义+(会非常混乱,因此不允许
      • 重新定义空间以表示任何间距在上面的示例中非常自然
      • 重新定义角色可以使图案更紧凑,除非过度使用
      • ,这是好的 例如,使用define('#', "\\\\")之类的东西来匹配反斜杠可以使模式更具可读性
  • 重新定义一些引用的序列,例如\s\w

命名模式充当一种局部变量,有助于将复杂的表达式分解为小而易于理解的部分。 正确的命名模式通常不需要注释。

问题

上述应该不难实现(我已经完成了大部分工作)并且可能非常有用,我希望如此。 你这么认为吗?

但是,我不确定它应该如何在括号内表现,有时候使用定义是有意义的,有时候不是,例如在

.define(' ', "\\s")            // a blank character
.define('~', "/\**[^*]+\*/")   // an inline comment (simplified)
.define("something", "[ ~\\d]")

将空间扩展到\s是有道理的,但扩展波浪号则不然。 也许应该有一个单独的语法以某种方式定义自己的角色类?

你能想到一些命名模式非常有用或根本没用的例子吗? 我需要一些边境案例和一些改进的想法。

对tchrist回答的反应

对他的反对意见的评论

  1. 缺少多行模式字符串。
    • Java中没有多行字符串,我想改变,但不能。
  2. 免于疯狂繁琐且容易出错的双重反弹......
    • 这是我不能做的事情,我只能提供一个解决方法,s。下方。
  3. 无效正则表达式文字缺少编译时异常,缺少正确编译的正则表达式文字的编译时缓存。
    • 由于正则表达式只是标准库的一部分而不是语言本身的一部分,所以这里没有什么可以做的。
  4. 没有调试或分析工具。
    • 我在这里什么都不做。
  5. 缺乏对UTS#18的遵守。
    • 通过重新定义我提出的相应模式,可以轻松解决这个问题。它并不完美,因为在调试器中你会看到被炸毁的替换品。
  6. 我看起来你不喜欢Java。我很高兴看到一些语法改进,但我无能为力。我正在寻找使用当前Java的东西。

    RFC 5322

    您的示例可以使用我的语法轻松编写:

    final MyPattern pattern = MyPattern.builder()
    .define(" ", "") // ignore spaces
    .useForBackslash('#') // (1): see (2)
    .define("address",         "`mailbox` | `group`")
    .define("WSP",             "[\u0020\u0009]")
    .define("DQUOTE",          "\"")
    .define("CRLF",            "\r\n")
    .define("DIGIT",           "[0-9]")
    .define("ALPHA",           "[A-Za-z]")
    .define("NO_WS_CTL",       "[\u0001-\u0008\u000b\u000c\u000e-\u001f\u007f]") // No whitespace control
    ...
    .define("domain_literal",  "`CFWS`? #[ (?: `FWS`? `dcontent`)* `FWS`? #] `CFWS1?") // (2): see (1)
    ...
    .define("group",           "`display_name` : (?:`mailbox_list` | `CFWS`)? ; `CFWS`?")
    .define("angle_addr",      "`CFWS`? < `addr_spec` `CFWS`?")
    .define("name_addr",       "`display_name`? `angle_addr`")
    .define("mailbox",         "`name_addr` | `addr_spec`")
    .define("address",         "`mailbox` | `group`")
    .build("`address`");
    

    缺点

    在重写您的示例时,我遇到了以下问题:

    • 由于没有\xdd转义序列\udddd必须使用
    • 使用另一个字符而不是反斜杠有点奇怪
    • 因为我更喜欢自下而上写,所以我不得不将你的行恢复为
    • 我不知道它做了什么,我除了自己做了一些错误

    好的一面: - 忽略空格没问题 - 评论没问题 - 可读性好

    最重要的是: 它是普通的Java,并按原样使用现有的正则表达式引擎。

2 个答案:

答案 0 :(得分:3)

命名捕获示例

  

你能想到一些命名模式非常有用或根本没用的例子吗?

在回答您的问题时,以下是命名模式特别有用的示例。它是用于解析RFC 5322邮件地址的Perl或PCRE模式。首先,凭借/x,它处于(?x)模式。其次,它将定义与调用分开;命名组address是执行完整递归下降解析的东西。它的定义在非执行(?DEFINE)…)块中跟随它。

   (?x)              # allow whitespace and comments

   (?&address)       # this is the capture we call as a "regex subroutine"

   # the rest is all definitions, in a nicely BNF-style
   (?(DEFINE)

     (?<address>         (?&mailbox) | (?&group))
     (?<mailbox>         (?&name_addr) | (?&addr_spec))
     (?<name_addr>       (?&display_name)? (?&angle_addr))
     (?<angle_addr>      (?&CFWS)? < (?&addr_spec) > (?&CFWS)?)
     (?<group>           (?&display_name) : (?:(?&mailbox_list) | (?&CFWS))? ; (?&CFWS)?)
     (?<display_name>    (?&phrase))
     (?<mailbox_list>    (?&mailbox) (?: , (?&mailbox))*)

     (?<addr_spec>       (?&local_part) \@ (?&domain))
     (?<local_part>      (?&dot_atom) | (?&quoted_string))
     (?<domain>          (?&dot_atom) | (?&domain_literal))
     (?<domain_literal>  (?&CFWS)? \[ (?: (?&FWS)? (?&dcontent))* (?&FWS)?
                                   \] (?&CFWS)?)
     (?<dcontent>        (?&dtext) | (?&quoted_pair))
     (?<dtext>           (?&NO_WS_CTL) | [\x21-\x5a\x5e-\x7e])

     (?<atext>           (?&ALPHA) | (?&DIGIT) | [!#\$%&'*+-/=?^_`{|}~])
     (?<atom>            (?&CFWS)? (?&atext)+ (?&CFWS)?)
     (?<dot_atom>        (?&CFWS)? (?&dot_atom_text) (?&CFWS)?)
     (?<dot_atom_text>   (?&atext)+ (?: \. (?&atext)+)*)

     (?<text>            [\x01-\x09\x0b\x0c\x0e-\x7f])
     (?<quoted_pair>     \\ (?&text))

     (?<qtext>           (?&NO_WS_CTL) | [\x21\x23-\x5b\x5d-\x7e])
     (?<qcontent>        (?&qtext) | (?&quoted_pair))
     (?<quoted_string>   (?&CFWS)? (?&DQUOTE) (?:(?&FWS)? (?&qcontent))*
                          (?&FWS)? (?&DQUOTE) (?&CFWS)?)

     (?<word>            (?&atom) | (?&quoted_string))
     (?<phrase>          (?&word)+)

     # Folding white space
     (?<FWS>             (?: (?&WSP)* (?&CRLF))? (?&WSP)+)
     (?<ctext>           (?&NO_WS_CTL) | [\x21-\x27\x2a-\x5b\x5d-\x7e])
     (?<ccontent>        (?&ctext) | (?&quoted_pair) | (?&comment))
     (?<comment>         \( (?: (?&FWS)? (?&ccontent))* (?&FWS)? \) )
     (?<CFWS>            (?: (?&FWS)? (?&comment))*
                         (?: (?:(?&FWS)? (?&comment)) | (?&FWS)))

     # No whitespace control
     (?<NO_WS_CTL>       [\x01-\x08\x0b\x0c\x0e-\x1f\x7f])

     (?<ALPHA>           [A-Za-z])
     (?<DIGIT>           [0-9])
     (?<CRLF>            \x0d \x0a)
     (?<DQUOTE>          ")
     (?<WSP>             [\x20\x09])
   )

我强烈建议不要重新使用完美的车轮。从与PCRE兼容开始。如果您希望超越基本的Perl5模式,例如上面的RFC5322解析器,那么总是需要Perl6 patterns

真的,非常支付对现有实践和文献的研究,然后开始进行开放式的研发任务。这些问题早已得到解决,有时也相当优雅。

改进Java Regex语法

如果你真的想要更好的Java正则表达式语法思想,你必须首先解决Java正则表达式中的这些特殊缺陷:

  1. 缺少多行模式字符串,如上所示。
  2. 免于疯狂繁琐且容易出错的双重反复,如上所述。
  3. 无效的正则表达式文字缺少编译时异常,缺少正确编译的正则表达式文字的编译时缓存。
  4. 无法更改"foo".matches(pattern)之类的内容以使用更好的模式库,部分原因可能不仅仅是因为final类不可覆盖。
  5. 没有调试或分析工具。
  6. 缺乏对UTS#18: Basic Regular Expression support的遵从性,这是使Java正则表达式对Unicode有用的最基本步骤。他们目前不是。它们甚至不支持十年前的Unicode 3.1属性,这意味着你不能以任何合理的方式使用Java模式进行Unicode;没有基本构件。
  7. 其中,前3个已经用几种JVM语言解决,包括Groovy和Scala;甚至Clojure都在那里。

    第二组3个步骤将更加严格,但绝对是强制性的。最后一个,即使是正则表达式中最基本的Unicode支持,也只是将Java用于Unicode工作。这在游戏后期是完全不可原谅的。如果需要,我可以提供大量的例子,但你应该相信我,因为我真的知道我在这里谈论的是什么。

    只有在完成所有这些工作后,您才会担心修复Java的正则表达式,以便他们能够赶上模式匹配中的当前最新技术水平。除非你照顾过去的这些疏忽,否则你不能开始关注现在,更不用说将来了。

答案 1 :(得分:1)

我认为也许正则表达式并不是真正意义上的所需,而是诸如Parser-Combinator库(可以处理字符和/或在其构造中包含正则表达式)之类的东西。 / p>

也就是说,步骤超越正则表达式的范围(不规则地实现它们 - tchrist绝对喜欢Perl实现;-)并且至少是无上下文语法 - 或者至少可以用LL(n)表示的那些,最好是最小的回溯。

Scala: The Magic Begind Parse-Combinators注意它看起来与BCNF非常相似。有一个很好的介绍。

Haskel: Parsec同上。

Java中的一些示例是JParsecJPC

然而,Java作为一种语言并不像某些竞争对手那样有利于无缝DSL扩展; - )