有没有办法自动将 \def 宏转换为 \newcommand 宏?

有没有办法自动将 \def 宏转换为 \newcommand 宏?

我有一个很大的宏文件,它们都是这种形式:

\def\eclaire{\mathbb}
\def\R{\ensuremath{\eclaire R}}

我想以\newcommand表格形式拥有它们,对于那些宏来说,这很容易,我只需要\def\newcommand这样替换:

\newcommand \eclaire{\mathbb}
\newcommand \R{\ensuremath{\eclaire R}}

但对于其他一些宏来说,例如这个:

\def\rond#1{\build#1_{}^{\>\circ}}

我不能,因为解决方案是:

\newcommand\rond[1]{\build#1_{}^{\>\circ}}

所以我的问题是,是否有一个脚本可以自动将我的\def命令转换为\newcommand

预先感谢您的帮助。

答案1

\def并且\newcommand并不完全等同,因为\newcommand如果要求重新定义现有命令,则会抛出错误。\newcommand因此,如果您想定义新命令,则首选它,但它不能用于重新定义现有命令。这不是脚本可以轻松检测到的东西,所以我现在将忽略它,即使它与您的预期用例相关,例如,您有

\newenvironment{Cases}{%
  \left\lbrace
  \def\arraystretch{1.2}%
  \array{@{}l@{\quad}l@{}}
}{%
  \endarray\right.%
}

此外,\def\newcommand不支持完全相同的语法定义。\newcommand只能定义最多九个(强制)参数的宏,其中第一个参数可以是可选的(并且用方括号括起来)。\def还可以支持其他分隔宏。我假设您的s 仅采用或的\def形式。这已经由 提到过\def\<cmd>{...}\def\<cmd>#1...#n焦耳伏特 在评论中. 这个问题

\def\build#1_#2^#3{\mathrel{\mathop{\kern 0pt#1}\limits_{#2}^{#3}}} %CLENET

\newcommand也不允许使用像\long\outer、这样的前缀\protected。因此,如果您使用这些前缀,您也无法轻松切换到\newcommand。(除了可能映射到/的\long/not 。)我假设您不使用任何这些前缀,我将使用,这使得它的参数,但您当然可以使用来获取“un ”参数。\long\newcommand\newcommand*\newcommand\long\newcommand*\long

如果我们允许这些简化

s/\\def(\\[^{#]*)(?:#+([0-9]))*{/\\newcommand{$1}[0$2]{/g

产生可接受的结果。

以下(粗略近似)Lua 脚本执行的操作非常类似。我没能在一个组中完成参数结构#1、 、 ... 的匹配,因此我选择使用循环来完成此操作。#1#2

local io = require('io')
local string = string

local filename = arg[1] or nil

function newcommandify(content)
  content = string.gsub(content, "\\def(\\[^{#]*){",
                        "\\newcommand{%1}{")
  local iargs = ''
  for i=1,9 do
    iargs = iargs .. "#+" .. i
    content = string.gsub(content, "\\def(\\[^{#]*)" .. iargs .. "{",
                          "\\newcommand{%1}[".. i .. "]{")
  end
  return content
end

if filename then
  local file_handle = assert(io.open(filename, 'r'))
  local content = file_handle:read('*all')
  io.close(file_handle)

  content = newcommandify(content)

  file_handle = assert(io.open(filename .. "-newcommand", 'w'))
  file_handle:write(content)
  io.close(file_handle)
end

另存为def-to-newcommand.lua并运行

texlua def-to-newcommand.lua <yourfilename>

<yourfilename>-newcommand\defs 转换为得到\newcommand

答案2

为什么您希望将 方面的作业转换\def为 方面的作业\newcommand

如果只是为了确保已经定义的宏不会被覆盖,那么可以采用另一种方法:

基本\newcommand行为如下:

它检查星号和可选参数,并应用内核宏\@ifdefinable来查明所讨论的控制序列标记是否已在当前范围内定义。

如果是这样,\@ifdefinable将抛出一个错误消息并且不会(重新)定义有问题的控制序列标记。

如果不是这样,则所讨论的控制序列标记将根据\def(,可能前面是\long)来定义。在处理可选参数的情况下,用于检测可选参数是否存在的例程\newcommand也将包含在由 执行的定义中。

注意事项\newcommand

  • \newcommand本身就是一个宏/是基于宏的“机制”。如果宏的参数包含根据 定义的标记\outer,则 (La)TeX 将发出错误消息。因此:如果您尝试通过 定义\newcommand已经根据 定义的命令\outer,您将不会收到有关已定义控制序列的错误消息,但您会收到一条错误消息! Forbidden control sequence found while scanning use of \new@command.

  • \newcommand将检查命令是否已在当前范围内定义。如果没有,它将仅在当前范围内定义该命令。因此,当打算全局定义宏时,例如通过 ,检查\newcommand并不完全安全\newcommand\foo{bar}\global\let\foo=\foo

  • 实际上,\newcommand“机制”并不检查命令是否已经定义,而是检查命令是否已经定义为原语含义以外的其他内容\relax。它通过应用于\csname..\endcsname命令的名称(通过应用\string和删除前导反斜杠形成)然后\ifx检查命令的含义是否等于\relax-primitive 的含义来实现这一点。它这样做是因为\csname..\endcsname它本身(无论 -parameter 的值如何\globaldefs)在当前范围内(仅)将 -primitive 的含义分配\relax给它形成的标记,以防它们在形成时未定义。因此,\newcommand默默地重新定义已经定义且含义等于 -primitive 的控制序列标记\relax。例如,您可以这样做

    \let\MyCommand=\relax
    % now \MyCommand is defined and its meaning equals the meaning of the \relax-primitive
    \newcommand\MyCommand{This is the redefinition of MyCommand which gets carried out without warnings/error-messages.}
    

    您还可以在本地范围/组内执行此操作,以导致该范围内未定义\newcommand\MyCommand

    需要注意的是:如果你做了类似的事情

    \begingroup
    \let\MyCommand=\relax
    % now \MyCommand is defined and its meaning equals the meaning of the \relax-primitive
    \newcommand\MyCommand{This is the redefinition of MyCommand which gets carried out without warnings/error-messages.}
    \global\let\MyCommand=\MyCommand
    \endgroup
    

    \MyCommand可能已经在组外定义,并且它将在组外/所有范围内被覆盖,而无需任何通知。

我建议的方法是:

方面的任务\def总是有模式的

⟨prefix⟩\def⟨control sequence token⟩⟨parameter text⟩{⟨replacement text⟩}

, 和⟨字首⟩\global=空虚或和/或\long和/或的组合\outer

(使用\gdef\edef或,\xdef 图案看起来会相应。)

你可以实现一个宏\definedchecker,通过捕获左括号分隔的参数(→参见 TeXbook 中的#1#{-notation)来收集⟨字首⟩,那个\def,那个⟨控制序列标记⟩⟨参数文本⟩然后检查它是否包含\def\gdef\edef\xdef,如果是,则使用该信息拆分⟨字首⟩,那个\def,那个⟨控制序列标记⟩⟨参数文本⟩在应用之前将其分开\@ifdefinable- 但请注意,\@ifdefinable当不仅在当前范围内而且在全局范围内定义控制序列标记时,-check 并不完全保存。

换句话说:您可以实现一个宏\definedchecker,将其添加到您的\def/ \gdef/ \edef/\xdef序列中,并检查所讨论的控制序列标记是否已定义在当前范围内如果是,则传递错误消息,如果不是,则执行分配。
!!!请注意,当进行全局定义时,检查当前范围内定义的控制序列标记并不完全安全!!!

% Compile this example by calling LaTeX from a shell/command-prompt/console
% where you can also see the messages of the program!
%
\documentclass{article}
\makeatletter
%%<-------------------------------------------------------------------->
%% Check whether argument is empty:
%%......................................................................
%% \UD@CheckWhetherNull{<Argument which is to be checked>}%
%%                     {<Tokens to be delivered in case that argument
%%                       which is to be checked is empty>}%
%%                     {<Tokens to be delivered in case that argument
%%                       which is to be checked is not empty>}%
%% The gist of this macro comes from Robert R. Schneck's \ifempty-macro:
%% <https://groups.google.com/forum/#!original/comp.text.tex/kuOEIQIrElc/lUg37FmhA74J>
\newcommand\UD@CheckWhetherNull[1]{%
  \romannumeral0\expandafter\@secondoftwo\string{\expandafter
  \@secondoftwo\expandafter{\expandafter{\string#1}\expandafter
  \@secondoftwo\string}\expandafter\@firstoftwo\expandafter{\expandafter
  \@secondoftwo\string}\expandafter\expandafter\@firstoftwo{ }{}%
  \@secondoftwo}{\expandafter\expandafter\@firstoftwo{ }{}\@firstoftwo}%
}%
%%<-------------------------------------------------------------------->
%% Code for definedchecker
%%......................................................................     
\@ifdefinable\UD@gobbleto@def{\long\def\UD@gobbleto@def#1\def{}}%
\@ifdefinable\UD@gobbleto@gdef{\long\def\UD@gobbleto@gdef#1\gdef{}}%
\@ifdefinable\UD@gobbleto@edef{\long\def\UD@gobbleto@edef#1\edef{}}%
\@ifdefinable\UD@gobbleto@xdef{\long\def\UD@gobbleto@xdef#1\xdef{}}%
\newcommand\UD@CheckWhetherNoDef[1]{\expandafter\UD@CheckWhetherNull\expandafter{\UD@gobbleto@def#1\def}}%
\newcommand\UD@CheckWhetherNoGdef[1]{\expandafter\UD@CheckWhetherNull\expandafter{\UD@gobbleto@gdef#1\gdef}}%
\newcommand\UD@CheckWhetherNoEdef[1]{\expandafter\UD@CheckWhetherNull\expandafter{\UD@gobbleto@edef#1\edef}}%
\newcommand\UD@CheckWhetherNoXdef[1]{\expandafter\UD@CheckWhetherNull\expandafter{\UD@gobbleto@xdef#1\xdef}}%
\@ifdefinable\UD@catchto@gef{\long\def\UD@catch@def#1\def#2#3#{\innerdefinedchecker{#1}{\def}{#2}{#3}}}%
\@ifdefinable\UD@catchto@gdef{\long\def\UD@catch@gdef#1\gdef#2#3#{\innerdefinedchecker{#1}{\gdef}{#2}{#3}}}%
\@ifdefinable\UD@catchto@edef{\long\def\UD@catch@edef#1\edef#2#3#{\innerdefinedchecker{#1}{\edef}{#2}{#3}}}%
\@ifdefinable\UD@catchto@xdef{\long\def\UD@catch@xdef#1\xdef#2#3#{\innerdefinedchecker{#1}{\xdef}{#2}{#3}}}%
\newcommand\innerdefinedchecker[5]{\@ifdefinable{#3}{#1#2#3#4{#5}}}%
\@ifdefinable\definedchecker{%
  \long\def\definedchecker#1#{%
     \UD@CheckWhetherNoDef{#1}{%
       \UD@CheckWhetherNoGdef{#1}{%
         \UD@CheckWhetherNoEdef{#1}{%
           \UD@CheckWhetherNoXdef{#1}{%
             #1%
           }{\UD@catch@xdef#1}%
         }{\UD@catch@edef#1}%
       }{\UD@catch@gdef#1}%
     }{\UD@catch@def#1}%
  }%
}%
\makeatother

\definedchecker\xdef\eclaire{\noexpand\mathbb}
\show\eclaire

\definedchecker\def\R{\ensuremath{\eclaire R}}
\show\R

\definedchecker\outer\global\long\def\myWeirdCommand#1Delimier#2delimiter#{This is my weird command}
\show\myWeirdCommand

% These throw "command already defined"-errors and leave the previous meanings untouched:

\definedchecker\edef\eclaire{Weird Redefinition of eclaire}
\show\eclaire

\definedchecker\gdef\R{Weird Redefinition of R}
\show\R

% Don't do this as \myWeirdCommand is outer.
% \newcommand\myWeirdCommand{Blah}

\begin{document}
\end{document}

以下是我收到的信息测试日志将其另存为测试.tex并使用 pdflatex 进行编译:

> \eclaire=macro:
->\mathbb .
l.111     \show\eclaire

? 
> \R=macro:
->\ensuremath {\eclaire R}.
l.114     \show\R

? 
> \myWeirdCommand=\long\outer macro:
#1Delimier#2delimiter{->This is my weird command{.
l.117     \show\myWeirdCommand

? 

! LaTeX Error: Command \eclaire already defined.
               Or name \end... illegal, see p.192 of the manual.

See the LaTeX manual or LaTeX Companion for explanation.
Type  H <return>  for immediate help.
 ...                                              

l.121 ...ef\eclaire{Weird Redefinition of eclaire}

? 
> \eclaire=macro:
->\mathbb .
l.122     \show\eclaire

? 

! LaTeX Error: Command \R already defined.
               Or name \end... illegal, see p.192 of the manual.

See the LaTeX manual or LaTeX Companion for explanation.
Type  H <return>  for immediate help.
 ...                                              

l.124 ...edchecker\gdef\R{Weird Redefinition of R}

? 
> \R=macro:
->\ensuremath {\eclaire R}.
l.125     \show\R

从这些消息中你可以看到:

如果当前范围内的相关命令已被定义为具有 -primitive 之外的含义\relax\definedchecker则会产生错误并且不会执行分配。
否则将执行分配。

请注意,关于避免覆盖已定义的宏的定义,当涉及到全局定义宏时,此策略并不完全安全。

答案3

Python 程序def_to_newcommand.py

#!/usr/bin/env python3

import argparse
import re


def main():
    args = parse_command_line()
    data = read(args.input)
    data = convert(data)
    write(args.output, data)


def parse_command_line():
    parser = argparse.ArgumentParser(
        description='Replace \\def with \\newcommand where possible.',
    )
    parser.add_argument(
        'input',
        help='TeX input file with \\def',
    )
    parser.add_argument(
        '--output',
        '-o',
        required=True,
        help='TeX output file with \\newcommand',
    )

    return parser.parse_args()


def read(path):
    with open(path, mode='rb') as handle:
        return handle.read()


def convert(data):
    return re.sub(
        rb'((?:\\(?:expandafter|global|long|outer|protected)'
        rb'(?: +|\r?\n *)?)*)?'
        rb'\\def *(\\[a-zA-Z]+) *(?:#+([0-9]))*\{',
        replace,
        data,
    )


def replace(match):
    prefix = match.group(1)
    if (
            prefix is not None and
            (
                b'expandafter' in prefix or
                b'global' in prefix or
                b'outer' in prefix or
                b'protected' in prefix
            )
    ):
        return match.group(0)

    result = rb'\newcommand'
    if prefix is None or b'long' not in prefix:
        result += b'*'

    result += b'{' + match.group(2) + b'}'
    if match.lastindex == 3:
        result += b'[' + match.group(3) + b']'

    result += b'{'
    return result


def write(path, data):
    with open(path, mode='wb') as handle:
        handle.write(data)

    print('=> File written: {0}'.format(path))


if __name__ == '__main__':
    main()

使用示例:

python3 def_to_newcommand.py foo.tex --output foo_changed.tex

以下定义

\def\eclaire{\mathbb}
\def\R{\ensuremath{\eclaire R}}
\def\rond#1{\build#1_{}^{\>\circ}}
\def\build#1#2#3{\mathrel{\mathop{\kern 0pt#1}\limits_{#2}^{#3}}}
\def\build#1_#2^#3{\mathrel{\mathop{\kern 0pt#1}\limits_{#2}^{#3}}}

\long\def\eclaire{\mathbb}
\long\def\R{\ensuremath{\eclaire R}}
\long\def\rond#1{\build#1_{}^{\>\circ}}
\long\def\build#1#2#3{\mathrel{\mathop{\kern 0pt#1}\limits_{#2}^{#3}}}
\long\def\build#1_#2^#3{\mathrel{\mathop{\kern 0pt#1}\limits_{#2}^{#3}}}

\global\long\def\eclaire{\mathbb}
\global\def\eclaire{\mathbb}
\protected\def\eclaire{\mathbb}

  \long
  \def \eclaire {\mathbb}

转换为:

\newcommand*{\eclaire}{\mathbb}
\newcommand*{\R}{\ensuremath{\eclaire R}}
\newcommand*{\rond}[1]{\build#1_{}^{\>\circ}}
\newcommand*{\build}[3]{\mathrel{\mathop{\kern 0pt#1}\limits_{#2}^{#3}}}
\def\build#1_#2^#3{\mathrel{\mathop{\kern 0pt#1}\limits_{#2}^{#3}}}

\newcommand{\eclaire}{\mathbb}
\newcommand{\R}{\ensuremath{\eclaire R}}
\newcommand{\rond}[1]{\build#1_{}^{\>\circ}}
\newcommand{\build}[3]{\mathrel{\mathop{\kern 0pt#1}\limits_{#2}^{#3}}}
\long\def\build#1_#2^#3{\mathrel{\mathop{\kern 0pt#1}\limits_{#2}^{#3}}}

\global\long\def\eclaire{\mathbb}
\global\def\eclaire{\mathbb}
\protected\def\eclaire{\mathbb}

  \newcommand{\eclaire}{\mathbb}

评论:

  • \def映射到\newcommand*并且\long\def映射到\newcommand

  • 如果可以检测到\global\protected\outer,则定义保持不变。

  • 仅支持空参数文本(\def\foo{...})或未分隔的参数( )。不支持任意参数文本()和分隔参数()。\def\foo#1#2#3#4#5#6#7#8#9{...}\newcommand\def\foo bar{...}\def\foo[#1]{...}

  • 对命令名称后的空格有一定的支持(\def \foo {...})。

  • 如果\expandafter检测到,则定义保持不变。(例如\expandafter\expandafter\expandafter\def\generatecmd{foo}{...}:)

  • 当然,\newcommand如果命令已经定义,则会抛出错误。这需要手动干预,因为存在不同的解决方案:

    • 可以更改命令名称以避免名称冲突。
    • 如果覆盖是故意的,\renewcommand则可解决问题。

相关内容