平均能量损失

平均能量损失

如何测试提供给expl3函数的参数是否为整数表达式?如果不是,并且我调用任何整数相关函数,则会产生错误 ( ! Missing number, treated as zero.)。我想在生成 TeX 错误之前捕获此错误。

平均能量损失

\documentclass{article}
\usepackage{expl3}
\begin{document}

\ExplSyntaxOn

\prg_new_protected_conditional:Npnn \if_is_int:n #1 { T, F, TF }
  {
    \int_eval:n { #1 }
    :~
    \prg_return_true:
  }

\if_is_int:nTF { 1 }
  { Is~integer }
  { Is~not~integer }

\par

\if_is_int:nTF { \c_one + 1 }
  { Is~integer }
  { Is~not~integer }

\par

\if_is_int:nTF { String }
  { Is~integer }
  { Is~not~integer }

\ExplSyntaxOff

\end{document}

答案1

1 检查数字是否为整数

可能\int_eval并且\fp_eval将来会支持您要求的功能:

如果人们认为它有用,我可能会调整l3fp解析器以提供一个函数来“解析直到失败”并返回拆分标记列表、结果和真/假。它可以有int、、、变体。 — Bruno Le FlochdimskipfpGitHub

同时,您可以使用正则表达式检查正则整数。如果您想知道此正则表达式是否可以扩展以涵盖可以\int_eval消化的表达式,那么我不得不让您失望了。正则表达式不支持递归,并且如您所见,(1+(1+(1+1)))例如是递归的。

\documentclass{article}
\usepackage{expl3}
\begin{document}

\ExplSyntaxOn

\prg_new_protected_conditional:Npnn \if_is_int:n #1 { T, F, TF }
  {
    \regex_match:nnTF { ^[\+\-]?[\d]+$ } {#1} % $
      { \prg_return_true: }
      { \prg_return_false: }
  }

\if_is_int:nTF { 1 }
  { Is~integer }
  { Is~not~integer }

\par

\if_is_int:nTF { String }
  { Is~integer }
  { Is~not~integer }

\ExplSyntaxOff

\end{document}

输出结果没有什么意外。

在此处输入图片描述


2 检查表达式是否可以计算为整数

如果您想知道表达式是否会在 中解析\int_eval,则可以使用 LPEG(Lua 解析表达式语法)来解析数学表达式。 优点是它可以完全扩展。 另一方面,这完全没有意义,因为无论如何您都可以在 Lua 中评估表达式。

下面的三个片段是一个连续的文件,但除此之外,TeX.SX 不允许我混合语法突出显示。(感谢@Mico 的想法!)

\documentclass{article}
\pagestyle{empty}
\usepackage{expl3}
\usepackage{luacode}
\begin{document}

\begin{luacode*}
local lpeg = require"lpeg"
local P, R, S, V = lpeg.P, lpeg.R, lpeg.S, lpeg.V

local white = S(" \t") ^ 0

local integer  = white * R("09") ^ 1 * white
local exponent = white * P("^") * white
local muldiv   = white * S("/*") * white
local addsub   = white * S("+-") * white
local open     = white * P("(") * white
local close    = white * P(")") * white

local calculator = P({
  "input",
  input      = V("expression") * -1,
  expression = V("term") * (addsub * V("term"))^0,
  term       = V("primary") * (muldiv * V("primary"))^0,
  primary    = integer + ( open * V("expression") * close )
})

function check_integer(str)
   if calculator:match(str) then
      tex.print("\\prg_return_true:")
   else
      tex.print("\\prg_return_false:")
   end
end
\end{luacode*}

\ExplSyntaxOn

\prg_new_conditional:Npnn \if_is_int:n #1 { p, T, F, TF }
  {
    \lua_now_x:n { check_integer([[\lua_escape_x:n {#1}]]) }
  }

\if_is_int:nTF { 5*3+2 }      { Is~integer } { Is~not~integer }\par
\if_is_int:nTF { 3*(5-7^8) }  { Is~integer } { Is~not~integer }\par
\if_is_int:nTF { -(1) }       { Is~integer } { Is~not~integer }\par
\if_is_int:nTF { String }     { Is~integer } { Is~not~integer }\par

\ExplSyntaxOff

\end{document}

在此处输入图片描述


3 实现自己的整数计算器

既然我们已经超越了合理的范围,为什么不更进一步,用 Lua 实现一个完整的数学表达式解析器和求值器呢?

这个东西是基于我更完整的 Lua 浮点数数学解析器,可以在这里找到我的 GitHub 上的 Gist(这个有更多花哨的功能,并支持数学常数和函数)。

\documentclass{article}
\usepackage{expl3}
\usepackage{luacode}
\begin{document}

\begin{luacode*}
local lpeg = require"lpeg"
local C, P, R, S, V = lpeg.C, lpeg.P, lpeg.R, lpeg.S, lpeg.V

local white = S(" \t") ^ 0

local integer = white * C(R("09") ^ 1) * white / tonumber
local power   = white * C(P("^")) * white
local muldiv  = white * C(S("/*%")) * white
local addsub  = white * C(S("+-")) * white
local open    = white * P("(") * white
local close   = white * P(")") * white

-- Evaluate AST recursively
local function eval(t)
    if type(t) == "table" then
        if     t.op == "+" then return (t.left and eval(t.left) or 0) + eval(t.right)
        elseif t.op == "-" then return (t.left and eval(t.left) or 0) - eval(t.right)
        elseif t.op == "*" then return eval(t.left) * eval(t.right)
        -- Below is my poor excuse for missing integer division before Lua 5.3
        elseif t.op == "/" then return math.floor(eval(t.left) / eval(t.right))
        elseif t.op == "%" then return eval(t.left) % eval(t.right)
        elseif t.op == "^" then return eval(t.left) ^ eval(t.right)
        else error("Cannot happen") end
    elseif type(t) == "number" then
        return t
    else error("Cannot happen") end
end

-- Insert binary node into AST
local function binary(rule)
    local function recurse(left,op,right,...)
        if op then
            return recurse({ op = op, left = left, right = right },...)
        else
            return left
        end
    end
    return rule / recurse
end

-- Insert unary node into AST
local function unary(rule)
    return rule / function(op,right)
        return { op = op, right = right }
    end
end

local grammar = P({
        "input",
        input      = V("expression") * -1,
        expression = binary( V("term") * ( addsub * V("term") )^0 ),
        term       = binary( V("factor") * ( muldiv * V("factor"))^0 ),
        factor     = binary( V("primary") * ( power * V("factor"))^0 ),
        primary    = integer
            + open * V("expression") * close
            + unary( addsub * V("primary") )
})

int = {
    check = function(str)
        if grammar:match(str) then
            tex.print("\\prg_return_true:")
        else
            tex.print("\\prg_return_false:")
        end
    end,
    eval = function(str)
        local result = eval(assert(grammar:match(str)))
        tex.print(string.format("%d",result))
    end
}
\end{luacode*}

\ExplSyntaxOn

\prg_new_conditional:Npnn \if_lua_is_int:n #1 { p, T, F, TF }
  {
    \lua_now_x:n { int.check([[\lua_escape_x:n {#1}]]) }
  }

\cs_new:Npn \lua_int_eval:n #1
  {
    \lua_now_x:n { int.eval([[\lua_escape_x:n {#1}]]) }
  }

\if_lua_is_int:nTF { 5*3+2 }     { \lua_int_eval:n { 5*3+2 }     } { :( }\par
\if_lua_is_int:nTF { 3*(5-7^8) } { \lua_int_eval:n { 3*(5-7^8) } } { :( }\par
\if_lua_is_int:nTF { -(1) }      { \lua_int_eval:n { -(1) }      } { :( }\par
\if_lua_is_int:nTF { String }    { \lua_int_eval:n { String }    } { :( }\par

\ExplSyntaxOff

\end{document}

在此处输入图片描述

答案2

不久前我写了一个程序来做这件事。这是测试 \numexpr#1\relax 是否会用尽所有输入,例如,如果数字中间有空格,它将返回 false。

它没有捕获-(1)。另一件事是它不处理寄存器。如果你们还有其他违反直觉的情况(或返回错误答案的情况),那么我很乐意听听你们的意见。

该实现是一个有限状态机,带有一个额外的计数器来跟踪括号。最棘手的部分是正确处理宏的扩展。

\documentclass{article}
\usepackage{expl3}
\usepackage{xcolor}


\ExplSyntaxOn
\makeatletter
\let\@xp\expandafter
\let\@nx\noexpand
\newcount\ifintexpr@tempcount

% Test if single token input is a digit
\def\ifintexpr@ifdigit#1{
    \ifodd0
        \ifx#1 0 1 \else
        \ifx#1 1 1 \else
        \ifx#1 2 1 \else
        \ifx#1 3 1 \else
        \ifx#1 4 1 \else
        \ifx#1 5 1 \else
        \ifx#1 6 1 \else
        \ifx#1 7 1 \else
        \ifx#1 8 1 \else
        \ifx#1 9 1 \else
        \fi \fi \fi \fi \fi
        \fi \fi \fi \fi \fi
    \relax
        \@xp\@firstoftwo
    \else
        \@xp\@secondoftwo
    \fi
}

%%% ifintexpr
% #1 -- expression to test
% #2 -- true case
% #3 -- false dcase
% This tests true if \numexpr #1\relax throws no error and consumes all of #1 and false otherwise.
\def\ifintexpr#1{%
    \bgroup
    \ifintexpr@tempcount\z@
    \ifintexpr@{needsint}#1\ifintexpr@fexpsafenil%
    \@xp\egroup\next
}

\def\ifintexpr@fexpsafenil{\@nx\ifintexpr@fexpsafenil}

% We need to use \futurelet so that we can detect open braces even when they only surround one token like {1}
% also we use it to detect spaces. Store the state in \ifintexpr@state first.
\def\ifintexpr@#1{\def\ifintexpr@state{#1}\futurelet\testtok\ifintexpr@@}
\def\ifintexpr@@{%
    \ifx\testtok\bgroup%
        \let\next\ifintexpr@false
    \else
        \ifx\testtok\ifintexpr@fexpsafenil
            \@xp\let\@xp\next\csname sseq@ifintexpr@@\ifintexpr@state @done\endcsname
        \else
            % We need to check here for a space because \string<space> produces NO OUTPUT regardless of the catcode of the space.
            % This messes up \ifintexpr@@@ because it doesn't expect \string#1 to produce no characters.
            \@xp\ifx\space\testtok
                \def\next{\ifintexpr@next{\space}\@xp\next\romannumeral-`0}
            \else
                \let\next\ifintexpr@@@
            \fi
        \fi
    \fi
    \next
}

\def\ifintexpr@@@#1{
    \ifcat$\@xp\@gobble\string#1$%
        \@xp\ifintexpr@@@@\@xp#1
    \else
        % This is a macro, so fexpand it
        % Then use f expansion.
        \@xp\ifintexpr@@@fexpcs\@xp#1
    \fi
}

\def\ifintexpr@@@fexpcs{\exp_last_unbraced:Nf\ifintexpr@@@fexpcs@}
\def\ifintexpr@@@fexpcs@{\futurelet\testtok\ifintexpr@@@fexpcs@@}
\def\ifintexpr@@@fexpcs@@{
    \ifx\testtok\bgroup
        \@xp\ifintexpr@false % We already tested for groups above, so we need to check if this expanded to a group
    \else
        \@xp\ifintexpr@@@@ % If it's still a control sequence, then this will fail in the \@ifundefined step
    \fi
}



% We can't just use \futurelet because "\let\testtok(" makes \testtok unexpandable
% (I guess that makes sense, but why is it that I need \@xp\ifx\otherspace above if I've also \let\otherspace to a character? Mysterious...),
% so then "\csname hello\testtok\endcsname" is an error. This indexes into our state machine,
% cases: a digits, + or -, * or /, (, ), or something else (anything else always leads to false
\def\ifintexpr@@@@#1{%
    \ifx#1\ifintexpr@fexpsafenil
        \def\next{\csname sseq@ifintexpr@@\ifintexpr@state @done\endcsname\ifintexpr@fexpsafenil}%
    \else
        \ifintexpr@ifdigit{#1}%
            {\ifintexpr@next{digit}}%
            {%
                \ifx#1+%
                    \ifintexpr@next{+-}
                \else
                    \ifx#1-%
                        \ifintexpr@next{+-}
                    \else
                        \ifx#1*%
                            \ifintexpr@next{*/}
                        \else
                            \ifx#1/%
                                \ifintexpr@next{*/}
                            \else
                                % This extra \string here is so that if a control sequence fexpanded and still gave a control sequence,
                                % we don't get a missing \endcsname error here, it just returns false
                                \@ifundefined{sseq@ifintexpr@@\ifintexpr@state @\string#1}%
                                    {\let\next\ifintexpr@false}%
                                    {\ifintexpr@next{#1}}%
                            \fi
                        \fi
                    \fi
                \fi
            }%
    \fi
    \next
}

\def\ifintexpr@true#1\ifintexpr@fexpsafenil{\ifnum\ifintexpr@tempcount=\z@ \let\next\@firstoftwo\else\let\next\@secondoftwo\fi}
\def\ifintexpr@false#1\ifintexpr@fexpsafenil{\let\next\@secondoftwo}

\def\ifintexpr@makeifint#1#2#3{\@xp\def\csname sseq@ifintexpr@@#1@#2\endcsname{#3}}
\def\ifintexpr@next#1{\@xp\let\@xp\next\csname sseq@ifintexpr@@\ifintexpr@state @#1\endcsname}

\ifintexpr@makeifint{needsint}{done}{\ifintexpr@false}
\ifintexpr@makeifint{needsint}{digit}{\ifintexpr@{int}}
\ifintexpr@makeifint{needsint}{*/}{\ifintexpr@false}
\ifintexpr@makeifint{needsint}{+-}{\ifintexpr@{needsint}}
\ifintexpr@makeifint{needsint}{(}{\advance\ifintexpr@tempcount\@ne\ifintexpr@{needsint}}
\ifintexpr@makeifint{needsint}{)}{\ifintexpr@false}
\ifintexpr@makeifint{needsint}{\space}{\ifintexpr@{needsint}}

\ifintexpr@makeifint{int}{done}{\ifintexpr@true}
\ifintexpr@makeifint{int}{digit}{\ifintexpr@{int}}
\ifintexpr@makeifint{int}{*/}{\ifintexpr@{needsint}}
\ifintexpr@makeifint{int}{+-}{\ifintexpr@{needsint}}
\ifintexpr@makeifint{int}{(}{\ifintexpr@false}
\ifintexpr@makeifint{int}{)}{
    \advance\ifintexpr@tempcount\m@ne
    \ifnum\ifintexpr@tempcount<\z@\relax
        \@xp\@xp\@xp\ifintexpr@false\@xp\@gobble
    \else
        \@xp\ifintexpr@
    \fi{nointallowed}
}
\ifintexpr@makeifint{int}{\space}{\ifintexpr@{nointallowed}}

\ifintexpr@makeifint{nointallowed}{done}{\ifintexpr@true}
\ifintexpr@makeifint{nointallowed}{digit}{\ifintexpr@false}
\ifintexpr@makeifint{nointallowed}{*/}{\ifintexpr@{needsint}}
\ifintexpr@makeifint{nointallowed}{+-}{\ifintexpr@{needsint}}
\ifintexpr@makeifint{nointallowed}{(}{\ifintexpr@false}
\ifintexpr@makeifint{nointallowed}{)}{
    \advance\ifintexpr@tempcount\m@ne
    \ifnum\ifintexpr@tempcount<\z@
        \@xp\@xp\@xp\ifintexpr@false\@xp\@gobble
    \else
        \@xp\ifintexpr@
    \fi{nointallowed}
}
\ifintexpr@makeifint{nointallowed}{\space}{\ifintexpr@{nointallowed}}

\ExplSyntaxOff
\makeatother

\begin{document}
\def\testifintexpr#1{\bgroup\ifintexpr{#1}{\color{blue}}{\color{red}}\texttt{\detokenize{#1}}\egroup\par}

\testifintexpr{-(1)} % Passes but apparently shouldn't ?!
\testifintexpr{1+---+++2}  % Passes
\testifintexpr{1*+---+++2} % Fails
\testifintexpr{1 1+-2}     % Fail
\testifintexpr{1+(2*3-1}   % Fail
\testifintexpr{1+(2*3-1)}  % Pass
\testifintexpr{2**3}       % Fail
\newcount\test\texttt{\detokenize{\newcount\test}}\par
\testifintexpr{\test}      % Fail even though \numexpr would pass
\def\testb{2+}\texttt{\detokenize{\def\testb{2+}}}\par
\testifintexpr{\testb1}
\testifintexpr{\testb}
\end{document}

相关内容