【问题标题】:Better regex syntax ideas更好的正则表达式语法思想
【发布时间】:2011-06-22 08:02:35
【问题描述】:

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

简介

在 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

其中流利的语法用于组合正则表达式,扩展如下:

  • 定义命名模式并通过用反引号括起来使用它们
    • `name` 创建一个命名组
      • 助记符:shell 捕获包含在反引号中的命令的结果
    • `:name` 创建非捕获组
      • 助记符:类似于(?:...)
    • `-name` 创建反向引用
      • 助记符:破折号将其连接到上一个事件
  • 重新定义单个字符并在任何地方使用它,除非被引用
    • 这里只允许使用一些字符(例如,~ @#%")
      • 重新定义 +( 会非常混乱,因此不允许
      • 在上面的示例中,将空格重新定义为任何间距都很自然
      • 重新定义字符可以使模式更紧凑,除非过度使用,否则这很好
      • 例如,使用 define('#', "\\\\") 之类的东西来匹配反斜杠可以使模式更具可读性
  • 重新定义一些引用序列,如\s\w
    • 标准定义为not Unicode conform
    • 有时您可能有自己的想法,什么是单词或空格

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

问题

上面的内容应该不难实现(我已经做了大部分),我希望它真的很有用。 你这么认为吗?

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

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

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

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

对 tchrist 回答的反应

对他的反对意见的评论

  1. 缺少多行模式字符串。
    • Java 中没有多行字符串,我想更改,但不能。
  2. 从繁重且容易出错的双反斜杠中解放出来...
    • 这又是我不能做的事情,我只能提供一种解决方法,s。下面。
  3. 在无效的正则表达式文字上缺少编译时异常,并且在正确编译的正则表达式文字上缺少编译时缓存。
    • 由于正则表达式只是标准库的一部分,而不是语言本身的一部分,因此这里无能为力。
  4. 没有调试或分析工具。
    • 我在这里无能为力。
  5. 不符合 UTS#18。
    • 这可以通过按照我的建议重新定义相应的模式来轻松解决。这并不完美,因为在调试器中您会看到被炸毁的替换。

我看起来你不喜欢 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,并按原样使用现有的正则表达式引擎。

【问题讨论】:

  • Java 1.7 已命名捕获。 (?&lt;name&gt; ...) 定义一个,\k&lt;name&gt; 反向引用它,就像在 Perl 中一样。
  • 我知道...但它们仅可用于捕获,不能用于定义稍后使用的子表达式。
  • maaartinus:您以后不能使用它们,因为 Java 还没有 (?(DEFINE)…) 块。这就是完成这项工作所需要的一切;然后你可以用(?&amp;name) 给他们打电话。我有这些here in the second, longer example 的更长示例。它们非常有用,但您应该已经在我给出的 BNF 示例中看到了这一点。
  • Java 没有 (?(DEFINE)...) 块,恐怕它会保持很长时间。但是,正如我向您展示的那样,它可以用我的语法代替。期待我在大约两个我们的长答案。
  • 这是一个相当不错的努力,我不得不承认。不过,我看到你还没有接近实现整个事情。 你明白为什么 Java 不可能做到这一点吗?这是因为 (?&amp;comment) 生产。此外,您知道,UTS#18 合规性要求远不止 RL1.2a。 Java 也不符合 RL1.1、RL1.2 和 RL1.4 以及这两个强烈建议。至于“不喜欢 Java”,我尽量务实:我更喜欢实际工作的东西,而不是皇家 PITA。归根结底,我是on Rob’s side

标签: java regex fluent


【解决方案1】:

命名捕获示例

你能想出一些例子,其中命名模式非常有用或根本没用吗?

在回答您的问题时,这是一个命名模式特别有用的示例。这是一种用于解析 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 兼容开始。如果您希望超越上述 RFC5322 解析器等基本 Perl5 模式,总有 Perl6 patterns 可供借鉴。

在开始一项开放式研发任务之前,对现有实践和文献进行研究真的,真的是值得的。这些问题早就解决了,有时相当优雅。

改进 Java 正则表达式语法

如果你真的想要更好的 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;没有基本的构建块。

其中,前 3 个已在多种 JVM 语言中得到解决,包括 Groovy 和 Scala;甚至 Clojure 也在其中。

第二组 3 个步骤会更难,但绝对是强制性的。最后一个,甚至连最基本的 Unicode 正则表达式都没有支持,简单地扼杀了 Java 的 Unicode 工作。这在游戏后期是完全不可原谅的。如果需要,我可以提供很多例子,但你应该相信我,因为我真的知道我在说什么。

只有完成所有这些之后,您才应该担心修复 Java 的正则表达式,以便它们能够赶上模式匹配领域的最新技术。除非您处理好这些过去的疏忽,否则您无法开始展望现在,更不用说展望未来了。

【讨论】:

  • "我强烈建议不要重新发明一个完美的轮子。" - 不幸的是,你的轮子 - 与我的不同 - 需要重新定义 Java 语法并重新编写整个正则表达式引擎。但是谢谢你的回答,我稍后会更详细地评论它。
  • @maaartinus:关于缓存的第三点你可以不用,但前两点非常重要;编译到 JVM 中的其他语言确实管理所有这三种语言。关于 Unicode 的最后一点,您可以使用 a rewrite library like this 来帮助使 Java 的模式更符合 UTS#18: Unicode Regular Expression’s requirement #RL1.2a,但它们仍然缺少 RL1.2 的其余部分,等等。
  • 为什么需要缓存?使用private final Pattern ...,它会被编译一次,并且可以重复使用。我还可以为动态创建的正则表达式添加缓存,肯定会非常冗长,但这是 Java 方式。我对正则表达式的最大问题是它们的不可读性——这就是我试图解决的问题,并且认为它比你的任何观点都重要。您的里程可能会有所不同,但许多程序员不需要高级概念或严格的 Unicode 一致性(甚至不知道它们存在)。
【解决方案2】:

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

也就是说,跨步正则表达式领域(尽可能不规则地实现——tchrist 绝对喜欢 Perl 实现 ;-) 并进入上下文无关语法——或至少那些可以用 LL(n) 表示的,最好用最少的回溯。

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

Haskel: Parsec 同上。

Java 中的一些示例是JParsecJPC

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

【讨论】:

  • 我不太喜欢这个 DSL。它非常冗长,尤其是在 Java 中。但是,谢谢指针。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-05-06
  • 1970-01-01
  • 2011-06-15
  • 2016-08-18
  • 2012-01-14
  • 2011-03-22
  • 2011-08-23
相关资源
最近更新 更多