从另一个构建正则表达式

从另一个构建正则表达式

我正在尝试定义一个命令,用户可以在其中定义一个正则表达式,然后该命令将用于创建一个更大的正则表达式,然后该正则表达式将在标记列表上进行匹配。

由于错误的 catcode 设置,从标记列表插入它的简单方法显然会失败:

\documentclass{article}
\usepackage{expl3}

\begin{document}
\ExplSyntaxOn

\tl_set:Nn \l_foo_tl { [a-z]+ }
\regex_const:Nn \l_foo_regex { (\w+)( \[ \u{l_foo_tl} \] ) }
\regex_show:N \l_foo_regex

\seq_new:N \l_foo_seq
\regex_extract_all:NnN \l_foo_regex { a[x], b[yy], c[zzz] } \l_foo_seq
\seq_show:N \l_foo_seq

\ExplSyntaxOff
\end{document}

输出

+-branch
  ,-group begin
  | Match, repeated 1 or more times, greedy
  |   range [97,122]
  |   range [65,90]
  |   range [48,57]
  |   char code 95
  `-group end
  ,-group begin
  | char code 91
  | char 91, catcode 12
  | char 97, catcode 11
  | char 45, catcode 12
  | char 122, catcode 11
  | char 93, catcode 12
  | char 43, catcode 12
  | char code 93
  `-group end.

我想要的是

\regex_const:Nn \l_sub_regex { [a-z]+ }
\regex_const:Nn \l_foo_regex { (\w+)( \[ ... \] ) }

其中...以某种方式插入由 表示的正则表达式\l_sub_regex\c{l_sub_regex}\u{l_sub_regex}在这里都给出错误的结果);或者将编译后的正则表达式转换回其字符串表示形式的方法,类似于\regex_to_str:N

也许有办法使用一些\detokenize\scantokens黑客技术将其从令牌列表中插回来,但我想知道是否l3regex已经为此提供了适当的解决方案。

编辑:我在文档中发现了一条l3regex关于“可能在未来某个时候实现”的功能的说明:

提供一种语法,例如\ur{l_my_regex}在更复杂的正则表达式中使用已编译的正则表达式。这使得正则表达式更容易组合。

因此,看起来这样的功能目前还不存在,但计划在未来推出。


(顺便说一句,如果函数\regex_show:也能打印可打印字符集中的字符的实际 ASCII 表示形式,那将会非常有帮助。其中几行代码的char code XXX调试比必要的要困难。)


编辑2:很遗憾接受的答案当将完整的正则表达式插入捕获组时,似乎无法正常工作。以下代码重现了该问题:

\regex_set:Nn \l_tmpa_regex { a|b|c }
\cs_show:N \l_tmpa_regex

\regex_set:Nn \l_tmpa_regex { (\y{l_tmpa_regex}) }
\regex_set:Nn \l_tmpb_regex { (a|b|c) }
\regex_show:N \l_tmpa_regex
\regex_show:N \l_tmpb_regex
\cs_show:N \l_tmpa_regex
\cs_show:N \l_tmpb_regex

\regex_extract_once:NnNTF \l_tmpa_regex {a} \l_tmpa_seq {} {}
\seq_show:N \l_tmpa_seq
\regex_extract_once:NnNTF \l_tmpb_regex {a} \l_tmpa_seq {} {}
\seq_show:N \l_tmpa_seq

两个正则表达式都应该在标记列表上匹配,a但只有第二个正则表达式匹配。

\l_tmpa_regex和中的最终正则表达式\l_tmpb_regex应该相同,因为两个相同的\regex_show:N输出表明:

> Compiled regex variable \l_tmpa_regex:
+-branch
  ,-group begin
  | char code 97 (a)
  +-branch
  | char code 98 (b)
  +-branch
  | char code 99 (c)
  `-group end.

> Compiled regex variable \l_tmpb_regex:
+-branch
  ,-group begin
  | char code 97 (a)
  +-branch
  | char code 98 (b)
  +-branch
  | char code 99 (c)
  `-group end.

但原始的内部结构表明存在差异(代码缩短了一点):

> \l_tmpa_regex=macro:->
\__regex_branch:n {
    \__regex_group:nnnN {
        \__regex_branch:n {
            <char a>
            \__regex_branch:n { <char b> }
            \__regex_branch:n { <char c> }
        }
    }{1}{0}\c_false_bool
}

> \l_tmpb_regex=macro:->
\__regex_branch:n {
    \__regex_group:nnnN {
        \__regex_branch:n { <char a> }
        \__regex_branch:n { <char b> }
        \__regex_branch:n { <char c> }
    }{1}{0}\c_false_bool
}

如果我们看一下最初插入的正则表达式,内部结构一开始看起来很好:

> \l_tmpa_regex=macro:->
\__regex_branch:n { <char a> }
\__regex_branch:n { <char b> }
\__regex_branch:n { <char c> }

