【发布时间】: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 回答的反应
对他的反对意见的评论
- 缺少多行模式字符串。
- Java 中没有多行字符串,我想更改,但不能。
- 从繁重且容易出错的双反斜杠中解放出来...
- 这又是我不能做的事情,我只能提供一种解决方法,s。下面。
- 在无效的正则表达式文字上缺少编译时异常,并且在正确编译的正则表达式文字上缺少编译时缓存。
- 由于正则表达式只是标准库的一部分,而不是语言本身的一部分,因此这里无能为力。
- 没有调试或分析工具。
- 我在这里无能为力。
- 不符合 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 已命名捕获。
(?<name> ...)定义一个,\k<name>反向引用它,就像在 Perl 中一样。 -
我知道...但它们仅可用于捕获,不能用于定义稍后使用的子表达式。
-
maaartinus:您以后不能使用它们,因为 Java 还没有
(?(DEFINE)…)块。这就是完成这项工作所需要的一切;然后你可以用(?&name)给他们打电话。我有这些here in the second, longer example 的更长示例。它们非常有用,但您应该已经在我给出的 BNF 示例中看到了这一点。 -
Java 没有 (?(DEFINE)...) 块,恐怕它会保持很长时间。但是,正如我向您展示的那样,它可以用我的语法代替。期待我在大约两个我们的长答案。
-
这是一个相当不错的努力,我不得不承认。不过,我看到你还没有接近实现整个事情。 你明白为什么 Java 不可能做到这一点吗?这是因为
(?&comment)生产。此外,您知道,UTS#18 合规性要求远不止 RL1.2a。 Java 也不符合 RL1.1、RL1.2 和 RL1.4 以及这两个强烈建议。至于“不喜欢 Java”,我尽量务实:我更喜欢实际工作的东西,而不是皇家 PITA。归根结底,我是on Rob’s side。