草庐IT

java - 更好的正则表达式语法想法

coder 2024-03-20 原文

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

介绍

有一个 question about better syntax对于 SE 上的正则表达式,但我认为我不会使用流畅的语法。
这对新手来说肯定很好,但在复杂的正则表达式的情况下,
你用一整页稍微好一点的胡言乱语替换一行胡言乱语。
我喜欢 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 语法用于组合扩展如下的正则表达式:
  • 定义命名模式并通过用反引号括起来来使用它们
  • `name`创建一个命名组
  • 助记符:shell 捕获用反引号括起来的命令的结果
  • `:name`创建一个非捕获组
  • 助记符:类似于 (?: ... )
  • `-name`创建反向引用
  • 助记符:破折号将它连接到上一个事件
  • 重新定义单个字符并在任何地方使用它,除非引用
  • 这里只允许使用一些字符(例如,~ @#% ")
  • 重新定义 +(会非常困惑,所以不允许
  • 在上面的示例中,重新定义空间以表示任何间距是非常自然的
  • 重新定义一个字符可以使模式更紧凑,除非过度使用,否则这很好
  • 例如,使用类似 define('#', "\\\\") 的东西匹配反斜杠可以使模式更具可读性
  • 重新定义一些引用序列,如 \s\w
  • 标准定义是 not Unicode conform
  • 有时您可能有自己的想法

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

    问题

    以上应该不难实现(我已经做了大部分)并且可能非常有用,我希望。
    你是这样认为的吗?

    但是,我不确定它在括号内应该如何表现,有时使用定义有意义,有时则没有,例如在
    .define(' ', "\\s")            // a blank character
    .define('~', "/\**[^*]+\*/")   // an inline comment (simplified)
    .define("something", "[ ~\\d]")
    

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

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

    对基督的回答的 react

    对他的反对意见的评论
  • 缺少多行模式字符串。
  • 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 并按原样使用现有的正则表达式引擎。

    最佳答案

    命名捕获示例

    Can you think of some examples where the named pattern are very useful or not useful at all?



    为了回答您的问题,这里有一个示例,其中命名模式特别有用。它是用于解析 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 正则表达式语法

    如果你真的想要更好的 Java 正则表达式语法想法,你必须首先解决 Java 正则表达式中的这些特定缺陷:
  • 缺少多行模式字符串,如上所示。
  • 如上所述,无需使用极其繁琐且容易出错的双反斜杠。
  • 缺少对无效正则表达式文本的编译时异常,并且缺少正确编译的正则表达式文本的编译时缓存。
  • 无法更改类似 "foo".matches(pattern) 的内容使用更好的模式库,部分但不完全是因为 final不可覆盖的类。
  • 没有调试或分析工具。
  • 不符合 UTS#18: Basic Regular Expression support ,使 Java 正则表达式对 Unicode 有用所需的最基本步骤。他们目前不是。它们甚至不支持十年前的 Unicode 3.1 属性,这意味着您不能以任何合理的方式为 Unicode 使用 Java 模式;没有基本的构建块。

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

    第二组 3 个步骤将更加艰难,但绝对是强制性的。最后一个,在正则表达式中甚至连最基本的 Unicode 支持都没有,只是为了 Unicode 工作而扼杀了 Java。这在游戏后期是完全不可原谅的。如果需要,我可以提供大量示例,但您应该相信我,因为我真的知道我在这里谈论的是什么。

    只有在完成所有这些之后,您才应该担心修复 Java 的正则表达式,以便它们能够 catch 模式匹配的当前技术水平。除非您处理这些过去的疏忽,否则您无法开始关注现在,更不用说 future 了。

    关于java - 更好的正则表达式语法想法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4914774/

    有关java - 更好的正则表达式语法想法的更多相关文章

    1. ruby - 树顶语法无限循环 - 2

      我脑子里浮现出一些关于一种新编程语言的想法,所以我想我会尝试实现它。一位friend建议我尝试使用Treetop(Rubygem)来创建一个解析器。Treetop的文档很少,我以前从未做过这种事情。我的解析器表现得好像有一个无限循环,但没有堆栈跟踪;事实证明很难追踪到。有人可以指出入门级解析/AST指南的方向吗?我真的需要一些列出规则、常见用法等的东西来使用像Treetop这样的工具。我的语法分析器在GitHub上,以防有人希望帮助我改进它。class{initialize=lambda(name){receiver.name=name}greet=lambda{IO.puts("He

    2. ruby 正则表达式 - 如何替换字符串中匹配项的第 n 个实例 - 2

      在我的应用程序中,我需要能够找到所有数字子字符串,然后扫描每个子字符串,找到第一个匹配范围(例如5到15之间)的子字符串,并将该实例替换为另一个字符串“X”。我的测试字符串s="1foo100bar10gee1"我的初始模式是1个或多个数字的任何字符串,例如,re=Regexp.new(/\d+/)matches=s.scan(re)给出["1","100","10","1"]如果我想用“X”替换第N个匹配项,并且只替换第N个匹配项,我该怎么做?例如,如果我想替换第三个匹配项“10”(匹配项[2]),我不能只说s[matches[2]]="X"因为它做了两次替换“1fooX0barXg

    3. ruby-on-rails - 使用 Sublime Text 3 突出显示 HTML 背景语法中的 ERB? - 2

      所以我在关注Railscast,我注意到在html.erb文件中,ruby代码有一个微弱的背景高亮效果,以区别于其他代码HTML文档。我知道Ryan使用TextMate。我正在使用SublimeText3。我怎样才能达到同样的效果?谢谢! 最佳答案 为SublimeText安装ERB包。假设您安装了SublimeText包管理器*,只需点击cmd+shift+P即可获得命令菜单,然后键入installpackage并选择PackageControl:InstallPackage获取包管理器菜单。在该菜单中,键入ERB并在看到包时选择

    4. java - 等价于 Java 中的 Ruby Hash - 2

      我真的很习惯使用Ruby编写以下代码:my_hash={}my_hash['test']=1Java中对应的数据结构是什么? 最佳答案 HashMapmap=newHashMap();map.put("test",1);我假设? 关于java-等价于Java中的RubyHash,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/22737685/

    5. ruby - 覆盖相似的方法,更短的语法 - 2

      在Ruby类中,我重写了三个方法,并且在每个方法中,我基本上做同样的事情:classExampleClassdefconfirmation_required?is_allowed&&superenddefpostpone_email_change?is_allowed&&superenddefreconfirmation_required?is_allowed&&superendend有更简洁的语法吗?如何缩短代码? 最佳答案 如何使用别名?classExampleClassdefconfirmation_required?is_a

    6. ruby-on-rails - 更好的替代方法 try( :output). try( :data). try( :name)? - 2

      “输出”是一个序列化的OpenStruct。定义标题try(:output).try(:data).try(:title)结束什么会更好?:) 最佳答案 或者只是这样:deftitleoutput.data.titlerescuenilend 关于ruby-on-rails-更好的替代方法try(:output).try(:data).try(:name)?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.c

    7. ruby 语法糖 : dealing with nils - 2

      可能已经问过了,但我找不到它。这里有2个常见的情况(对我来说,在编程Rails时......)用ruby​​编写是令人沮丧的:"astring".match(/abc(.+)abc/)[1]在这种情况下,我得到一个错误,因为字符串不匹配,因此在nil上调用[]运算符。我想找到的是比以下内容更好的替代方法:temp="astring".match(/abc(.+)abc/);temp.nil??nil:temp[1]简而言之,如果不匹配,则简单地返回nil而不会出错第二种情况是这样的:var=something.very.long.and.tedious.to.writevar=some

    8. java - 从 JRuby 调用 Java 类的问题 - 2

      我正在尝试使用boilerpipe来自JRuby。我看过guide从JRuby调用Java,并成功地将它与另一个Java包一起使用,但无法弄清楚为什么同样的东西不能用于boilerpipe。我正在尝试基本上从JRuby中执行与此Java等效的操作:URLurl=newURL("http://www.example.com/some-location/index.html");Stringtext=ArticleExtractor.INSTANCE.getText(url);在JRuby中试过这个:require'java'url=java.net.URL.new("http://www

    9. ruby-on-rails - 使用 ruby​​ 将多个实例变量转换为散列的更好方法? - 2

      我收到格式为的回复#我需要将其转换为哈希值(针对活跃商家)。目前我正在遍历变量并执行此操作:response.instance_variables.eachdo|r|my_hash.merge!(r.to_s.delete("@").intern=>response.instance_eval(r.to_s.delete("@")))end这有效,它将生成{:first="charlie",:last=>"kelly"},但它似乎有点hacky和不稳定。有更好的方法吗?编辑:我刚刚意识到我可以使用instance_variable_get作为该等式的第二部分,但这仍然是主要问题。

    10. ruby - Ruby 语法糖有 "rules"吗? - 2

      我正在学习Ruby的基础知识(刚刚开始),我遇到了Hash.[]method.它被引入a=["foo",1,"bar",2]=>["foo",1,"bar",2]Hash[*a]=>{"foo"=>1,"bar"=>2}稍加思索,我发现Hash[*a]等同于Hash.[](*a)或Hash.[]*一个。我的问题是为什么会这样。是什么让您将*a放在方括号内,是否有某种规则可以在何时何地使用“it”?编辑:我的措辞似乎造成了一些困惑。我不是在问数组扩展。我明白了。我的问题基本上是:如果[]是方法名称,为什么可以将参数放在括号内?这看起来几乎——但不完全是——就像说如果你有一个方法Foo.d

    随机推荐