尤其是因为两个结果正则表达式中的命令数量\__regex_branch:n相同,我认为这不是内部正则表达式结构的限制,而是接受的答案将一个正则表达式插入另一个正则表达式的方式的限制。这可能与括号标记进入错误位置有关。

问题似乎比仅仅移动一些括号更复杂。当l3regex读取分支的定义时,它已经在内部将序列推送到\__regex_branch:n { \if_false: } \fi:结果标记列表中。因此,正确的修复必须检查它发生在什么上下文中(分支的开头/中间/结尾)并修改内部标记列表,以使要插入的正则表达式正确适合。

答案1

经过速成课程,l3regex我设法找到了两种(相当愚蠢的)方法来实现您所要求的。我会将它们作为单独的答案发布,因为它们在实现和使用层面上完全不同。

免责声明:这个答案使用了内部代码,l3regex绝对不应该在文档中使用,所以一旦代码发生变化,它可能会中断l3regex,所以请不要使用此代码除非你确实知道自己在做什么。

插入要编译为正则表达式的标记列表:

虽然这种方法允许更大的灵活性,但它与预定义标记列表并使用通常方法预先扩展它大致相同(假设您\regex_const:Nx使用定义\cs_generate_variant:Nn \regex_const:Nn { Nx }):

\tl_const:Nn \c_foo_tl { a-z }
\regex_const:Nx \c_bar_regex { [\tl_use:c{c_foo_tl}]+ }

所以实施这个并没有真正的好处,所以其他方法已实施(见布鲁诺的回答)。

这个在正则表达式被标记之后但在实际编译发生之前介入正则表达式的编译,\y{<tl var>}用 的内容替换所有内容\<tl var>。这是对正则表达式中的标记列表的真正恶意强制执行,但它显然工作正常。

使用此方法,您可以插入任何要编译为正则表达式的标记列表\y{<tl var>}

\tl_const:Nn \c_foo_tl { a-z }
\tl_const:Nn \c_bar_tl { [\y{c_foo_tl}]+ }
\regex_const:Nn \c_foo_regex { (\w+)( \[ \y{c_bar_tl} \] ) }

在上面的例子中,标记列表\c_bar_tl被插入到正则表达式中。\c_bar_tl本身包含对的递归调用\y{c_foo_tl}。我提供了两个不同的宏,您可以选择是否允许递归。如果不允许递归,则上面的代码将匹配文字字符串y{c_foo_tl}。如果允许递归,则\y{c_foo_tl}扩展并插入a-z到表达式中,然后正确编译。请注意,启用递归后,无穷如果您的代码允许,那么递归也会被启用。

我在这一个中使用了\y而不是\ur只是为了保持与其他答案相同的字符,但它很容易就是\ur其他字符串。

我插入了一个宏\__regex_escape_use:nnnn,在编译正则表达式之前,用\y{<tl var>}的内容替换所有内容\<tl var>。这需要在 中添加一行\__regex_escape_use:nnnn,代码从此开始。然后,该函数继续执行适当的替换(递归或非递归),直到找到表达式的结尾。当它完成时,该函数将控制权返回给正则表达式引擎以正常执行其工作。

由于这会在编译正则表达式之前扩展转义序列,因此每个使用的内容没有限制,\<tl var>只要最终表达式为真,扩展后所有\y结果都是有效的正则表达式。这允许您使用一个标记列表定义另一个标记列表c_foo_tl[a-它将\y{c_foo_tl}z]+扩展为预期的[a-z]+

控制转义序列扩展后行为的函数\y\__regex_prescan_yank_eval_continue:n。下面有两个不同的版本可供选择,即递归和非递归,如前所述。

代码如下:

