(纯)TeX 中的十进制数解析器

(纯)TeX 中的十进制数解析器

我需要帮助编写一个仅使用 TeX 和纯 TeX 的十进制数解析器。可接受的输入集由正则表达式给出:

(+-)?(0-9)*(.,)?(0-9)+

除了排版符号、整数部分和小数部分之外,解析器不需要做任何其他事情。

编辑:

这个问题似乎需要澄清一下,所以就在这里。我正在尝试设计和实现一个宏(类似于\num),它将读取作为参数传递的数字并以自己的方式格式化。到目前为止,我已将此任务分解为以下步骤:

  1. 检查参数是否为有效数字,即是否与上面的正则表达式一致(这是我在 TeX 中很难做到的部分)。我特别想知道我们是否可以在宏参数中检测空格,例如“12 345”、“12 .3”、“12. 34”、“- 1”、“+ 1”,以及如何检测宏参数中的其他非数字,例如原始控制序列和字母(\num可以吗);

  2. “提取”符号、整数部分和小数部分,每个都在单独的宏中(一旦我知道如何实现步骤 1,我认为这很容易做到);

  3. 使用符号、整数和小数部分(已实现)来格式化和排版给定宏的数字。

所以,基本上我需要第 1 步的帮助。

编辑2:

在上面的正则表达式中,?表示前一组字符出现零次或一次,*表示零次或多次出现,+-- 表示一次或多次出现。

答案1

已编辑,可以无缝处理点和/或逗号作为小数点分隔符。

REEDITED 提供错误检查。错误处理程序通过预处理字符串并从中删除“数字有效”标记(任何数字、前导+-以及单个.,)来工作。如果处理后的字符串在此错误检查过程结束时不是空字符串,则意味着数字格式不正确并设置错误标志。

如果没有发现错误,则调用正常解析来从原始参数中依次提取前导+-、小数点前部分和小数点后部分。

\def\parsenum#1{%
\leavevmode\llap{#1} %REMOVE THIS ECHO LINE FOR GENERAL USE
  \errcheck{#1}%
  \if T\badchars \def\errcode{1}\def\psign{}\def\pint{}\def\pdec{}\else%
    \def\errcode{0}%
    \expandafter\parsedots#1,\relax%
    \expandafter\parsesign\pdots\relax\relax\relax%
  \fi%
}
\def\parsedots#1,#2\relax{\ifx\relax#2\relax\gdef\pdots{#1}\else%
  \parsedotshelper#1.#2\fi}
\def\parsedotshelper#1,{\gdef\pdots{#1}}
\def\parsesign#1#2\relax{%
  \if+#1\gdef\psign{#1}\parseint#2.\relax\relax\else%
    \if-#1\gdef\psign{#1}\parseint#2.\relax\relax\else%
      \gdef\psign{+}\parseint#1#2.\relax\relax%
    \fi%
  \fi%
}
\def\parseint#1.#2\relax{\gdef\pint{#1}\parsedec#2.\relax}
\def\parsedec#1.#2\relax{\gdef\pdec{#1}}

