在 LaTeX3 中,哪种数据类型最适合存储、解析和输出自由用户输入?

在 LaTeX3 中,哪种数据类型最适合存储、解析和输出自由用户输入?

我正在开发一个小型解析器,它接受免费的用户输入并将某些输入解释为命令。例如,解析器将其解释+\oplus[为“以括号作为分隔符启动预配置数组”。解析器最终将能够方便地插入语言学中使用的某种数据结构(称为 AVM),而 CTAN 上目前没有这种数据结构的软件包。

解析器目前基于循环遍历输入标记列表 (wit \tl_map_inline:nn)。但循环遍历用户输入中的空格和控制序列让我头疼。例如,用户输入可能包含:

Hello World
\textit{Hello World}

由于\tl_map_inline:nn循环遍历标记列表的项目,输出将变成“HelloWorld”和“H“elloWorld”。

当然,受保护的输入

Hello{~}World
{\textit{Hello World}}

将给出所需的结果,但用户不太可能以这种方式输入。此外,上面的两个输入确实完全不同:Hello{~}World是一个包含 11 个项目的标记列表,但{\textit{Hello World}}只有 1 个。在映射中,我想将命令的内容解析\textit为单个字母,而不是单个标记(因为它的参数可能包含解析器应该敏感的字符,就像+上面提到的那样)。

这让我想到,也许有比使用标记列表和映射更好的方法来实现解析器。如果标记列表是最好的方法,那么有哪些方法可以:

  1. 准备用户输入,以便将空格保留为项目?(也许用 替换它们~,但是怎么做呢?)
  2. 正确地将控制序列转发到输出并能够访问其参数所组成的标记?

这个答案,@egreg 建议将用户输入作为序列接收,在每个空格处将其拆分为标记列表,然后解析标记列表(在每个 tl 后向输出添加一个空格)。这种方法可以应用于示例中的命令吗\textit

(物品和代币之间的差异真的是问题的核心吗?)


这是我的方法的基本框架(实际还包含一个模式开关,以便用户可以禁用替换,例如,[以便仍然可以在解析器范围内输入带有可选参数的命令):

\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn

\NewDocumentCommand{\parse}{+m}{    
    \avm_parse:n { #1 }
}
\tl_new:N \l_avm_output_tl
\cs_new:Nn \avm_parse:n {
    \tl_clear:N \l_avm_output_tl
    \tl_map_inline:nn {#1} {
        \tl_put_right:Nn \l_avm_output_tl {##1}
    }
        \tl_use:N \l_avm_output_tl
}
\ExplSyntaxOff  
\begin{document}
    \noindent
    \parse{Hello World}\\
    \parse{\textit{Hello World}}\\
    \parse{Hello{~}World}\\ 
    \parse{{\textit{Hello{~}World}}}
\end{document}

答案1

可以解析整个标记列表,同时保留空格和组,但这比“正常”\tl_map_inline:nn解析要费力得多(请参阅这里以获得更完整的解释)。问题是\tl_map_inline:nn,正如其文档中interface3所述,代币,但在项目XX,并删除外括号。 、X XX{X}和全部{XX}{XX}都是两个项目的列表(在最后一个中,每个项目都有两个标记)。 这种空格和括号删除发生在 TeX 的低级别,因为当它抓取一个参数时, 和 全部都会\next X\next{X}抓取\next <spaces>XX参数:这就是定义物品

迭代适当的代币,您需要特别检查空格和括号的情况。expl3有三个条件函数,\tl_if_head_is_space:n(TF)用于检查参数标记列表中的第一个标记是否为空格、\tl_if_head_is_group:n(TF)标记分组列表的情况以及所有其他情况。 这允许您有条件地处理参数标记列表中的空格和组,从而在处理过程中保留它们。 's (以前的)、、 和其他一些\tl_if_head_is_N_type:n(TF)方法也使用此方法。expl3\text_uppercase:n\tl_upper_case:n\tl_count_tokens:n\tl_reverse:n

这里我定义了一个\avm_parse:n函数,让它迭代参数的每个标记,如果标记是 -type N,它会检查当前标记是 a+还是 a [(使用),并调用相应的函数来处理每种情况。您可以更改和\str_case:nnF的定义以执行您期望的操作(我制作了produce和produce (还解析了with )。\__avm_special_plus:\__avm_special_lbrack:w+\ensuremath{\oplus}[<tokens>]\textbf{<tokens>}<tokens>\avm_parse:n

代码如下:

\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
\NewExpandableDocumentCommand {\parse} {+m}
  { \avm_parse:n {#1} }
\cs_new:Npn \avm_parse:n #1
  { \exp_args:No \exp_not:o { \__avm_parse:n {#1} } }
\cs_new:Npn \__avm_parse:n #1
  {
    \exp:w
      \group_align_safe_begin:
        \__avm_parse_loop:w #1
          \q_recursion_tail \q_recursion_stop
        \__avm_result:n { }
  }
\cs_new:Npn \__avm_end:w \__avm_result:n #1
  {
      \group_align_safe_end:
    \exp_end:
    #1
  }
\cs_new:Npn \__avm_parse_loop:w #1 \q_recursion_stop
  {
    \tl_if_head_is_N_type:nTF {#1}
      { \__avm_N_type:N }
      {
        \tl_if_head_is_group:nTF {#1}
          { \__avm_group:nw }
          { \__avm_space:w }
      }
    #1 \q_recursion_stop
  }
\cs_new:Npn \__avm_N_type:N #1
  {
    \quark_if_recursion_tail_stop_do:Nn #1
      { \__avm_end:w }
    \__avm_parse_specials:N #1
  }
\cs_new:Npn \__avm_parse_specials:N #1
  {
    \str_case:nnF {#1}
      {
        { + }{ \__avm_special_plus: }
        { [ }{ \__avm_special_lbrack:w }
      }
      { \__avm_non_special:N #1 }
  }
\cs_new:Npn \__avm_group:nw #1
  { \exp_args:NNo \exp_args:No \__avm_group:n { \__avm_parse:n {#1} } }
\cs_new:Npn \__avm_group:n #1 { \__avm_add_result:nw { {#1} } }
\exp_last_unbraced:NNo
\cs_new:Npn \__avm_space:w \c_space_tl { \__avm_add_result:nw { ~ } }
\cs_new:Npn \__avm_add_result:nw #1 #2 \q_recursion_stop \__avm_result:n #3
  { \__avm_parse_loop:w #2 \q_recursion_stop \__avm_result:n {#3 #1} }
\cs_new:Npn \__avm_non_special:N #1 { \__avm_add_result:nw {#1} }
%
\cs_new:Npn \__avm_special_plus:
  { \__avm_add_result:nw { \ensuremath { \oplus } } }
\cs_new:Npn \__avm_special_lbrack:w #1 ]
  {
    \exp_args:No \__avm_add_result:nw
      { \exp:w \exp_args:NNf \exp_end: \textbf { \avm_parse:n {#1} } }
  }
\ExplSyntaxOff
\begin{document}
\noindent
\parse{Hello World}\\
\parse{\textit{Hello World}}\\
\parse{Hello{~}World}\\
\parse{{\textit{Hello{~}World}}}\\
\parse{Hello [World]}\\
\parse{\textit{Hell[o W]orld}}\\
\parse{Hell[o{~+}W]orld}\\
\end{document}

示例的输出如下:

在此处输入图片描述

答案2

我发现递归方法是可以理解的,但比函数系列更难理解analysis保存的字符码并不容易{}

备注:在简单情况下,如果正则表达式替换就足够了,就使用它。而且它更快。

替代方法包括

\tl_analysis_map_inline 不用多说,\f\a使用的示例在实际代码中不要重新定义重要的 LaTeX 宏,例如等等

\ExplSyntaxOn
\def \f #1 {
    \tl_build_begin:N \a
    \tl_analysis_map_inline:nn {#1} {
        \int_compare:nNnTF {##2} = {`[} {
            \tl_build_put_right:Nn \a {
                \iftrue \noexpand\textbf{ \else } \fi  % this will x-expand to '\textbf{'
            }
        } {
            \int_compare:nNnTF {##2} = {`]} {
                \tl_build_put_right:Nn \a {
                    \iffalse { \else } \fi  % this will x-expand to '}'
                }
            } {
                \int_compare:nNnTF {##2} = {`+} {
                    \tl_build_put_right:Nn \a { $\noexpand \oplus$ }
                } {
                    \tl_build_put_right:Nn \a {##1}
                }
            }
        }
    }
    \tl_build_end:N \a
    \tl_set:Nx \a {\a}
}

\ExplSyntaxOff
\f{a [Hello + world] b}
\a

答案中还演示了构建部分不平衡中间标记列表的方法。(请参阅我的其他答案中的解释这里

输出为输出图像,处理后的token列表内容为代币列表内容

然而,这种方法的缺点是,必须手动计算左括号和右括号的数量

  • 抓取一件物品,或者
  • 仅在顶层操作。

(虽然tokcycle包可以自动执行此操作,但它会忽略 的字符码{}

相关内容