\documentclass{article}
\usepackage{expl3}
\ExplSyntaxOn
\cs_gset_protected:Npn \__regex_escape_use:nnnn #1#2#3#4
  {
    \group_begin:
      \tl_clear:N \l__regex_internal_a_tl
      \cs_set:Npn \__regex_escape_unescaped:N ##1 { #1 }
      \cs_set:Npn \__regex_escape_escaped:N ##1 { #2 }
      \cs_set:Npn \__regex_escape_raw:N ##1 { #3 }
      \__regex_standard_escapechar:
      \tl_gset:Nx \g__regex_internal_tl
        { \__kernel_str_to_other_fast:n {#4} }
      \__regex_prescan_yank:N \g__regex_internal_tl % <-- Added
      \tl_put_right:Nx \l__regex_internal_a_tl
        {
          \exp_after:wN \__regex_escape_loop:N \g__regex_internal_tl
          { break } \prg_break_point:
        }
      \exp_after:wN
    \group_end:
    \l__regex_internal_a_tl
  }
\cs_new_protected:Npn \__regex_prescan_yank:N #1
  { \tl_set:Nx #1 { \exp_args:NV \__regex_prescan_yank:n #1 } }
\cs_set:Npn \__regex_tmp:w #1#2
  {
    \cs_new:Npn \__regex_prescan_yank:n ##1
      { \__regex_prescan_yank:w ##1 #1 \q_nil #2 \q_stop }
    \cs_new:Npn \__regex_prescan_yank:w ##1 #1 ##2 #2
      {
        ##1
        \quark_if_nil:nT {##2}
          { \use_none_delimit_by_q_stop:w }
        \__regex_prescan_yank_eval_continue:n {##2}
      }
  }
\exp_args:Nxx \__regex_tmp:w
  { \__kernel_str_to_other_fast:n { \y } \c_left_brace_str }
  { \c_right_brace_str }
% \cs_new:Npn \__regex_prescan_yank_eval_continue:n #1
%   { % Non-recursive
%     \exp_args:Nv \__kernel_str_to_other_fast:n { #1 }
%     \__regex_prescan_yank:w
%   }
\cs_new:Npn \__regex_prescan_yank_eval_continue:n #1
  { % Recursive
    \exp_last_unbraced:Nf \__regex_prescan_yank:w
    \exp_args:Nv \__kernel_str_to_other_fast:n { #1 }
  }
\ExplSyntaxOff
\begin{document}
\ExplSyntaxOn
\tl_const:Nn \c_foo_tl { a-z }
\tl_const:Nn \c_bar_tl { [\y{c_foo_tl}]+ }
\regex_const:Nn \c_foo_regex { (\w+)( \[ \y{c_bar_tl} \] ) }
% \regex_const:Nn \c_foo_regex { (\w+)( \[ [a-z]+ \] ) }
\regex_show:N \c_foo_regex

\seq_new:N \l_foo_seq
\regex_extract_all:NnN \c_foo_regex { a[x], b[yy], c[zzz] } \l_foo_seq
\seq_show:N \l_foo_seq
\ExplSyntaxOff
\end{document}

答案2

经过速成课程,l3regex我设法找到了两种(相当愚蠢的)方法来实现您所要求的。我会将它们作为单独的答案发布,因为它们在实现和使用层面上完全不同。

免责声明:这个答案使用的内部代码l3regex绝对不应该在文档中使用,因此一旦代码发生变化,它可能会中断l3regex,所以请不要使用此代码除非你确实知道自己在做什么。

将已编译的正则表达式插入另一个正则表达式:

此代码的工作版本现在已l3regex作为\ur转义序列实现,并且文档中曾经存在的注释已不再存在:

提供一种语法,例如\ur{l_my_regex}在更复杂的正则表达式中使用已编译的正则表达式。这使得正则表达式更容易组合。

您首先需要使用\regex_const:Nn\regex_set:Nn类似的东西编译一个正则表达式,然后在新的正则表达式中使用编译后的正则表达式:

\regex_const:Nn \c_bar_regex { [a-z]+ } % Compile \c_bar_regex
\regex_const:Nn \c_foo_regex { (\w+)( \[ \ur{c_bar_regex} \] ) } % Insert it with \ur{...}

我所实现的版本(对于那些关心的人来说,可以在这篇文章的编辑历史中查看)有几个限制;例如,它不允许使用量词,而当前的实现允许l3regex使用量词:

\regex_const:Nn \c_bar_regex { [a-z] } % Compile \c_bar_regex
\regex_const:Nn \c_foo_regex { \ur{c_bar_regex}+ ) } % Insert it with \ur{...}

奖金,对于不喜欢输出的人来说\regex_show:N

\regex_show:(N|n)也发生了变化,现在如果字符代码在可见的 ASCII 范围内,则会打印字符代码,然后打印实际字符(用括号括起来),因此现在\regex_show:n { (\w+)( \[ [a-z]+ \] ) }打印:

+-branch
  ,-group begin
  | Match, repeated 1 or more times, greedy
  |   range [97 (a),122 (z)]
  |   range [65 (A),90 (Z)]
  |   range [48 (0),57 (9)]
  |   char code 95 (_)
  `-group end
  ,-group begin
  | char code 91 ([)
  | range [97 (a),122 (z)], repeated 1 or more times, greedy
  | char code 93 (])
  `-group end.

答案3

目前已实现(在即将发布的版本中)\ur{l_my_regex}

\regex_set:Nn \l_tmpa_regex { a|b|c* }
\regex_set:Nn \l_tmpa_regex { (\ur{l_tmpa_regex}? z) }
\regex_show:N \l_tmpa_regex

显示以下内容(字符被明确显示,并且正则表达式插入到正确的位置)。请注意,\ur{l_tmpa_regex}被视为黑框,因此其行为与不同( a | b | c*?z ),其中,z只会出现在其中一个分支中。

> Compiled regex variable \l_tmpa_regex:
+-branch
  ,-group begin
  | ,-group begin (no capture)
  | | char code 97 (a)
  | +-branch
  | | char code 98 (b)
  | +-branch
  | | char code 99 (c), repeated 0 or more times, greedy
  | `-group end, repeated between 0 and 1 times, greedy
  | char code 122 (z)
  `-group end.

相关内容