\def\errcheck#1{%
  \def\dwindling{}%
  \rmplus#1\relax\relax%
  \edef\tmp{\dwindling}\def\dwindling{}%
  \expandafter\rmminus\tmp\relax\relax%
  \edef\tmp{\dwindling}\def\dwindling{}%
  \expandafter\rmone\tmp\relax\relax%
  \edef\tmp{\dwindling}\def\dwindling{}%
  \expandafter\rmtwo\tmp\relax\relax%
  \edef\tmp{\dwindling}\def\dwindling{}%
  \expandafter\rmthree\tmp\relax\relax%
  \edef\tmp{\dwindling}\def\dwindling{}%
  \expandafter\rmfour\tmp\relax\relax%
  \edef\tmp{\dwindling}\def\dwindling{}%
  \expandafter\rmfive\tmp\relax\relax%
  \edef\tmp{\dwindling}\def\dwindling{}%
  \expandafter\rmsix\tmp\relax\relax%
  \edef\tmp{\dwindling}\def\dwindling{}%
  \expandafter\rmseven\tmp\relax\relax%
  \edef\tmp{\dwindling}\def\dwindling{}%
  \expandafter\rmeight\tmp\relax\relax%
  \edef\tmp{\dwindling}\def\dwindling{}%
  \expandafter\rmnine\tmp\relax\relax%
  \edef\tmp{\dwindling}\def\dwindling{}%
  \expandafter\rmzero\tmp\relax\relax%
  \edef\tmp{\dwindling}\def\dwindling{}%
  \expandafter\rmdot\tmp\relax\relax%
  \if F\dotfind%
    \edef\tmp{\dwindling}\def\dwindling{}%
    \expandafter\rmcomma\tmp\relax\relax%
  \fi
  \if\relax\dwindling\relax\gdef\badchars{F}\else\gdef\badchars{T}\fi%
}
\def\rmplus#1#2\relax{\if+#1\edef\dwindling{\dwindling#2}\else%
  \edef\dwindling{\dwindling#1#2}\fi%
}
\def\rmminus#1#2\relax{\if-#1\edef\dwindling{\dwindling#2}\else%
  \edef\dwindling{\dwindling#1#2}\fi%
}
\def\rmone#1#2\relax{\if1#1\else\edef\dwindling{\dwindling#1}\fi%
  \ifx\relax#2\relax\else\expandafter\rmone#2\relax\fi
}
\def\rmtwo#1#2\relax{\if2#1\else\edef\dwindling{\dwindling#1}\fi%
  \ifx\relax#2\relax\else\expandafter\rmtwo#2\relax\fi
}
\def\rmthree#1#2\relax{\if3#1\else\edef\dwindling{\dwindling#1}\fi%
  \ifx\relax#2\relax\else\expandafter\rmthree#2\relax\fi
}
\def\rmfour#1#2\relax{\if4#1\else\edef\dwindling{\dwindling#1}\fi%
  \ifx\relax#2\relax\else\expandafter\rmfour#2\relax\fi
}
\def\rmfive#1#2\relax{\if5#1\else\edef\dwindling{\dwindling#1}\fi%
  \ifx\relax#2\relax\else\expandafter\rmfive#2\relax\fi
}
\def\rmsix#1#2\relax{\if6#1\else\edef\dwindling{\dwindling#1}\fi%
  \ifx\relax#2\relax\else\expandafter\rmsix#2\relax\fi
}
\def\rmseven#1#2\relax{\if7#1\else\edef\dwindling{\dwindling#1}\fi%
  \ifx\relax#2\relax\else\expandafter\rmseven#2\relax\fi
}
\def\rmeight#1#2\relax{\if8#1\else\edef\dwindling{\dwindling#1}\fi%
  \ifx\relax#2\relax\else\expandafter\rmeight#2\relax\fi
}
\def\rmnine#1#2\relax{\if9#1\else\edef\dwindling{\dwindling#1}\fi%
  \ifx\relax#2\relax\else\expandafter\rmnine#2\relax\fi
}
\def\rmzero#1#2\relax{\if0#1\else\edef\dwindling{\dwindling#1}\fi%
  \ifx\relax#2\relax\else\expandafter\rmzero#2\relax\fi
}
\def\rmdot#1#2\relax{\if.#1\edef\dwindling{\dwindling#2}\def\dotfind{T}\else%
  \edef\dwindling{\dwindling#1}%
  \ifx\relax#2\relax\def\dotfind{F}\else\expandafter\rmdot#2\relax\fi\fi
}
\def\rmcomma#1#2\relax{\if,#1\edef\dwindling{\dwindling#2}\else%
  \edef\dwindling{\dwindling#1}%
  \ifx\relax#2\relax\else\expandafter\rmcomma#2\relax\fi\fi
}

\parsenum{123} [\psign][\pint][\pdec]:\errcode\par
\parsenum{+123.34} [\psign][\pint][\pdec]:\errcode\par
\parsenum{-123.} [\psign][\pint][\pdec]:\errcode\par
\def\x{-123}\parsenum{\x} [\psign][\pint][\pdec]:\errcode\par
\parsenum{+123,34} [\psign][\pint][\pdec]:\errcode\par
\parsenum{-123,} [\psign][\pint][\pdec]:\errcode\par
\def\x{-123,45}\parsenum{\x} [\psign][\pint][\pdec]:\errcode\par
\parsenum{-123,A} [\psign][\pint][\pdec]:\errcode\par
\parsenum{X-123,} [\psign][\pint][\pdec]:\errcode\par
\parsenum{12+3,} [\psign][\pint][\pdec]:\errcode\par
\parsenum{-12-3,} [\psign][\pint][\pdec]:\errcode\par
\parsenum{-123,2,5} [\psign][\pint][\pdec]:\errcode\par
\parsenum{-123,2.5} [\psign][\pint][\pdec]:\errcode\par
\bye

