解析 LaTeX 以输出定义的每个命令的参数数量

解析 LaTeX 以输出定义的每个命令的参数数量

假设我有一个文件 foo.sty,其中包含以下内容:

\newcommand{\a}{x}
\newcommand{\b}[2]{x}
\newcommand{\c}[2][y]{x}
\newenvironment{d}[3]{}{}

我希望能够通过某些软件运行它并生成一个数据文件,说明已定义哪些命令和环境,以及它们有多少个可选和强制参数 - 如下所示:

command,a,0,0
command,b,0,2
command,c,1,1
environment,d,0,3

我可以用 perl 编写一些在大多数情况下都能正常工作的代码,但我想知道是否有任何方法可以做到这一点,从而使其高度可靠。我得到的印象是完全正确地解析 LaTeX 很难,基本上只有 LaTeX 才能做到这一点。有没有办法让 LaTeX 做到这一点,也许使用适当构造的 .tex 文件来提取 foo.sty、进行自省,然后执行 write18 的操作……?

我正在寻找一个开源的解决方案,并且可以在 Linux 上以自动化方式运行(而不是 GUI)。

答案1

将以下代码放在序言或.sty文件中

\usepackage{xparse}
\ExplSyntaxOn
% Define the command that will start the working; we'll redefine \newcommand
% so that \newcommand{\xac}[2][cccc]{abc} will execute
%
%    \command_check:w { \command_check_newcommand:w }{ command } {\xab} 
%
% #1 = \newcommand|\renewcommand|\newenvironment|\renewenvironment (in new form)
% #2 = command|environment
% #3 = the possible *
% #4 = the argument to \newcommand
% #5 = the number of arguments
% #6 = the possible optional argument
% 
% We check if the last argument is missing and take the appropriate action 
\NewDocumentCommand{ \command_check:w }{m m s m O{0} o}
  {
   \IfBooleanTF{#3}
     { \tl_gset:Nn \g_command_check_star_tl { * } \bool_gset_true:N \g_command_check_star_bool }
     { \tl_gset:Nn \g_command_check_star_tl { } \bool_gset_false:N \g_command_check_star_bool }
   \IfNoValueTF{#6}
     { \command_check_noopt:nnnn {#1} {#2} {#4} {#5} }
     { \command_check_opt:nnnnn {#1} {#2} {#4} {#5} {#6} }
  }
% \StartSaveCommands sets up the checks, first by creating aliases for the kernel commands
% and then redefining them as explained above
\NewDocumentCommand{\StartSaveCommands}{}
  {
   \cs_set_eq:NN \command_check_newcommand:w \newcommand
   \cs_set_eq:NN \command_check_renewcommand:w \renewcommand
   \cs_set_eq:NN \command_check_newenvironment:w \newenvironment
   \cs_set_eq:NN \command_check_renewenvironment:w \renewenvironment
   \cs_set:Npn \newcommand { \command_check:w {\command_check_newcommand:w}{command} }
   \cs_set:Npn \renewcommand { \command_check:w {\command_check_renewcommand:w}{command} }
   \cs_set:Npn \newenvironment { \command_check:w {\command_check_newenvironment:w}{environment} }
   \cs_set:Npn \renewenvironment { \command_check:w {\command_check_renewenvironment:w}{environment} }
  }
% \StopSaveCommands restores the kernel commands
\NewDocumentCommand{\StopSaveCommands}{}
  {
   \cs_set_eq:NN \newcommand \command_check_newcommand:w
   \cs_set_eq:NN \renewcommand \command_check_renewcommand:w
   \cs_set_eq:NN \newenvironment \command_check_newenvironment:w 
   \cs_set_eq:NN \renewenvironment \command_check_renewenvironment:w
  }
% \WriteSaveCommands will take care of writing out the list of commands
% with their number of arguments
\NewDocumentCommand{\WriteSaveCommands}{}
  {
   \iow_open:Nn \g_command_check_write { \jobname.cmd }
   \seq_map_inline:Nn \g_command_check_seq {\iow_now:Nx \g_command_check_write { ##1 } }
  }
% Allocate a write stream
\iow_new:N \g_command_check_write
% If there's no optional argument, say \newcommand{\xab}[1]{aaa},
% we want to write out "command,\xab ,1,0", so we store that
% string into an item appended to the sequence \g_command_check_seq
\cs_new:Npn \command_check_noopt:nnnn #1 #2 #3 #4
  {
   \seq_gput_right:Nx \g_command_check_seq
     {
      #2 \bool_if:NT \g_command_check_star_bool { (*) }, 
      \tl_to_str:n {#3} , #4, 0
     }
   \exp_after:wN #1 \g_command_check_star_tl {#3} [#4]
  }
% If there's an optional argument, say \newcommand{\xac}[2][cccc]{abc},
% we want to write out "command,\xac,1,1[cccc]; everything as before,
% but we decrease by 1 the number of stated arguments
\cs_new:Npn \command_check_opt:nnnnn #1 #2 #3 #4 #5
  { 
   \seq_gput_right:Nx \g_command_check_seq
     {
      #2 \bool_if:NT \g_command_check_star_bool { (*) }, \tl_to_str:n {#3} , 
      \int_to_arabic:n {#4-1} , 1[\tl_to_str:n{#5}]
     }
   \exp_after:wN #1 \g_command_check_star_tl {#3}[#4][#5]
  }
% Allocate the sequence, the token list variable and the boolean
\seq_new:N \g_command_check_seq
\tl_new:N \g_command_check_star_tl
\bool_new:N \g_command_check_star_bool
\ExplSyntaxOff

现在,您可以在\StartSaveCommands和之间输入您的个人命令\StopSaveCommands,例如

\documentclass{article}

<the above code>

\StartSaveCommands

\newcommand{\xaa}{abc}
\newcommand*{\xab}[1]{abc}
\newcommand{\xac}[2][cccc]{abc}
\renewcommand{\phi}{\varphi}
\newenvironment{xad}{}{}
\newenvironment{xae}[1]{}{}
\newenvironment{xaf}[2][sss]{}{}

\StopSaveCommands
\WriteSaveCommands

\begin{document}

<text>

\end{document}

这将定义命令和环境并编写一个扩展名为的文件.cmd;在我们的示例中,它将包含

command,\xaa ,0,0
command(*),\xab ,1,0
command,\xac ,1,1[cccc]
command,\phi ,0,0
environment,xad,0,0
environment,xae,1,0
environment,xaf,1,1[sss]

\newcommand所有用或定义以及在和\renewcommand之间创建的环境的命令都将在文件中创建一个条目。\StartSaveCommands\StopSaveCommands.cmd

(注:编辑时也考虑到了\newcommand*\newenvironment*

答案2

在日志文件中你会发现:

command, ,\A,0,0
command,*,\AB,0,0
command, ,\B,2,0
command,*,\BA,2,0
command, ,\C,2,y
command,*,\CBA,2,y
environment, ,D,3,0
environment,*,DD,3,0
environment, ,foobar,3,0
environment,*,FooBar,3,y

它还处理星号版本\newcommand\newenvironment

也可以将其保存在自己的文件中。代码还可以扩展到\renewcommand等等...

\documentclass{minimal}

\makeatletter
\newif\if@star 
\newif\if@command
\let\NewCommand\newcommand
\let\NewEnvironment\newenvironment
\def\newcommand{\global\@commandtrue%
  \@ifnextchar*{\global\@startrue\test@CommEnv}{\global\@starfalse\test@CommEnv*}}
\def\newenvironment{\global\@commandfalse%
  \@ifnextchar*{\global\@startrue\test@CommEnv}{\global\@starfalse\test@CommEnv*}}
\def\test@CommEnv*#1{\@ifnextchar[{\test@CommEnv@i{#1}}{%
    \if@command
      \typeout{command,\if@star*\else\space\fi,\string#1,0,0}%
      \def\next{\NewCommand{#1}}%
    \else
      \typeout{environment,\if@star*\else\space\fi,\string#1,0,0}%
      \def\next{\NewEnvironment{#1}}
    \fi\next}}
\def\test@CommEnv@i#1[#2]{%
  \@ifnextchar[{\test@CommEnv@ii{#1}[#2]}{%
    \if@command
      \typeout{command,\if@star*\else\space\fi,\string#1,#2,0}%
      \def\next{\NewCommand{#1}[#2]}%
    \else
      \typeout{environment,\if@star*\else\space\fi,#1,#2,0}%
      \def\next{\NewEnvironment{#1}[#2]}
    \fi\next}}
\def\test@CommEnv@ii#1[#2][#3]{%
    \if@command
      \typeout{command,\if@star*\else\space\fi,\string#1,#2,#3}%
      \def\next{\NewCommand{#1}[#2][#3]}%
    \else
      \typeout{environment,\if@star*\else\space\fi,#1,#2,#3}%
      \def\next{\NewEnvironment{#1}[#2][#3]}
    \fi\next}

\newcommand{\A}{x}
\newcommand*{\AB}{x}
\newcommand{\B}[2]{x}
\newcommand*{\BA}[2]{x}
\newcommand{\C}[2][y]{x}
\newcommand*{\CBA}[2][y]{x}
\newenvironment{D}[3]{}{}
\newenvironment*{DD}[3]{}{}
\newenvironment{foobar}[3]{}{}
\newenvironment*{FooBar}[3][y]{}{}

\begin{document}
\A \B{1}{2} \C[1]{2} \begin{D}{1}{2}{3} D \end{D}
\end{document}

相关内容