【问题标题】:Regexp rules for tokenizing with nette/tokenizer (PHP)使用 nette/tokenizer (PHP) 进行标记的正则表达式规则
【发布时间】:2021-11-02 16:04:11
【问题描述】:

我已经在一个沙盒项目中安装了 nette/tokenizer https://packagist.org/packages/nette/tokenizer 来玩令牌化。

“基本”规则定义

最基本的测试是玩他们在这里给出的例子:https://doc.nette.org/en/3.1/tokenizer#toc-string-tokenization由这3条规则组成:

    $languagePatterns[ 'basic' ] = [
        'number' => '\d+',
        'whitespace' => '\s+',
        'string' => '\w+',
    ];

我称这种语言为“基础”,这是我测试的输入:

There were 101 dalmatians
There were   101      dalmatians

我在输出中用颜色绘制每个标记,效果很好:

“括号”规则定义

我现在想尝试识别“括号中的块”并让其余部分保持不变。例如对于这个输入:

There were (more than) 101 dalmatians

(more than) 块应该是一个标记,其余的标记为基本语言(区分字符词、空格和数字)。所以输出应该是:

"string":     "There"
"whitespace": " "
"string":     "were"
"whitespace": " "
"group":      "(more than)"
"whitespace": " "
"number":     "101"
"whitespace": " "
"string":     "dalmatians"

所以我保留了我的规则,并添加了一条新规则group,如下所示:

    $languagePatterns[ 'parentheses' ] = [
        'number' => '\d+',
        'whitespace' => '\s+',
        'string' => '\w+',
        'group' => '\((.*)\)',
    ];

它有效:

问题

我现在想这样标记:

  • “括号块”是一个标记
  • “任何其他”是另一个标记。

例如输入

There were (more than) 101 dalmatians

应该被标记为:

"anything":   "There were "
"group":      "(more than)"
"anything":   " 101 dalmatians"

我尝试了这样的规则:

    $languagePatterns[ 'parentheses2' ] = [
        'whatever' => '.*',
        'group' => '\((.*)\)',
    ];

我得到一个完整的匹配:

或者如果规则的顺序有影响,我会尝试那些(首先匹配括号块,然后是其他任何东西):

    $languagePatterns[ 'parentheses2' ] = [
        'group' => '\((.*)\)',
        'whatever' => '.*',
    ];

然后我什么都不匹配。

问题

我想知道我应该在 PHP 中放置什么规则以使 nette/tokenizer 按预期运行。

【问题讨论】:

  • 'whatever' => '[^()]+'
  • 经过测试,它可以工作!您能否将其设置为答案以便选择它?此外,对于所有读者来说,如果你能善意地表达它的作用,那就太棒了。
  • XRef:我提出这个问题是为了解决另一个问题stackoverflow.com/questions/68988193/…
  • XRef:添加对@Lone Learner 的旧问题的引用,该问题也在寻求如何匹配“其他所有内容”stackoverflow.com/questions/27217075/…

标签: regex tokenize regular-language nette


【解决方案1】:

当您定义一个标记时,请注意该模式不匹配任何内容(空字符串)。这是一种预防措施,因为在修改令牌模式时很容易被忽略。例如。 .*,尤其是短量词 * 表示零个或多个 {0,}

他们的 group 令牌对此免疫:

        'group' => '\((.*)\)',

因为它包含括号,所以它至少有两个字符。

但是他们的 whatever 令牌不是:

        'whatever' => '.*',

它可以同时匹配too littletoo much

由于 Nette Tokenzier 看起来有一个先赢的策略,所以将其放在 group 上方不会匹配任何内容,将其放在 group 下方会匹配所有内容(因为标记化从第一个字符)。

您可以通过另外标记括号字符来“取消定义”括号字符,而不是让它不匹配:

        'whatever' => '[^()]+',

匹配任何字符 () - 至少一次+ 是一个或多个{1,} 的短量词) .

说到标记化这可能已经回答了您的第一手问题,但是问题本身应该突出一个额外的问题:某些字符串将无法完全标记化。正如这个 Nette Tokenizer 示例所示,没有匹配项应该是警告标志。

例如如果有一个未封闭的怎么办?或者字符串从可能是 group 的中间开始?

您可能需要考虑单独匹配每个括号字符,然后在令牌流上解释层次结构(例如组)。但即使没有,令牌也应该解析整个字符流:

        'group' => '\((.*)\)',
        'text' => '[^()]+',
        'parenthesis_open' => '\(',
        'parenthesis_close' => '\)',
        'any' => '.+',

这首先具有最高顺序的标记(),然后是备用的左括号和右括号以及一个终止的任何/全部标记以匹配其他所有内容。

如果令牌流包含任何令牌,您可以轻松识别可能不是预期的部分。如果你把 parenthesis_* 标记放在外面,你可以在那里找到它们。

最后一个例子也应该可见

        'group' => '\((.*)\)',

也可以捕获太多,例如不是配对括号。也许可以使用基于 PCRE 的正则表达式模式(它可以支持递归)来制定这一点,但从技术上讲,这在标记化方面发挥了很大作用。不是一个严格的规则,但标记化通常是一种分而治之的策略,所以用简单的标记将其分解,稍后对标记流进行解析(例如,什么构成一个组)。

仅仅通过标记化,最好限制它:

        'group' => '\(([^()]*)\)',

不包含任何内容或文本。你可以看到图案。

【讨论】:

    【解决方案2】:

    此标记化代码尝试返回通过表达式找到的所有匹配项。

    使用

    $languagePatterns[ 'basic' ] = [
        'anythingbutparens' => '[^()]+',
    ];
    

    您将实现所需的任何字符串,不包括括号。

    解释

      [^()]+                   any character except: '(', ')' (1 or more
                               times (matching the most amount possible))
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-08-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-04-10
      • 1970-01-01
      相关资源
      最近更新 更多