我需要帮助编写一个仅使用 TeX 和纯 TeX 的十进制数解析器。可接受的输入集由正则表达式给出:
(+-)?(0-9)*(.,)?(0-9)+
除了排版符号、整数部分和小数部分之外,解析器不需要做任何其他事情。
编辑:
这个问题似乎需要澄清一下,所以就在这里。我正在尝试设计和实现一个宏(类似于\num
),它将读取作为参数传递的数字并以自己的方式格式化。到目前为止,我已将此任务分解为以下步骤:
检查参数是否为有效数字,即是否与上面的正则表达式一致(这是我在 TeX 中很难做到的部分)。我特别想知道我们是否可以在宏参数中检测空格,例如“12 345”、“12 .3”、“12. 34”、“- 1”、“+ 1”,以及如何检测宏参数中的其他非数字,例如原始控制序列和字母(
\num
可以吗);“提取”符号、整数部分和小数部分,每个都在单独的宏中(一旦我知道如何实现步骤 1,我认为这很容易做到);
使用符号、整数和小数部分(已实现)来格式化和排版给定宏的数字。
所以,基本上我需要第 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
再说一次,我并没有禁止所有可能的空间:虽然可行但很乏味,所以我宁愿不这样做!