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

maa*_*nus 7 java regex fluent

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

介绍

关于SE上正则表达式的更好语法有一个问题,但我认为我不会使用流畅的语法.这对新手来说肯定不错,但是如果是复杂的正则表达式,你会用一整页稍微好一点的胡言乱语来代替一行乱码.我喜欢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
Run Code Online (Sandbox Code Playgroud)

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

  • 定义命名模式并通过封闭反引号来使用它们
    • `name` 创建一个命名组
      • 助记符:shell捕获反引号中包含的命令的结果
    • `:name` 创建一个非捕获组
      • 助记符:类似于(?:......)
    • `-name` 创建一个反向引用
      • 助记符:短划线将其连接到上一个匹配项
  • 除非引用,否则重新定义个别角色并在任何地方使用它
    • 这里只允许一些字符(例如~ @#%")"
      • 重新定义+(将是非常混乱,所以这是不允许的
      • 在上面的例子中,重新定义空间意味着任何间距是非常自然的
      • 重新定义一个角色可以使图案更紧凑,除非过度使用,这是好的
      • 例如,使用类似define('#', "\\\\")匹配反斜杠的东西可以使图案更具可读性
  • 重新定义一些引用的序列,如\s\w
    • 标准定义不符合Unicode
    • 有时候你可能有自己的想法是什么单词或空间

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

问题

我希望上面的内容不应该很难实现(我已经完成了大部分工作)并且可能非常有用. 你这么认为吗?

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

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

扩大空间\s是有道理的,但扩大波浪号并不是. 也许应该有一个单独的语法以某种方式定义自己的字符类?

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

对tchrist回答的反应

评论他的反对意见

  1. 缺少多行模式字符串.
    • Java中没有多行字符串,我想改变它,但不能.
  2. 免于疯狂繁琐且容易出错的双重反复...
    • 这也是我不能做的事情,我只能提供一种解决方法.下面.
  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`");
Run Code Online (Sandbox Code Playgroud)

缺点

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

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

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

最重要的是:它是普通的Java并且使用现有的正则表达式引擎.

tch*_*ist 3

命名捕获示例

\n\n
\n

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

\n
\n\n

为了回答您的问题,这里有一个示例,其中命名模式特别有用。它\xe2\x80\x99是用于解析RFC 5322邮件地址的Perl或PCRE模式。首先,它\xe2\x80\x99s/x凭借(?x). 其次,它将定义与调用分开;命名组address是执行完整递归下降解析的东西。它的定义遵循非执行(?DEFINE)\xe2\x80\xa6)块中的定义。

\n\n
   (?x)              # allow whitespace and comments\n\n   (?&address)       # this is the capture we call as a "regex subroutine"\n\n   # the rest is all definitions, in a nicely BNF-style\n   (?(DEFINE)\n\n     (?<address>         (?&mailbox) | (?&group))\n     (?<mailbox>         (?&name_addr) | (?&addr_spec))\n     (?<name_addr>       (?&display_name)? (?&angle_addr))\n     (?<angle_addr>      (?&CFWS)? < (?&addr_spec) > (?&CFWS)?)\n     (?<group>           (?&display_name) : (?:(?&mailbox_list) | (?&CFWS))? ; (?&CFWS)?)\n     (?<display_name>    (?&phrase))\n     (?<mailbox_list>    (?&mailbox) (?: , (?&mailbox))*)\n\n     (?<addr_spec>       (?&local_part) \\@ (?&domain))\n     (?<local_part>      (?&dot_atom) | (?&quoted_string))\n     (?<domain>          (?&dot_atom) | (?&domain_literal))\n     (?<domain_literal>  (?&CFWS)? \\[ (?: (?&FWS)? (?&dcontent))* (?&FWS)?\n                                   \\] (?&CFWS)?)\n     (?<dcontent>        (?&dtext) | (?&quoted_pair))\n     (?<dtext>           (?&NO_WS_CTL) | [\\x21-\\x5a\\x5e-\\x7e])\n\n     (?<atext>           (?&ALPHA) | (?&DIGIT) | [!#\\$%&\'*+-/=?^_`{|}~])\n     (?<atom>            (?&CFWS)? (?&atext)+ (?&CFWS)?)\n     (?<dot_atom>        (?&CFWS)? (?&dot_atom_text) (?&CFWS)?)\n     (?<dot_atom_text>   (?&atext)+ (?: \\. (?&atext)+)*)\n\n     (?<text>            [\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])\n     (?<quoted_pair>     \\\\ (?&text))\n\n     (?<qtext>           (?&NO_WS_CTL) | [\\x21\\x23-\\x5b\\x5d-\\x7e])\n     (?<qcontent>        (?&qtext) | (?&quoted_pair))\n     (?<quoted_string>   (?&CFWS)? (?&DQUOTE) (?:(?&FWS)? (?&qcontent))*\n                          (?&FWS)? (?&DQUOTE) (?&CFWS)?)\n\n     (?<word>            (?&atom) | (?&quoted_string))\n     (?<phrase>          (?&word)+)\n\n     # Folding white space\n     (?<FWS>             (?: (?&WSP)* (?&CRLF))? (?&WSP)+)\n     (?<ctext>           (?&NO_WS_CTL) | [\\x21-\\x27\\x2a-\\x5b\\x5d-\\x7e])\n     (?<ccontent>        (?&ctext) | (?&quoted_pair) | (?&comment))\n     (?<comment>         \\( (?: (?&FWS)? (?&ccontent))* (?&FWS)? \\) )\n     (?<CFWS>            (?: (?&FWS)? (?&comment))*\n                         (?: (?:(?&FWS)? (?&comment)) | (?&FWS)))\n\n     # No whitespace control\n     (?<NO_WS_CTL>       [\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f])\n\n     (?<ALPHA>           [A-Za-z])\n     (?<DIGIT>           [0-9])\n     (?<CRLF>            \\x0d \\x0a)\n     (?<DQUOTE>          ")\n     (?<WSP>             [\\x20\\x09])\n   )\n