在此处输入图片描述

答案2

此版本不可扩展,但不需要任何终止符,它只会吞噬数字的末尾。它仅限于适合 tex 计数寄存器的整数。

在此处输入图片描述

\newcount\ca
\newcount\cb
\def\p#1{%
\def\digitone{}%
\ifx+#1\def\sign{#1}\else
\ifx-#1\def\sign{#1}\else
\def\sign{}\def\digitone{#1}\fi\fi
\afterassignment\n\ca=1\digitone}

\def\n#1{%
\def\digitone{}%
\ifx,#1\def\point{#1}\else
\ifx.#1\def\point{.}\else
\def\point{}\def\digitone{#1}\fi\fi
\afterassignment\nn\cb=1\digitone}

\def\gobble#1{}

\def\nn{\par
[sign=\sign]
[n1=\expandafter\gobble\the\ca]
[point=\point]
[n2=\expandafter\gobble\the\cb]
\par}


\p 123 hello \p456,99 hafa \p ggg \p-12,88

\bye

答案3

(v2)中的代码siunitx使用循环遍历输入中的每个标记,然后将其与各种可能的“数字部分”列表进行比较。由于该过程无法对标记的性质做出假设,并且它涵盖了各种输入形式,因此它相当复杂。我目前正在重写所有内容,使其更高效、更清晰,但需要处理一系列输入意味着它仍然不简单。

使用此处给出的更受限制的输入形式,可以对许多概念进行硬编码以生成更快的解析器。例如,下面使用一个简单的逐个标记循环遍历输入,其中知道第一个标记可以是 或+-其他标记可以是 ,并且允许在整数部分和小数部分之间换行,01234567890且恰好有一个.或。,

