【问题标题】:Raku Grammar: Use named regex without consuming matching stringRaku 语法:使用命名正则表达式而不消耗匹配字符串
【发布时间】:2020-05-22 12:09:46
【问题描述】:

我有一个很容易回答的 Raku 语法问题。我不会解析日志文件并按日志条目取回条目日志条目。日志条目可以只是一行或多行字符串。

我的草稿代码如下所示:

grammar Grammar::Entries {
    rule TOP { <logentries>+ }

    token logentries { <loglevel> <logentry> }
    token loglevel { 'DEBUG' | 'WARN' | 'INFO ' | 'ERROR' }
    token logentry { .*? <.finish> }
    token finish { <.loglevel> || $ }
}

这仅适用于第一行,因为在第二行中,第一行匹配消耗了日志级别,尽管我使用了 '.'在正则表达式 中,据我所知,这意味着非捕获。

以下是日志示例:

INFO    2020-01-22T11:07:38Z    PID[8528]   TID[6736]:  Current process-name: C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe
INFO    2020-01-22T11:07:38Z    PID[8528]   TID[6736]:  Session data:
    PID: 1234
    TID: 1234
    Session: 1
INFO    2020-01-22T11:07:38Z    PID[8528]   TID[6736]:  Clean up.

即使对于多行日志条目,什么是正确的方法来取回日志条目? 谢谢!

【问题讨论】:

    标签: grammar raku


    【解决方案1】:

    .*? 有效但效率低下。
    它必须做很多回溯。

    要改进它,您可以使用 \N* 匹配除换行符以外的所有内容。

    grammar Grammar::Entries {
        rule TOP { <logentries>+ }
    
        token logentries { <loglevel> <logentry> }
        token loglevel { 'DEBUG' | 'WARN' | 'INFO' | 'ERROR' }
        token logentry { \N* \n }
    }
    

    然后你必须重新添加换行符匹配。

        token logentry {
          <logline>* %% \n
        }
        token logline { <!before \w> \N* }
    

    这可行,但仍然不是很好。


    我会将语法结构更像您要解析的内容。

    grammar Grammar::Entries {
        token TOP { <logentries>+ }
    
        token logentries { <loglevel> <logentry> }
        token loglevel { 'DEBUG' | 'WARN' | 'INFO' | 'ERROR' }
        token logentry { <logline>* }
        token logline { '    ' <(\N+)> \n? }
    }
    

    由于我注意到日志行总是以 4 个空格开头,因此我们可以使用它来确保只有以该空格开头的行才算作 logline。这也处理了与日志级别有关的行的剩余数据。

    我真的不喜欢你有一个只匹配一件事的复数名称的标记。
    基本上我会将logentries 命名为logentry。当然这意味着logentry也需要改名。

    grammar Grammar::Entries {
        token TOP { <logentry>+ }
    
        token logentry { <loglevel> <logdata> }
        token loglevel { 'DEBUG' | 'WARN' | 'INFO' | 'ERROR' }
        token logdata { <logline>* }
        token logline { '    ' <(\N+)> \n? }
    }
    

    我也不喜欢附加到每个令牌上的多余的log

    grammar Grammar::Entries {
        token TOP { <entry>+ }
    
        token entry { <level> <data> }
        token level { 'DEBUG' | 'WARN' | 'INFO' | 'ERROR' }
        token data { <line>* }
        token line { '    ' <(\N+)> \n? }
    }
    

    这就是说Grammar::Entries 至少包含一个entry
    entrylevel 开头,并以一些 data 结尾。
    data 由任意数量的 lines 组成
    line 以四个空格开头,至少一个非换行符,并且可以以换行符结尾。


    我要说明的是,以与数据结构相同的方式构建语法。

    您甚至可以添加用于提取信息的结构,这样您就不必将其作为第二步。

    【讨论】:

    • 感谢@BradGilbert,非常有帮助。我从自上而下的方法开始,但已经失败了,所以我想我再试一次,这次用更小的步骤。第一个是获取所有日志条目,然后继续进行。
    【解决方案2】:

    据我所知&lt;.loglevel&gt; 表示不捕获。

    这意味着非捕获(不要保留匹配,以便代码稍后可以访问它),而不是非匹配

    您要做的是匹配而不推进匹配位置,即所谓的“零宽度断言”。我尚未对此进行测试,但希望它能够工作(著名的遗言):

    grammar Grammar::Entries {
        rule TOP { <logentries>+ }
    
        token logentries { <loglevel> <logentry> }
        token loglevel { 'DEBUG' | 'WARN' | 'INFO ' | 'ERROR' }
        token logentry { .*? <.finish> }
        token finish { <?loglevel> || $ }     # <-- the change
    }
    

    【讨论】:

    • 虽然这确实阻止了loglevel 推进索引,但它在$/ 中留下了before。所以你想要做的是使用&lt;.before…&gt;&lt;?before…&gt;。但到那时,为什么不直接使用 /&lt;?loglevel&gt;/ 来做同样的事情。
    • 谢谢布拉德。固定的。虽然我非常更喜欢你的回答。
    • 谢谢 raiph。你当然是对的。非捕获不是零宽度断言。那确实是一条腰带。但是,我会选择布拉德的答案,因为它更详细。
    • 是的,我倾向于回答我认为应该问的问题,而不是实际问的问题。
    猜你喜欢
    • 1970-01-01
    • 2014-05-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-08-20
    • 2023-03-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多