Run Code Online (Sandbox Code Playgroud)\n\n

我强烈建议不要重新发明一个完美的轮子。从兼容 PCRE 开始。如果您希望超越基本的 Perl5 模式(如上面的 RFC5322 解析器),则始终可以利用\xe2\x80\x99s Perl6 模式。

\n\n

在开始一项开放式研发任务之前,对现有实践和文献进行研究确实非常值得。这些问题早已得到解决,有时甚至相当优雅。

\n\n

改进 Java 正则表达式语法

\n\n

如果您确实想要更好的 Java 正则表达式语法想法,则必须首先解决 Java\xe2\x80\x99s 正则表达式中的这些特定缺陷:

\n\n
    \n
  1. 缺少多行模式字符串,如上所示。
  2. \n
  3. 免于极其繁重且容易出错的双反斜杠,正如上面所演示的那样。
  4. \n
  5. 缺少无效正则表达式文字的编译时异常,并且缺少正确编译的正则表达式文字的编译时缓存。
  6. \n
  7. 不可能改变一些东西,比如"foo".matches(pattern)使用更好的模式库,部分原因但不仅仅是因为final类不可重写。
  8. \n
  9. 没有调试或分析工具。
  10. \n
  11. 不符合UTS#18:基本正则表达式支持,这是使 Java 正则表达式对 Unicode 有用所需的最基本步骤。目前他们还没有。它们甚至不支持十年前的 Unicode 3.1 属性,这意味着您无法以任何合理的方式将 Java 模式用于 Unicode;基本的构建模块不存在。
  12. \n
\n\n

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

\n\n

第二组 3 个步骤会更困难,但绝对是强制性的。最后一个,正则表达式中甚至缺乏最基本的 Unicode 支持,只会扼杀 Java 的 Unicode 工作。在比赛的后期,这是完全不可原谅的。如果需要的话,我可以提供很多例子,但你应该相信我,因为我真的知道我\xe2\x80\x99m在这里谈论的是什么。

\n\n

只有完成所有这些后,您才应该担心修复 Java\xe2\x80\x99s 正则表达式,以便它们能够赶上模式匹配的当前技术水平。除非你解决了过去的这些疏忽,否则你无法开始展望现在,更不用说展望未来了。

\n