\catcode`\@=11 %
\def\num#1{%
  \begingroup
    \edef\num@temp{#1}%
    \def\num@int@part{}%
    \def\num@dec@part{}%
    \ifx\num@temp\empty
    \else
      \expandafter\num@first\num@temp\num@stop 
    \fi
    \ifx\num@int@part\empty
      \ifx\num@dec@part\empty
        \ERROR
      \fi
    \fi
    $
      \num@sign@part
      \ifx\num@int@part\empty
        0%
      \else
        \num@int@part
      \fi
      \ifx\num@dec@part\empty
      \else
        .\num@dec@part
      \fi  
    $%
  \endgroup
}
\def\num@first#1#2\num@stop{%
  \ifnum
    \ifx#1+ 1\fi
    \ifx#1- 1\fi
    0>0 %
    \def\num@sign@part{#1}%
    \expandafter\num@first@auxi
  \else
    \def\num@sign@part{}%
    \expandafter\num@first@auxii
  \fi
    {#1}{#2}%
}
\def\num@first@auxi#1#2{\num@int#2\num@stop}
\def\num@first@auxii#1#2{\num@int#1#2\num@stop}
\def\num@int#1{%
  \ifx\num@stop#1%
  \else
    \ifnum
      \ifx#1. 1\fi
      \ifx#1, 1\fi
      0>0 %
      \expandafter\expandafter\expandafter\num@dec
    \else
      \num@digit@check#1\num@int@part
      \expandafter\expandafter\expandafter\num@int
    \fi
  \fi
}
\def\num@dec#1{%
  \ifx\num@stop#1%
  \else
    \num@digit@check#1\num@dec@part
    \expandafter\num@dec
  \fi
}

\def\num@digit@check#1#2{%
  \begingroup
    \def\num@digit@check@aux##1#1{}%
    \toks0\expandafter{\num@digit@check@aux0123456789{}{}#1}%
    \edef\num@digit@check@aux{\the\toks0}%
  \expandafter\endgroup
  \ifx\num@digit@check@aux\empty
    \def#2{}%
    \ERROR
  \else
    \edef#2{#2#1}%
  \fi
}
\def\num@stop{\num@stop}
\long\def\gobble#1{}
\num{1234.56789010}
\bye

我使用了一个简单的错误陷阱:未定义的控制序列。真正的版本当然需要在这里更详细一些。

参数中的空格很尴尬。siunitx我直接忽略它们,这很容易,因为普通的基于标记的映射将在 TeX 级别完成。如果您真的想在这种情况下检查空格,这是可行的,但更繁琐,我不确定是否值得付出努力。(通常输入应该是“合理的”。)

在我的修订中,siunitx我将采取更像Steven B. Segletes 的回答对我来说,这很有意义,因为我可以划分具有不同限制的部分,并且只需要测试的一个子集。由于这里的数字比较简单,这种划分可能节省不了多少,所以我选择了单一映射。


检查数字的另一种方法是使用 TeX,它限制了计数寄存器的范围,但避免了需要循环遍历所有内容。例如,这可能是

\catcode`\@=11 %
\def\num#1{%
  \begingroup
    \edef\num@temp{#1}%
    \def\num@int@part{}%
    \def\num@dec@part{}%
    \ifx\num@temp\empty
    \else
      \expandafter\num@first\num@temp\num@stop 
    \fi
    \ifx\num@int@part\empty
      \ifx\num@dec@part\empty
        \ERROR
      \fi
    \fi
    $
      \num@sign@part
      \ifx\num@int@part\empty
        0%
      \else
        \num@int@part
      \fi
      \ifx\num@dec@part\empty
      \else
        .\num@dec@part
      \fi  
    $%
  \endgroup
}
\def\num@first#1#2\num@stop{%
  \ifnum
    \ifx#1+ 1\fi
    \ifx#1- 1\fi
    0>0 %
    \def\num@sign@part{#1}%
    \expandafter\num@first@auxi
  \else
    \def\num@sign@part{}%
    \expandafter\num@first@auxii
  \fi
    {#1}{#2}%
}
\def\num@first@auxi#1#2{\num@int{#2}}
\def\num@first@auxii#1#2{\num@int{#1#2}}
\def\num@int#1{%
  \ifx\relax#1\relax
    \expandafter\gobble
  \else
    \expandafter\num@int@auxi
  \fi
    {#1}%
}
\def\num@int@auxi#1{\num@int@auxii#1\num@stop}
\def\num@int@auxii#1#2\num@stop{%
  \ifnum
    \ifx#1. 1\fi
    \ifx#1, 1\fi
    0>0 %
      \num@dec{#1#2}%
  \else
    \num@digit@check{#1#2}\num@int@part\num@dec
  \fi
}
\def\num@dec#1{%
  \ifx\relax#1\relax
  \else
    \num@dec@auxi#1\num@stop
  \fi
}
\def\num@dec@auxi#1#2\num@stop{%
   \num@dec@auxii{#2}%
}
\def\num@dec@auxii#1{%
  \ifx\relax#1\relax
  \else
    \num@dec@auxiii#1\num@stop
  \fi
}
\def\num@dec@auxiii#1#2\num@stop{%
  \ifnum
    \ifx#1+ 1\fi
    \ifx#1- 1\fi
    0>0 %
    \ERROR
  \else
    \num@digit@check{#1#2}\num@dec@part\num@end
  \fi
}
\def\num@end#1{%
  \ifx\relax#1\relax\else\ERROR\fi
}
\def\num@digit@check#1#2#3{%
  \begingroup
    \def\num@digit@check@aux##1\relax{%
      \edef\num@digit@check@aux{%
        \def\noexpand#2{\the\count0 }%
        \noexpand#3{##1}%
      }%
    }%
    \afterassignment\num@digit@check@aux\count0=#1\relax
  \expandafter\endgroup
  \num@digit@check@aux
}

\def\num@stop{\num@stop}
\long\def\gobble#1{}

\num{1234.56789010}
\bye

再说一次,我并没有禁止所有可能的空间:虽然可行但很乏味,所以我宁愿不这样做!

相关内容