如何构建包含 pythontex 或 fancyvrb 环境的宏?

如何构建包含 pythontex 或 fancyvrb 环境的宏?

目标:我想创建一个从宏内部执行 pythontex 代码块的命令。

应用:我有一个宏,用于排版一些方程式。我希望它们的使用副作用是在指定环境中执行静态代码。

这个问题可以归结为解决“为什么 verbatim 在... 内不起作用?”对于这个特定的 MWE。在这个问题中Geoffrey Poore 指出这些环境不能内联使用,但这并不能阻止我需要解决方案。(例如,放置filecontents*在宏旁边,将其逐字内容传递到在会话内执行它的命令中)。

\documentclass{minimal}
\usepackage{pythontex}

\newcommand\runsSomeCode[0]{
    \begin{sympycode}
        j = 8
    \end{sympycode}
}

\begin{document}
    \runsSomeCode{}
    \begin{equation}
        j = \sympy{j}
    \end{equation}
\end{document}

无论是否使用Verbatimsympycode块,结果都是预期的:

! FancyVerb Error:
  Extraneous input `j = 8 \end {sympycode} {}' between \begin{sympycode}[<key=val
ue>] and line end
.
\FV@Error ... {FancyVerb Error:
\space \space #1
}

l.11     \runsSomeCode{}

此解决方案的必要条件

  1. 运行代码:我不是在寻找文本格式化解决方案。我不是在寻找等宽字体或代码列表。我根本不需要 Python 输出。

  2. 适合会话:我有多个会话,它们可能需要或不需要在其上下文中执行相同的代码。如果没有这个要求,我只需在sympyblock宏外部创建一个 Python 函数并通过\sympy{}或调用它即可\py{}

  3. 必须通过宏调用:我没有能力在文档的顶层结构中创建自定义环境。

  4. 必须在数学模式下运行

  5. 避免使用外部输入文件:我使用构建我的文档.latexmkrc,因此在编译结束时删除临时文件的解决方案是可以接受的。使用filecontents来生成输出也是公平的游戏。

目前,如果这导致我的文档无法被 depythontex 处理,我根本不担心。

答案1

也许你可以使用解析's +v-argument-type 让 LaTeX 逐字读取内容。

壳牌-package 提供命令\ShellEscape作为将命令写入控制台/shell 的一种方式。
您可能可以使用它来pythontex“从 LaTeX-run 内部”进行调用。

如果在我的系统上我将以下代码保存为test.tex

\documentclass{minimal}

\usepackage{shellesc}
\ShellEscape{pythontex \jobname.pytxcode }

\usepackage{amsmath}
\usepackage{xparse}
\usepackage[keeptemps, gobble=auto]{pythontex}

\newcommand\runSomeCodeCatcodeBegingroup{%
  \begingroup
  \catcode`\^^I=12 %
  \catcode`\^^M=12 %
  \newlinechar=\endlinechar
}%
\NewDocumentCommand{\runSomeCode}{}{%
  \runSomeCodeCatcodeBegingroup
  \runSomeCodeInternal
}%
\begingroup
\catcode`\^^M=12\relax%
\NewDocumentCommand{\runSomeCodeInternal}{+v+v}{%
  \endgroup%
  \NewDocumentCommand{\runSomeCodeInternal}{+v+v}{%
     \scantokens{\endgroup#1[##1]^^M##2^^M#2}%
     \ignorespaces%
  }%
}%
\runSomeCodeInternal{\begin{sympycode}}{\end{sympycode}}%

\NewDocumentCommand{\newCodeSnippet}{m}{%
  \runSomeCodeCatcodeBegingroup
  \newCodeSnippetInternal{#1}%
}%

\NewDocumentCommand{\newCodeSnippetInternal}{m+v+v}{%
  \endgroup
  \newcommand*{#1}[1][#2]{\scantokens{\runSomeCode{##1}{#3}}}%
  \ignorespaces
}%

\begin{document}

\newCodeSnippet{\functionExpression}{SessionA}{functionExpression = x**9 + 7*z}

\runSomeCode{SessionA}{x, z = symbols('x z')}
\functionExpression

\runSomeCode{SessionB}{
  x = 2
  z = 3
}
\functionExpression[SessionB]


\begin{equation*}
\sympy[SessionA]{x} = \sympy[SessionB]{x};
\sympy[SessionA]{z} = \sympy[SessionB]{z}
\to \frac{d}{dz} f\left(z\right) = \sympy[SessionA]{functionExpression} = \sympy[SessionB]{functionExpression}
\end{equation*}


\runSomeCode{SessionC}{
  x = 1
  z = 1
}
\functionExpression[SessionC]

\begin{equation*}
\sympy[SessionA]{x} = \sympy[SessionC]{x};
\sympy[SessionA]{z} = \sympy[SessionC]{z}
\to \frac{d}{dz} f\left(z\right)  = \sympy[SessionA]{functionExpression} = \sympy[SessionC]{functionExpression}
\end{equation*}


\end{document}

并通过 shell 命令编译两次

pdflatex -shell-escape test.tex

,然后我得到这个:

在此处输入图片描述

答案2

我找到了一种适用于 和 的解决方案fancyvrbpythontex此解决方案适用于fancyvrb该解决方案在文档中filecontentsdef。希望这能帮助其他人解决看似常见问题的问题。

\usepackage[keeptemps, gobble=auto]{pythontex}
\usepackage{xparse}
\usepackage{filecontentsdef}

% [1] store tabs as active characters so they can be handled by fancyvrb
{\catcode`\^^I=\active\gdef\FCDtabtomacro{\noexpand^^I}}

% [2]
\begin{filecontentsdefmacro}{\testwe}
    x, z = symbols('x z')
    functionExpression = x**9 + 7*z
\end{filecontentsdefmacro}

% Define our macro that should execute the verbatim code block
\NewDocumentCommand\nestedcode{m}{{% [3]
    \renewcommand*\FCDprintenvname{sympycode}% [4]
    \renewcommand*\FCDprintenvoptions{% [5] environment options can be set
        \unexpanded{[#1]}%
    }%
    \filecontentsprint\testwe % [6] we could pass a csname here
    \frac{d}{dz} f\left(z\right) = \sympy[#1]{functionExpression}
}}

在您自己的文档中应用此解决方案的步骤如下:

  1. 根据filecontentsdef包,我们将标签保存为活动角色。
  2. 通过宏功能定义您的内容filecontentsdef。这些宏稍后将用作我们希望执行的代码的标识符。
  3. 构建任何你想要的宏。我利用了xparse在这里使用它是为了在“复杂”的调用层次结构中强调这段代码(如果有任何效果的话)。如果你想防止污染定义,那么将宏主体包装在一个组中按照正常的 LaTeX 惯例。
  4. 设置保存的宏希望扩展的环境
  5. 设置该环境接受的任何可选参数(例如 的会话名称sympy
  6. 调用\filecontentsprint以在所需的上下文中扩展已保存的内容。

为了演示所需的行为,以下文档:

\begin{document}
    An expression, nested within an equasion environment, producing text while
    modifying a session whose name was passed by argument:
    \begin{equation}
        \nestedcode{sessionname}
    \end{equation}

    Confirming that the session does, in fact, subsequently contain those symbols:
    \begin{equation}
        \sympy[sessionname]{x}
    \end{equation}
\end{document}

产生所需的输出。

演示输出

答案3

很明显,您必须为字符提供正确的 catcode,这可以通过以下方式完成:

  • 正确的方法:查找包的文档,看看是否有任何命令可以在不改变 catcode 的情况下工作。

    对于verbatim\texttt,对于pythontex\py\pyc,对于listings\lstinline

    也可以看看:

  • 手动方式:您必须以某种方式弄清楚环境需要哪些 catcode,然后手动将字符转换为正确的 catcode 并将其提供给环境。

  • 自动方式等效(旧引擎):将内容写入外部文件,\input该文件,然后(可选)删除该文件。

  • 自动方式:使用合适的 catcodes 创建一个标记列表,然后\scantokens得出结果。 (用于以下答案)

没有其他办法,因为为了知道哪个 catcode 是正确的,你必须运行代码,一旦执行代码,就无法“撤消”效果。


下面的解决方案使用的\scantokens方法与其他答案...但希望更容易理解。

\documentclass{article}
\usepackage{fancyvrb}
\begin{document}
\ExplSyntaxOn
% ======== Define \my__tl_set_verbatim : similar to \tl_set:Nn, but second argument is of xparse's +v type. ========
\NewDocumentCommand \my__tl_set_verbatim {m +v} {
    \tl_set:Nn #1 {#2}
}
% ======== Define some auxiliary helper TLs. ========
\tl_set:Nx \my__newline_char_other_cat   {\char_generate:nn {`\^^M} {12}}  % A newline character with "other" category code.
\my__tl_set_verbatim \my__begin_verbatim {\begin{Verbatim}[frame=single, numbers=left]}
\my__tl_set_verbatim \my__end_verbatim   {\end{Verbatim}}
% ======== Define the main command. ========
\cs_set:Npn \my__command:n #1 {
    \exp_args:Nx \scantokens {
        \my__begin_verbatim     \my__newline_char_other_cat
            x~ =~ #1            \my__newline_char_other_cat
        \my__end_verbatim       \my__newline_char_other_cat
    }
}
% ======== Use that command. ========
\my__command:n {123}
\ExplSyntaxOff
\end{document}

该解决方案使用 fancyvrb 的Verbatim环境进行演示,但它应该适用于其他环境。

结果是:

输出

当然,要使用\ExplSyntaxOn模式之外的命令,请定义一个适当命名的命令。


实用提示:

  • 为了进行调试,将命令定义更改为

    \cs_set:Npn \my__command:n #1 {
        \tl_set:Nx \my__tmp {
           \my__begin_verbatim     \my__newline_char_other_cat
              x~ =~ #1             \my__newline_char_other_cat
           \my__end_verbatim       \my__newline_char_other_cat
        }
        \tl_analysis_show:N \my__tmp
        \exp_args:NV \scantokens {
           \my__tmp
        }
    }
    

    查找文档以\tl_analysis_show:N获取更多信息。

  • 不起作用的事情:

    • 使用\detokenize{\end{Verbatim}}. 因为环境期望\end{Verbatim}完全按照 而不是(和\end {Verbatim}之间有一个空格)来结束环境。\end{

    • 在定义行中包含换行符\my__begin_verbatim

      \my__tl_set_verbatim \my__begin_verbatim {\begin{Verbatim}[frame=single, numbers=left]
      }
      

      根据我的其他答案xparse 的+v参数将在模式下将换行符转换为空格字符\ExplSyntaxOn。(但是,切换到 \ExplSyntaxOff定义之前是有效的)

相关内容