[我鼓励你看看 Phelype 令人印象深刻的方法]
我听说过一些宏会执行诸如“展开直到遇到不可展开的标记”之类的操作,这个问题可能(也可能不)与这种事情和技巧有关。特别是,我在考虑诸如遇到标记时终止之\romannumeral
类的事情的行为。但是,如果到达组的末尾(显然也到达组的开头),它们也会在没有标记的情况下终止。因此\numexpr
\relax
\relax
\the\numexpr 1+1+1\relax
和
{\the\numexpr 1+1+1}
都有效。
我对的递归版本很感兴趣\numexpr
,称之为\rnumexpr
,它将扩展其参数中的组,并使用先前分组的数据继续计算。
这就是它,它似乎工作得很好。它依赖于标记化的一个特性,即如果将组作为参数传递,则分组将被剥离,组的内容将成为实际参数。
然而,根据我的编码,它需要一个明确的终止符(在本例中为\rrelax
)。
已编辑,可处理最多 8 个嵌套级别(即 8 个连续的左括号),但仍然无法处理隐式分隔符
\documentclass{article}
\makeatletter
\let\@relax\relax
% CAN HANDLE 8 SUCCESSIVE LEFT BRACES
\def\rnumexpr#1\rrelax{\numexpr\@rnumexpr
\@empty\@empty\@empty\@empty\@empty\@empty\@empty\@empty\@empty
#1\relax \@empty\@empty\@empty\@empty\@empty\@empty\@relax}
\def\@rnumexpr#1#2#3#4#5#6#7#8#9\@relax{%
#1\ifx\relax#2\relax\else\@rnumexpr#2#3#4#5#6#7#8#9\@relax\fi}
\makeatother
\begin{document}
\the\numexpr+1+1+1+1+1\relax,
\the\numexpr+1+1{+1+1+1}\relax,
\the\numexpr+1+1{+1{+1+1}}\relax
\the\rnumexpr+1+1+1+1+1\rrelax,
\the\rnumexpr+1+1{+1+1+1}\rrelax,
\the\rnumexpr+1+1{+1{+1+1}}\rrelax,
Expandable! \edef\z{\the\rnumexpr+1+1{+1{+1+1}}\rrelax}\z
\the\rnumexpr+1+1+1+1+1\rrelax,
\the\rnumexpr+1+1{+1+1+1}\rrelax,
\the\rnumexpr+1+1{+1{+1+1}}\rrelax,
\the\rnumexpr{+1{+1{+1{+1{+1{+1{+1{+1{+1{+1}}}}}}}}}}+1\rrelax,
Can handle up to 8 successive left braces:
\the\rnumexpr{+1{{{{{{{{+1}+1}+1}+1}+1}+1}+1}+1}+1}+1\rrelax{},
\the\rnumexpr{+1{{{{{{{{+1}}}}}}}}}+1\rrelax{},
\the\rnumexpr{{{{{{{{+1}}}}}}}}\rrelax{}
{\the\numexpr1+1+1} numexpr uses implicit delimiter
%{\the\rnumexpr1+1+1}
but rnumexpr won't work...EXPLICIT DELIMITER EXPECTED
\end{document}
\numexpr
前两行比较了和的结果\rnumexpr
,显示了\numexpr
当它到达 begin-group 时似乎停止了,而\rnumexpr
提取它并继续计算。它甚至显示是可扩展的!
第三行和第四行的\rnumexpr
测试更加严格。Phelype 指出,我最初的要求对于它可以处理的嵌套层数非常有限。这种修改后的方法可以处理更多的嵌套层数(最多 8 个连续的左括号),但仍然有有限的限制。
输出的第 5 行显示了如何\numexpr
在没有显式 的情况下终止\relax
。尝试使用这样的语法\rnumexpr
不起作用,因为我已对其进行了编码以期望显式分隔符。
有没有办法重新定义\rnumexpr
为在到达组末尾时也结束,而不是显式终止符(同时不是到达组起点时结束)
注意:本文的目的不是开发一种嵌套计算的逻辑方法。虽然在某些应用中这可能是可取的,但这并不是本文要尝试的。因此,建议使用括号而不是带括号的子单元的方法并不能解决我的担忧。
正如我回答 David 时所说,我真正感兴趣的过程是计算任意参数中的某些“合格”标记。例如,使用我解决这个较大问题的方法,我会忽略“不合格”标记,但当我遇到“合格”标记时,我会+1
在输出宏中放置一个。但是,我开发的过程还保留了输出宏中原始参数的分组。
因此,当我逐个检查完参数(保留分组)后,输出将包含+1
参数原始分组结构中的任意数量的标记。我希望用 来操作这个输出宏\rnumexpr
。由于我正在编写代码,因此我始终可以确保在末尾添加\rrelax
,但这个问题更多地与我想知道是否有可能在没有结束分隔符的情况下重写有关\rnumexpr
。
答案1
我为你制作了一个可扩展的版本,\rnumexpr
它不需要分隔符,并且会在第一个不可扩展\numexpr
的无效标记处停止。它尝试在某种程度上模拟行为\numexpr
,并忽略括号对。
关于\numexpr
,每个人都已经评论过,它是原始的,所以它的规则与人类处理简单宏的规则不同。不幸的是,有些事情如果没有原始支持就无法完成。
您需要可扩展性,因此您不能立即使用前瞻(使用\futurelet
)。\futurelet
这将允许您查看下一个标记并决定如何处理它。可扩展性限制您抓取标记作为参数并以有趣的方式传递它们,而抓取内容作为参数(使用开放式命令,如\rnumexpr
)意味着:
{\rnumexpr 1+1}
是不可能的,因为 TeX 抓取时会对你大喊大叫}
\rnumexpr 1+1 ⟨something else⟩
最终会抓住⟨something else⟩
,无论它是什么,确定它是否必须扩展,并进行相应的处理。
使用分隔参数,您可以使用类似expl3
's之类的东西\__tl_act:NNNnn
来扩展循环遍历标记列表并对项目采取不同的操作,具体取决于它是空格、分组标记列表还是另一个单个标记,这将使手头的任务变得容易得多。
首先,让我指出一些有关您的代码的问题。在对空性的测试中\expandafter\ifx\relax#2\relax
,\expandafter
跳过\ifx
并扩展\relax
,因此它没有多大用处,可以删除。此外,如果输入包含,此测试可能会打印不需要的字符\relax
。当然,您正处于中间\numexpr
,所以这只是吹毛求疵。
此外,您的条件不会在每次迭代时结束\@rnumexpr
,而只会在结束时结束\numexpr
。对于大型表达式(我说的大型是指足够多的副本以+1
获得大于 1500 的结果) ,这将非常大)会用尽 TeX 的所有输入堆栈。最后,您的定义不适用于\rnumexpr{+1{+1}}+1\rrelax
和其他(太奇怪而不能被视为正常输入)括号组合。
我定义了一个缓慢的、肯定不是最优的、可能过于复杂、最有可能有缺陷的、⟨在此处插入其他限定符⟩ 的模拟\numexpr
。大部分行为是相同的(就我所做的测试而言),除了它忽略了括号。
它开始逐个标记地扫描输入,然后决定如何处理每个标记。它会尝试在扫描过程中扩展标记,并在第一个不可扩展的\numexpr
-invalid 标记上停止。如果该标记是\relax
,则会像 一样被消耗\numexpr
,因此在这方面的行为非常相似。
主要区别在于,由于它将标记作为未分隔的参数抓取,因此空格会被忽略,因此虽然 的结果\the\numexpr 1+1 1
是21
(2
附加一个1
), 的结果\the\rnumexpr 1+1 1
是12
( 1+11
),因此它需要一个比 更“难”的结尾标记\numexpr
。可以通过使用\relax
:\the\rnumexpr 1+1\relax 1
来结束\rnumexpr
或通过使用 来避免这种情况\obeyspaces
,这样空格就会被发送到底层\numexpr
,然后底层会做正确的事情。
这里是:
\documentclass{article}
\makeatletter
\def\rnumexpr{\romannumeral-`0\rn@collect{}}
\long\def\rn@collect#1#2{%
\rn@ifsinglechar{#2}%
{%
\rn@ifvalid@numexpr@token{#2}%
{\rn@collect{#1#2}}%
{\rn@finish{#1}{#2}}%
}%
{%
\rn@ifsingletoken{#2}%
{%
\rn@ifrelax{#2}%
{\rn@finish{#1}{}}%
{\rn@expand@after{#1}#2}%
}%
{\rn@collect{#1}#2}%
}%
}
\def\rn@qrtail{\rn@qrtail}
\def\rn@expand@after#1{%
\rn@@expand@after{\expandafter\rnumexpr}#1\rn@qrtail\rn@qrstop}
\def\rn@@expand@after#1#2{%
\ifx#2\rn@qrtail
\rn@finish@expandafter{#1}%
\else
\expandafter\rn@@expand@after
\fi
{#1\expandafter#2}%
}
\def\rn@finish@expandafter#1#2\fi#3\rn@qrstop{%
\fi#1\romannumeral-`0\rn@check@unexpandable}
\long\def\rn@check@unexpandable#1{%
\expandafter\rn@@check@unexpandable\expandafter#1%
\romannumeral-`0#1}
\long\def\rn@@check@unexpandable#1#2{%
\ifx#1#2%
\expandafter\rn@unexpandable
\else
\expandafter\rn@expandable
\fi
{#1}{#2}}
\long\def\rn@expandable#1#2{#2}
\long\def\rn@unexpandable#1#2{\relax#2}
\long\def\rn@finish#1#2{%
\numexpr#1\relax#2}
\long\def\rn@ifrelax#1{%
\ifx#1\relax
\expandafter\@firstoftwo
\else
\expandafter\@secondoftwo
\fi
}
\def\rn@ifvalid@numexpr@token#1{%
\expandafter\rn@@ifvalid@numexpr@token\expandafter{\number`#1}}
\def\rn@@ifvalid@numexpr@token#1{%
\if
\ifnum58>#1 1\else x\fi
\ifnum #1>39 1\else y\fi
\ifnum
\ifnum#1=44 1\else 0\fi
\ifnum#1=46 1\else 0\fi
=0
\rn@true
\else
\rn@false
\fi
\else
\ifnum#1=32
\rn@true
\else
\rn@false
\fi
\fi
}
\def\rn@true{\expandafter\@firstoftwo\romannumeral-`0}
\def\rn@false{\expandafter\@secondoftwo\romannumeral-`0}
\edef\rn@catofamp{\the\catcode`\&}
\catcode`\&=11
\long\def\rn@gobble#1&{%
\romannumeral-`0\rn@@gobble#1\rn@qrtail &}
\long\def\rn@@gobble#1#2&{%
\ifx\rn@qrtail#1%
\expandafter\rn@@gobble@end
\else
\expandafter\rn@de@tail
\fi#2}
\def\rn@@gobble@end{ }
\long\def\rn@de@tail#1\rn@qrtail{ #1}
\long\def\rn@ifsinglechar#1{%
\rn@ifempty{#1}%
{\@secondoftwo}%
{%
\if\relax\expandafter\rn@gobble\detokenize{#1}&\relax
\expandafter\@firstoftwo
\else
\expandafter\@secondoftwo
\fi
}%
}
\long\def\rn@ifsingletoken#1{%
\rn@ifempty{#1}%
{\@secondoftwo}%
{%
\rn@if@head@is@group{#1}%
{\@secondoftwo}%
{%
\if\relax\detokenize\expandafter\expandafter
\expandafter{\rn@gobble#1&}\relax
\expandafter\@firstoftwo
\else
\expandafter\@secondoftwo
\fi
}%
}%
}
\long\def\rn@if@head@is@group#1{%
\ifcat\expandafter\@gobble\expandafter{\expandafter{\string#1?}}**%
\expandafter\@secondoftwo
\else
\expandafter\@firstoftwo
\fi
}
\catcode`\&=\rn@catofamp
\long\def\rn@ifempty#1{%
\if\relax\detokenize{#1}\relax
\expandafter\@firstoftwo
\else
\expandafter\@secondoftwo
\fi
}
\makeatother
\begin{document}
\def\twop{+1+1}
\the\numexpr 1+1 1
\the\rnumexpr 1+1 1
\the\numexpr\twop+1+1+1
\the\numexpr\twop+1+1+1
\the\numexpr\twop+1+1+1
\the\numexpr\twop+1+1+1+1+1
\the\numexpr\twop+1+1+1+1+1
\the\numexpr 1+1
\the\numexpr 1+1\twop
\def\twop{{+1+1}}
\the\rnumexpr\twop+1{+1+1}\relax
\the\rnumexpr\twop{+1+1+1}\relax
\the\rnumexpr\twop{+1{+1+1}}\relax
\the\rnumexpr\twop{+1{+1+1}}+1+1\relax
\the\rnumexpr\twop{+1{+1+1{}}}+1+1\relax
\the\rnumexpr 1+1
\the\rnumexpr 1+1\twop
Expandable! \edef\z{\the\rnumexpr+1+1{+1+1}\relax}\texttt{\meaning\z}
\the\rnumexpr1{{+1}+1{+1}}+1\relax
\the\rnumexpr{1{+1}}+1\relax
{\the\numexpr1+1+1}
Groups everywhere:
\the\rnumexpr{+1{+1{+1{+1{+1{+1{+1{+1{+1{+1}}}}}}}}}}+1,
\the\rnumexpr{+1{{{{{{{{+1}+1}+1}+1}+1}+1}+1}+1}+1}+1,
\the\rnumexpr{+1{{{{{{{{+1}}}}}}}}}+1,
\the\rnumexpr{{{{{{{{{{{{{{{{{{{{{{{{{{+1}}}}}}}}}}}}}}}}}}}}}}}}}}
No leftover:
\detokenize\expandafter{\the\rnumexpr{+1{{{{{{{{+1}}}}}}}}}+1\relax}
% {\the\rnumexpr1+1+1} STILL WON'T WORK :(
\end{document}
如果事先用 来评估表达式\the\numexpr0
,而不是抓取每个标记,然后在最后才评估它们,那么宏的速度会快得多。然而,这会破坏宏的“稳定性”(如果你可以这么称呼它),因为每次评估(有多少组就有多少个)\relax
都会消耗 a,所以要正确终止宏,你需要求助于 之类的东西\the\rnumexpr1{+1{+1{+1}}}\relax\relax\relax\relax
,所以我选择了放弃这种可能性。
答案2
当发现\numexpr
无法出现在 a 中的某个东西(不可扩展)时, 的输入结束。请注意, 会触发扩展,直到输入按照之前的定义终止。\numexpr
\numexpr
如果表示整数表达式结束的标记是\relax
,它将被完全删除;因此,如果您说
\edef\test{\the\numexpr1+1\relax}
其将扩展为2
。
整数表达式中不允许使用括号,除非它们用于分隔在扫描整数表达式时扩展的宏的参数。所以
\def\addition#1#2{#1+#2}
\numexpr\addition{1}{2}\relax
将计算为3
。但是这\numexpr 1+{1+1}\relax
是非法的,因为{
停止扫描并且+
缺少第一个操作数。
您可以使用(
和)
来限定要按照通常的优先规则进行评估的子表达式:\numexpr2*(1+3)\relax
评估结果为 8。