符号函数:更改 catcodes 和 \tl_rescan:nn

符号函数:更改 catcodes 和 \tl_rescan:nn

目标是让用户输入(为了论证)一个变量的线性函数,例如,2*x+3连同变量(这并不重要)和变量的值。然后我想计算函数的值。下面的方法有效,但我现在认为它更多的是偶然起作用的。

  1. 我最初的尝试\findvalueB失败了,似乎不喜欢下划线,所以我尝试了\findvalueA其中一种有效的方法。为什么其中一个有效,而另一个无效?

  2. 当决定切换变量名进行测试时,我遇到了明显的问题(例如使用ybreaks \mytoks。因此我尝试对变量名进行硬编码,但再次失败。基本思想似乎可行,如下图所示。为什么这次尝试会失败?

大概我没有理解的地方有一个共同点,这可能是对某些事情的根本误解,我想纠正一下。我对解释比代码解决方案更感兴趣,尽管两者都很好。

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

\NewDocumentCommand {\findvalueA} { m m m }
    {
        \group_begin:
        \char_set_catcode_active:N #1
        \tl_rescan:nn{}{\def#1{#2}}
        \tl_rescan:nn{}{\mytoks={#3}}
        \fp_set:Nn \l_tmpa_fp {\the\mytoks}
        \fp_use:N \l_tmpa_fp
        \group_end:
    }

\NewDocumentCommand {\findvalueB} { m m m }
    {
        \group_begin:
        \char_set_catcode_active:N #1
        \tl_rescan:nn{}{\def#1{#2}}
        % underscores=bad
        \tl_rescan:nn{}{\tl_set:Nn \l_tmpa_tl {#3}}
        % or
        % \tl_rescan:nn{}{\fp_eval:n {#3}}
        \fp_use:N \l_tmpa_fp
        \group_end:
    }

    \NewDocumentCommand {\findvalueC} { m m }
    {
        \group_begin:
        \char_set_catcode_active:N x
        \tl_rescan:nn {} {\defx{#1}}
        \tl_rescan:nn {} {\mytoks={#2}}
        \fp_set:Nn \l_tmpa_fp {\the\mytoks}
        \fp_use:N \l_tmpa_fp
        \group_end:
    }

\ExplSyntaxOff
\begin{document}

% works
\findvalueA{x}{5}{2*x+3}

% fails
%\findvalueB{x}{5}{2*x+3}

% fails
%\findvalueC{5}{x+2}

The method of findvalueC works down here but not above: if $x=5$, then $x+2=$
\ExplSyntaxOn
\group_begin:
\char_set_catcode_active:N x
\defx{5}
\fp_eval:n {x+2}
\group_end:
\ExplSyntaxOff

\end{document}

答案1

方法 B 的失败很容易解释。重新扫描材料会用当前适用的类别代码读取它。当你使用 \findvalueB,您在文档中,因此_类别为“数学下标”,而不是“字母”(:稍后您也会发现这是错误的)。人们经常对此感到惊讶,因为他们认为重新扫描将使用定义命令时设置的代码。这是使用重新扫描相当棘手的一件事。

方法 C 的失败更难发现。这里发生的情况是,TeX\defx在重新扫描时“有帮助地”在后面插入了一个空格,就像它在改变标记化时在任何其他控制序列后插入空格一样(例如在\write或中\detokenize)。这发生在重新扫描“之前”,其中\defx将是一个标记。结果是在方法 C 中x定义为后面跟着一个必需的空格:没有空格,所以我们得到错误。这在方法 A 中不会发生,因为这里的空格插入在后面\def和前面#1(它们是重新扫描之前的单独标记)。因此这种方法可以工作。

这两个问题都表明了为什么许多 TeX 程序员不喜欢重新扫描:我倾向于避免它。我们在 LaTeX3 中提供了合理的包装器,但您的问题表明它仍然很难做到正确。您真正想要的是将所有变量字母替换为值。因此,我根本不会重新扫描,而是使用

\NewDocumentCommand { \findvalueD } { m m m }
  {
    \tl_set:Nn \l_tmpa_tl {#3}
    \tl_replace_all:Nnn \l_tmpa_tl {#1} {#2}
    \fp_eval:n { \l_tmpa_tl }
  }

它不存在任何这些问题。(我假设你没有例如sin在表达式中和字母n作为可能的变量:这种情况可以处理,但需要更多的思考!)

相关内容